Dr Jefyll wrote:
Code:
0= # LDA, HERE 24 + STA,
I don't fully understand this part. Is it Bill Ragsdale's assembler you're using? 0= is both a Forth word and an assembler word. What is the value you intend to load in A? Is it part of a PFA or a CFA, perhaps?
Edit: alright,
0= # LDA, puts an
opcode in A, is that it? (Hence the run-time operation.) Perhaps this illustrates the point noted below about very careful commenting.
Yes, it puts an opcode in A. The assembler uses Bill Ragsdale's syntax, but the assembler is one I wrote.
Quote:
Self-modifying code is a powerful technique, and it can be done fairly cleanly. Whether it's appropriate or not will depend on the situation -- the advantages and drawbacks needs to evaluated in every prospective instance.
In most case you'll just modify operands -- not opcodes -- and thus the technique needn't be mind-bendingly complex.
I noticed that the only difference between
SCAN and
SKIP was one byte! the type of branch used at the bottom of the loop. By using SMC I was able to save 40 bytes.
As for dangerous, this is from the source for my Forth kernel. It's built with a metacompiler that I wrote which runs on the Commodore 64. When I first got the idea for this, I tested it by compiling on the host and using
SEE to make sure I got the address correct in both
SCAN and
SKIP.
Code:
SEE SCAN
SCAN
F44 D0 # LDA
F46 F6A STA
F49 3 # LDA
F4B 86A JSR SETUP
F4E F5F JMP
F51 89 INC N 4 +
F53 F57 BNE
F55 8A INC N 5 +
F57 87 LDA N 2+
F59 F5D BNE
F5B 88 DEC N 3 +
F5D 87 DEC N 2+
F5F 87 LDA N 2+
F61 88 ORA N 3 +
F63 F6C BEQ
F65 89 )Y LDA N 4 +
F67 85 EOR N
F69 .A ASL
F6A F51 ^^ BNE
F6C DEX
F6D DEX
F6E 89 LDA N 4 +
F70 0 ,X STA
F72 8A LDA N 5 +
F74 1 ,X STA
F76 87 LDA N 2+
F78 PHA
F79 88 LDA N 3 +
F7B 838 JMP PUSH
OK
SEE SKIP
SKIP
F87 F0 # LDA
F89 F6A STA ' SCAN >BODY 26 +
F8C F49 JMP ' SCAN >BODY 5 +
OK
What makes using the hand calculated offsets worse is that I added labels to my metacompiler before I tried this.
LABEL creates a constant in the
host dictionary ( so it doesn't disturb the target code ). The new constant has the address of
THERE, the target's
HERE in virtual memory ( the Commodore REU ).
I probably should have defined
SCAN like this:
Code:
// SCAN
HEX
CODE SCAN ( A1 L1 C -- A2 L2 )
0= # LDA,
LABEL PATCH.ME
BAD STA, // STA TO DUMMY ADDRESS 2989
3 # LDA, SETUP JSR,
AHEAD,
BEGIN,
N 4 + INC,
0= IF, N 5 + INC, THEN,
N 2+ LDA,
0= IF, N 3 + DEC, THEN,
N 2+ DEC,
CS-SWAP THEN, // AHEAD JUMPS TO HERE
N 2+ LDA, N 3 + ORA,
0= NOT WHILE,
N 4 + )Y LDA, N EOR, .A ASL,
HERE PATCH.ME 1+ !
0= UNTIL, // STORE DESIRED BRANCH HERE
THEN,
DEX, DEX,
N 4 + LDA, 0 ,X STA,
N 5 + LDA, 1 ,X STA,
N 2+ LDA, PHA,
N 3 + LDA,
PUSH JMP, END-CODE
And
SKIP should have been:
Code:
CODE SKIP ( A1 L1 C -- A2 L2 )
0= NOT # LDA,
' SCAN @ 3 + @ STA,
' SCAN @ 5 + JMP, END-CODE
SKIP still has hand calculated offsets, but they are small.
Note: I haven't tested this yet.
Quote:
I suspect SMC became stigmatized largely because
it requires very careful commenting in the source code, and that effort wasn't always forthcoming. That's one main drawback. The others are:
- SMC isn't reentrant
- SMC isn't ROM-able.
- SMC can entail caching issues (but those don't apply to 6500 family processors).
-- Jeff
Any code definition which uses the scratchpad area
N isn't reentrant.
If I was writing this for a ROM-able Forth, I'd have to rewrite a lot more than some SMC.
If I was writing for a different processor family, there is a great deal I'd have to rewrite. I wouldn't think of trying something like this on an intel processor.