Screen set 1 character 32 and 96 are both space.

Basic and Machine Language

Moderator: Moderators

RJBowman
Vic 20 Enthusiast
Posts: 198
Joined: Tue Oct 25, 2011 7:50 pm

Post by RJBowman »

Has anyone ever tried to produce executable machine code by putting characters on the screen?
User avatar
Jeff-20
Denial Founder
Posts: 5759
Joined: Wed Dec 31, 1969 6:00 pm

Post by Jeff-20 »

The closest I have come: I've printed to screen (text matching background to make it invisible) to produce custom characters. When storing the custom character set at 7168, the on-screen data can be accessed as shift mode characters. Nothing spectacular, but if I've done it in BASIC, I'm sure someone would do the same in ML.
High Scores, Links, and Jeff's Basic Games page.
User avatar
Mike
Herr VC
Posts: 4838
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

RJBowman wrote:Has anyone ever tried to produce executable machine code by putting characters on the screen?
From the CPU's view, there's no difference between RAM committed for screen display and other RAM. I've seen quite a few uses, for example decompressors which execute in the screen buffer to unpack data in the BASIC memory range. Just take a look at some C64 intros or demos.

Here's a particularly nice example:

Image

... which can be entered like this (in quoted PETSCII):

Code: Select all

PRINT"{CLR,RVS ON}){RVS OFF}A{RVS ON}(Q{RVS OFF}+{SPACE}3{RVS ON,SHIFT-E,
RVS OFF}X{RVS ON}%{RVS OFF}"CHR$(34)CHR$(34)CHR$(20)"{SHIFT-POUND}B{RVS ON}E
{RVS OFF}-{RVS ON}%{RVS OFF}#{SHIFT-POUND}@{RVS ON}E{RVS OFF}.{SPACE,SHIFT-Y,
RVS ON,SHIFT-F,RVS OFF,SHIFT-L,C=-H,RVS ON,SHIFT-D}":SYS256*PEEK(648)
It fits on one logical line using the abbreviations ? for PRINT, C{SHIFT-H} for CHR$, P{SHIFT-E} for PEEK and S{SHIFT-Y} for SYS.

Pop-Quiz: what's the above code supposed to do?
RJBowman
Vic 20 Enthusiast
Posts: 198
Joined: Tue Oct 25, 2011 7:50 pm

Post by RJBowman »

I recall that there were a few microcomputer BASICs that did not offer the poke and peek command, or any other good way for the user to enter machine code. The print-code-to-screen technique could be used as a workaround for these constrained machines.

But the only home computer BASIC without poke and peek that comes to mind is the TI-994A, which has separate video memory that the CPU can't access directly.
RJBowman
Vic 20 Enthusiast
Posts: 198
Joined: Tue Oct 25, 2011 7:50 pm

Post by RJBowman »

I just thought of the killer app for machine code printed to screen. To memory-optimize the BASIC code for poking machine code into memory.

Here's where the savings come in:

The standard method is to convert the machine code into a sequence of decimal numbers, which are put into data statements to be read, then poked into memory. This method requires that one to three data-statement bytes be used to represent each byte of machine code, with comma separators adding an additional data-statement byte per byte of code represented.

But if you convert the code to printable strings, you can print any of 128 values to the screen with a single byte of overhead, and with the <reverse> and <normal> control codes you can toggle to the other 128 possible values. So that works out to, at most, two "data" bytes per byte of code; in practice, you would not have to toggle the print mode for every code byte, so it might average 1.5 data bytes per code byte. And of course there would also be an overhead for the line number, print-command token, and quotes. I think that it would still be a substantial savings over poking from data statements.

Now you might point out that this system would limit you to machine code fragments that occupy screen memory. Not necessarily.

The first string of code printed to the screen could be a relocatable subroutine, who's purpose is to copy fragments from the screen to other parts of memory. Then the screen could be cleared and filled with more code to be copied by the code from the first iteration.

Why did no one think of this back in the day?
User avatar
Mike
Herr VC
Posts: 4838
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

Why did no one think of this back in the day?
That looks like a general scheme you like to put questions. People in the past were likely smarter than you might think they have been.

DATA loaders with numeric values were an easy method in those days to transcribe a (small) machine code program from a list of opcodes (with the address modes) into decimal, and then into memory. Otherwise, it was always possible to store the memory block as is onto disk or tape, and load it back from there.

You didn't answer my Pop-Quiz. What's the effect of the program I wrote above?
User avatar
darkatx
Vic 20 Afficionado
Posts: 471
Joined: Wed Feb 04, 2009 2:17 pm
Location: Canada

Post by darkatx »

I kept messing up the code by entering C=-H literally until I looked closer to the above code in the screen cap.
Then looking at the code in ML made more sense in the jumps - it resets the End of Basic/Start of Variables pointer and displays a ready message and drops out.
That's as far as I can make out since I never played with the SOB/SOV before. Either the entire Basic is being relocated or its next line is being reset - to me it almost looks like a NEW command from what I can figure. :roll:

EDIT - OMG - this is over my head...lol. I did notice that there was a 0 when I LISTed the program. So there was a line number and that was it and all the RAM was displayed as FREe minus the single line number code. So it printed the line in the screen and then ran from screen memory $1E00 - did the above and dropped out to a ready code.
Learning all the time... :)
User avatar
Mike
Herr VC
Posts: 4838
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

darkatx wrote:I did notice that there was a 0 when I LISTed the program.
Indeed you get that or a similar (and admittedly useless) result when you run this line on a VIC-20 which has just be powered on.

You might want to use this code under other circumstances though ... citing examples would give it away already. :wink:
User avatar
freshlamb
Vic 20 Dabbler
Posts: 76
Joined: Sun Apr 04, 2004 5:38 pm
Website: http://www.rufnoiz.com
Location: Prince Albert SK Can

Post by freshlamb »

Took a while to write out and dis-assemble. I am not too familiar with KERNAL calls, but from what I can see it looks like an UNNEW.

Pretty neat! Too bad it would be hard to type this in every time you wanted to bring back a program. I definitely learned a couple things from this short program.
User avatar
darkatx
Vic 20 Afficionado
Posts: 471
Joined: Wed Feb 04, 2009 2:17 pm
Location: Canada

Post by darkatx »

I never would have figured that out in a million years due to my aversion of the NEW command. I just don't use it...ever. I normally keep hacking away at a program until it works and keep backing up before moving forward on every little change I make.
Programming makes me a packrat. :lol:
Learning all the time... :)
User avatar
Mike
Herr VC
Posts: 4838
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

freshlamb wrote:[...]it looks like an UNNEW.
Spot on! :D

Here's an assembly listing, without instruction addresses as the code is fully relocatable (which is also the reason SYS256*PEEK(648) works at all):

Code: Select all

LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer

JSR $C533   ; re-chain lines (which mainly restores the first
            ; link-pointer)

; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:

CLC
LDA $22
ADC #$02
STA $2D
LDA $23
ADC #$00
STA $2E

JSR $C659   ; CLR. Also initialises other pointers from $2E/$2D.

; At this stage, the entire stack has been purged, so we can't
; simply return to BASIC with RTS. Instead, we jump to the
; routine which first prints the 'READY.' prompt and then
; continues with keyboard entry for the screen editor.

JMP $C474
Pretty neat! Too bad it would be hard to type this in every time you wanted to bring back a program.
There's another version of this routine included in the MG batch suite. It loads with ',8,1' and starts with SYS320.

I frankly admit I derived the PRINT version of this OLD routine from my earlier release just to prove the point: while it might look 'cool' to provide machine code this way, it's just annoying for those who'd have to type it in (as type-in from a magazine page, for example). When it comes to readability, this method easily finishes behind decimal data loaders or hex dump editors (like Compute!'s MLX).
darkatx wrote:my aversion of the NEW command. I just don't use it ... ever
It can also recover the program after a RESET or when the pointers to the start of the variables have not been set after a '?LOAD ERROR'. This error can happen just because the copy on tape had been cropped by accident. However, in that case there's an even easier way to restore the pointers, see here.

Of course with cross-developing the work flow has changed a lot so the occasions where an OLD routine might save your day indeed have become sparse. :wink:
Last edited by Mike on Sun Jun 09, 2013 12:34 pm, edited 1 time in total.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Post by tlr »

Nice way to enter an emergency unnew.

You should be able to cut the jmp $c474 and make the routine end in jmp $c659.
This is because even though $c659 resets the stack pointer it first pops the return address and pushed it back after the sp reset.

This was previously discussed here: thread

Perhaps the print line could be made shorter and/or easier to type by opimizations specifically targetting the number of escape codes and/or the number of characters involving shifting?
User avatar
Mike
Herr VC
Posts: 4838
Joined: Wed Dec 01, 2004 1:57 pm
Location: Munich, Germany
Occupation: electrical engineer

Post by Mike »

tlr wrote:You should be able to cut the jmp $c474 [...] [t]his was previously discussed here: [...]
Indeed, that one would save 3 bytes. Wow, this thread is from 2005!
Perhaps the print line could be made shorter and/or easier to type by op[t]imizations specifically targetting the number of escape codes and/or the number of characters involving shifting?
Doubtful. The shifted characters come mainly from the high-bytes of the JSR instructions pointing into BASIC ROM and the two ADC instructions. I also already used {RVS ON} and {RVS OFF} only where necessary. The single double-quote (which was necessary for the LDA $22 instruction) was especially annoying, as it had to be expressed as two double-quotes followed by delete to remain in non-quoted mode after.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Post by tlr »

Mike wrote:Doubtful. The shifted characters come mainly from the high-bytes of the JSR instructions pointing into BASIC ROM and the two ADC instructions. I also already used {RVS ON} and {RVS OFF} only where necessary. The single double-quote (which was necessary for the LDA $22 instruction) was especially annoying, as it had to be expressed as two double-quotes followed by delete to remain in non-quoted mode after.
Let's examine the relinker:

Code: Select all

.C:c533  A5 2B       LDA $2B
.C:c535  A4 2C       LDY $2C
.C:c537  85 22       STA $22
.C:c539  84 23       STY $23
.C:c53b  18          CLC
.C:c53c  A0 01       LDY #$01
.C:c53e  B1 22       LDA ($22),Y
.C:c540  F0 1D       BEQ $C55F      ; only way out
.C:c542  A0 04       LDY #$04
.C:c544  C8          INY
.C:c545  B1 22       LDA ($22),Y
.C:c547  D0 FB       BNE $C544
.C:c549  C8          INY
.C:c54a  98          TYA
.C:c54b  65 22       ADC $22
.C:c54d  AA          TAX
.C:c54e  A0 00       LDY #$00
.C:c550  91 22       STA ($22),Y
.C:c552  A5 23       LDA $23
.C:c554  69 00       ADC #$00
.C:c556  C8          INY
.C:c557  91 22       STA ($22),Y
.C:c559  86 22       STX $22
.C:c55b  85 23       STA $23
.C:c55d  90 DD       BCC $C53C   ; always taken
.C:c55f  60          RTS
This routine is pretty compact. I would say that it returns X=contents of $22, Acc=$00, Y=$01, Carry=0
There is one exception. X will be left untouched in the case that there is no link at the first line but this will new happen as we just fabricated a link there before the call.

Assuming the above, then this should work:

Code: Select all

LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer

JSR $C533   ; re-chain lines (which mainly restores the first
            ; link-pointer)
; X=($22), Acc=$00, Y=$01, C=0

; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:

TXA
ADC #$02
STA $2D
LDA $23
ADC #$00
STA $2E

; The stack pointer will be reset to $fa but the return address preserved
; by the call below.
JMP $C659   ; CLR. Also initialises other pointers from $2E/$2D.
tlr
Vic 20 Nerd
Posts: 567
Joined: Mon Oct 04, 2004 10:53 am

Post by tlr »

Continuing to examine the start of NEW and CLR:

Code: Select all

.C:c644  A9 00       LDA #$00     ; NEW
.C:c646  A8          TAY
.C:c647  91 2B       STA ($2B),Y
.C:c649  C8          INY
.C:c64a  91 2B       STA ($2B),Y
.C:c64c  A5 2B       LDA $2B
.C:c64e  18          CLC
.C:c64f  69 02       ADC #$02
.C:c651  85 2D       STA $2D
.C:c653  A5 2C       LDA $2C
.C:c655  69 00       ADC #$00             ; <--- our entry point
.C:c657  85 2E       STA $2E
.C:c659  20 8E C6    JSR $C68E           ; CLR
.C:c65c  A9 00       LDA #$00
.C:c65e  D0 2D       BNE $C68D
.C:c660  20 E7 FF    JSR $FFE7
...
We can partially reuse the end address calculation used by NEW to save a few more bytes.

Assuming the above, then this should work:

Code: Select all

LDA #$01
TAY
STA ($2B),Y ; write a non-0 value to the first link-pointer

JSR $C533   ; re-chain lines (which mainly restores the first
            ; link-pointer)
; X=($22), Acc=$00, Y=$01, C=0

; Now $23/$22 points to the last link-pointer of the program,
; which contains two zeroes. The variables are supposed to
; start 2 bytes later:

TXA
ADC #$02
STA $2D
LDA $23

; The stack pointer will be reset to $fa but the return address preserved
; by the call below. Also initialises other pointers from $2E/$2D.
JMP $C655   ; CLR with adc #0; sta $2e prepended
Post Reply