Self Modifying Code

Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

Fleet Forth has a word, WHERE , to show where an error occurred. If the value of BLK is zero, WHERE displays the string pointed to by TIB with the length stored in #TIB . If BLK is non zero, WHERE prints the block and line numbers and displays the text on that line. Since I/O is involved (if the block is not in memory) there is a possibility of WHERE causing another error which would lead to WHERE being called which would cause another error and so on.
The solution I went with looks like this:

Code: Select all

NH VARIABLE WHERE?  TRUE WHERE? !
: WHERE  ( -- )
   WHERE? @ IF
      WHERE? OFF
      // The main body of WHERE
            .
            .
            .
   THEN
   WHERE? ON ;
NH is a metacompiler directive that makes the following word headless.
If any error occurs while WHERE executes, WHERE does nothing but set the variable WHERE? to true.
Here's a solution involving self modifying code:

Code: Select all

HEX
: WHERE  ( -- )
   // SWITCH WHERE OFF
   ['] EXIT (IS) RECURSE
   // The main body of WHERE
         .
         .
         .
(IS) is the primitive compiled by IS and RECURSE compiles the CFA of the latest word so the phrase

Code: Select all

   ['] EXIT (IS) RECURSE
makes EXIT the first word in the body of WHERE , effectively switching it off. All error handling in Fleet Forth eventually goes through ABORT . A slight modification to ABORT switches WHERE back on. The original ABORT :

Code: Select all

: ABORT  ( -- )
   ERR SP! AP!
   QUIT ; -2 ALLOT
and the new version :

Code: Select all

: ABORT  ( -- )
   ERR SP! AP!  ['] LIT (IS) WHERE
   QUIT ; -2 ALLOT
WHERE becomes eight bytes smaller while ABORT becomes eight bytes bigger. No net loss or gain there. The headless variable WHERE? is no longer needed for a total savings of four bytes.
By the way, the word ERR is a deferred word to allow extending the error handling. It is normally set to the word NOOP , a no-op.
chitselb
Posts: 232
Joined: 21 Aug 2010
Location: Ontonagon MI
Contact:

Re: Self Modifying Code

Post by chitselb »

I recall QUAN structures in MMSForth, a syntactically cleaner albeit nonstandard replacement for VARIABLE. The way it worked in your code was:

Code: Select all

quan foo
42 is foo
foo .
at foo .
: compiledfoo   ( -- )
   37 is foo   foo .    at foo . ;
Under the hood, FOO has three different code field addresses, and the words IS and AT are immediate words to select either the 'assignment CFA' or 'address-of CFA'. If neither IS nor AT prefixes FOO, then the 'value-of CFA' is used.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

chitselb wrote:
I recall QUAN structures in MMSForth, a syntactically cleaner albeit nonstandard replacement for VARIABLE. The way it worked in your code was:

Code: Select all

quan foo
42 is foo
foo .
at foo .
: compiledfoo   ( -- )
   37 is foo   foo .    at foo . ;
Under the hood, FOO has three different code field addresses, and the words IS and AT are immediate words to select either the 'assignment CFA' or 'address-of CFA'. If neither IS nor AT prefixes FOO, then the 'value-of CFA' is used.
I was thinking that maybe instead of IS , use TO.
TO and AT would be used with QUAN's or any QUAN like word ( double QUAN's, floating point QUAN's , etc. ) and IS could still be used to set DEFERred words.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

I was thinking that QUANs could be a replacement for variable if the default action is to return a QUAN's data's address ( like a variable ). TO could select the set value CFA and AT could select the fetch value CFA.

Code: Select all

QUAN FOO
AT FOO U.  \ get the data AT FOO
137 TO FOO \ send the data TO FOO
FOO U.     \ where is the data ( just like VARIABLE )
Here is some prototype code I wrote last night.

Code: Select all

SCR# 1076 
// QUAN -- CODE FOR CODE FIELDS
HEX
SUBR DO.VAR
   CLC,
   6 # LDA,  W ADC,  PHA,
   TYA,   W 1+ ADC,
   PUSH JMP,  END-CODE
SUBR TO.Q
   CLC,  DEX,  DEX,
   4 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' ! @ JMP,  END-CODE
SUBR AT.Q
   ' BL @ JMP,  END-CODE

SCR# 1077 
// TO AT
HEX
// SET A QUAN
: TO ( N -- )  // PARSE TEXT STREAM
   2 ' DUP @ DO.VAR <>
   ABORT" NOT A QUAN"
   +  STATE @
   IF  , EXIT  THEN
   EXECUTE ; IMMEDIATE
// GET A QUAN'S VALUE
: AT  ( -- N )  // PARSE TEXT STREAM
   4 BRANCH [ ' TO >BODY 2+ , ] ;
   -2 ALLOT IMMEDIATE

SCR# 1078 
// QUAN
HEX
: CFA-ALIGN
   >IN @ BL WORD  // AVOID INDIRECT
   SWAP >IN !     // JUMP BUG IN
   COUNT + 1 AND  // NMOS 6510
   ALLOT ;        // PROCESSOR
: QUAN
   CFA-ALIGN CREATE -2 ALLOT
   DO.VAR ,  TO.Q ,  AT.Q ,
   0 , ;
;S
CREATE MAKES SURE THE FIRST CFA
DOES NOT STRADDLE A PAGE BOUNDARY.
CFA-ALIGN HANDLES THE OTHER TWO.

SCR# 1079 
// 2QUAN
HEX
SUBR TO.2Q
   CLC,  DEX,  DEX,
   4 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' 2! @ JMP,  END-CODE
SUBR AT.2Q
   CLC,  DEX,  DEX,
   2 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' 2@ @ JMP,  END-CODE
: 2QUAN
   CFA-ALIGN  CREATE -2 ALLOT
   DO.VAR , TO.2Q ,  AT.2Q ,
   0 , 0 , ;
With this behavior, QUAN could be renamed VARIABLE and 2QUAN could be renamed 2VARIABLE. As long as TO and AT were not used, these versions of VARIABLE and 2VARIABLE would behave just like the original versions.
I was thinking about removing the ABORT" in TO and have it ( and AT ) execute or compile the word's only CFA if the word is not a 'QUAN' .
[Edit: fixed incorrect stack comment. TO does not return an address, it parses the text stream for the next word.]
Last edited by JimBoyd on Mon Jan 18, 2021 10:35 pm, edited 1 time in total.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

There are some things I neglected to mention in my previous post.
The defining word SUBR creates a variable with no space alloted, but it switches on the assembler. A word created with SUBR returns its address.
The word CFA-ALIGN is only needed for Forth's running on NMOS 6502's or 6510's.
That funny looking word ;S ends loading of a block ( it's an alias for EXIT ).
And since this is for a Commodore 64, just read \ ( backslash ) for each // ( double forward slash ).
Here is the code for new style variables ( VARs ) cleaned up a little:

Code: Select all


SCR# 1076 
// VAR -- CODE FOR CODE FIELDS
HEX
: DO.VAR  // PRIMARY CODE FIELD
   ;CODE
   CLC,
   6 # LDA,  W ADC,  PHA,
   TYA,   W 1+ ADC,
   PUSH JMP,  END-CODE
SUBR TO.VAR
   CLC,  DEX,  DEX,
   4 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' ! @ JMP,  END-CODE
SUBR AT.VAR
   ' BL @ JMP,  END-CODE

SCR# 1077 
// TO AT
HEX
// SET A VAR
: TO ( N -- )
   ' DUP @ [ ' DO.VAR 4 + ] LITERAL
   <> ABORT" NO TO BEHAVIOR"
   2+  STATE @
   IF  , EXIT  THEN
   EXECUTE ; IMMEDIATE
// GET A VAR'S VALUE
: AT  ( -- N )
   ' DUP @ [ ' DO.VAR 4 + ] LITERAL
   <> ABORT" NO AT BEHAVIOR"
   2+ 2+  STATE @
   IF  , EXIT  THEN
   EXECUTE ; IMMEDIATE

SCR# 1078 
// VAR
HEX
: CFA-ALIGN
   >IN @ BL WORD  // AVOID INDIRECT
   SWAP >IN !     // JUMP BUG IN
   COUNT + 1 AND  // NMOS 6510
   ALLOT ;        // PROCESSOR
: VAR
   CFA-ALIGN CREATE 
   TO.VAR ,  AT.VAR ,
   0 ,  DO.VAR ;
;S
CREATE MAKES SURE THE FIRST CFA
DOES NOT STRADDLE A PAGE BOUNDARY.
CFA-ALIGN HANDLES THE OTHER TWO.

SCR# 1079 
// 2VAR
HEX
SUBR TO.2VAR
   CLC,  DEX,  DEX,
   4 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' 2! @ JMP,  END-CODE
SUBR AT.2VAR
   CLC,  DEX,  DEX,
   2 # LDA,  W ADC,  0 ,X STA,
   TYA,   W 1+ ADC,  1 ,X STA,
   ' 2@ @ JMP,  END-CODE
: 2VAR
   CFA-ALIGN  CREATE
   TO.2VAR ,  AT.2VAR ,
   0 , 0 ,  DO.VAR ;
Since ANS Forth uses TO to set the data of a VALUE, if one wanted to ad ANS Forth style VALUEs to Forth:

Code: Select all

: VALUE
   CONSTANT ;CODE
   ' BL @ JMP,
END-CODE
: TO   
   ' DUP VALUE-CFA <> // TEST IF NOT A VALUE
   IF   [COMPILE] TO EXIT  THEN
   // PERFORM VALUE RELATED BEHAVIOR
; IMMEDIATE
[Edit: I made some mistakes when changing the code. Sorry, I was away from my desktop. I've corrected the errors. Hopefully. I'm fighting with a weak data connection.]
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

JimBoyd wrote:
I was thinking that QUANs could be a replacement for variable if the default action is to return a QUAN's data's address ( like a variable ). TO could select the set value CFA and AT could select the fetch value CFA.

At one time I thought that this behavior ( the default action being that of a VARIABLE rather than that of a VALUE would be the only easy way to tell if a word being parsed by TO or AT was a multi code field word. I no longer feel this is necessary.
I just realized that a clever implementation would allow TO and AT to fetch the address of the first code field, to get to the parent word, and backup to find if there is a word unique to multi code field defining words ( even if that word is a no-op ). This word's address could be the flag used to tell if a word is a multi code field word.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »


I'm not sure if this would qualify as self modifying code. The following word modifies itself without modifying the running code.
Fleet Forth has a word CHAR that is used by the following:

Code: Select all

.(  (  ,"  "
.( ABC)  displays the string ABC
( ABC)  comment -- discards the string
," ABC"  compiles the string ABC
" ABC"   If interpreting, stores ABC as a counted string at PAD
         If compiling, compiles helper word (") and ABC as a counted string

CHAR takes the ASCII code for a character to use as a delimiter and parses the text stream until that delimiter is found. It returns the address and count of the string that was parsed. CHAR aborts with the following message if the delimiter was not found:
'X' MISSING
Where 'X' is the delimiter used. For example:

Code: Select all

.( THIS IS A TEST
^^
) MISSING

This is the most streamlined version I had:

Code: Select all

: CHAR  ( C -- ADR CNT )
   DUP>R HERE C!
   'STREAM 2DUP R>
   SCAN DUP 0=
   IF
      WHERE
      CR HERE C@ EMIT ."  MISSING"
      ABORT
   THEN
   ADJUST ;

This version is 13 bytes smaller

Code: Select all

: CHAR  ( C -- ADR CNT )
   DUP>R LIT [ HERE >A 0 , ] C!
   'STREAM 2DUP R>
   SCAN DUP 0=
   [ HERE 3 + A> ! ]
   ABORT"   MISSING"
   ADJUST ;

CHAR modifies the inline text used by (ABORT") , which is compiled by ABORT" , by storing the ASCII code for the delimiter at the address of the first character of the string.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Self Modifying Code

Post by JimBoyd »

JimBoyd wrote:
What about manipulating the return stack to control program flow?
I was just reading Dynamically Structured Codes by M. L. Gassanenko. Does this count as self modifying code?

Since the actual code does not get modified, I have concluded that this is not self modifying code.
Post Reply