6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sat Apr 27, 2024 4:33 pm

All times are UTC




Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Tue Feb 27, 2024 12:48 pm 
Offline

Joined: Tue Sep 26, 2023 11:09 am
Posts: 51
I have a bunch of symbolic constants which I'm using to compile words for my app, but don't need/have space for them at runtime since I need to load a large data segment.

I read in gforth docs (below) that there's no standard way to use #define style constants that are inlined and discarded after compilation. So i'm looking for ideas. I'm using taliforth if it matters.

possible approaches:

1. use some external preprocessor to inline and remove constants in the forth source before compiling
2. use a wordlist to hold all the symbols and then throw it away (can I cleanly recover all the space?)
3. write some store / fetch symbol words that mange compile #define's in a separate associative array that I can discard after

here's what I read:

https://www.complang.tuwien.ac.at/forth ... tants.html

Constants in Forth behave differently from their equivalents in other programming languages. In other languages, a constant (such as an EQU in assembler or a #define in C) only exists at compile-time ... Some Forth compilers attempt to optimise constants by in-lining them where they are used. You can force Gforth to in-line a constant like this ...
Code:
 : someword ... [ my-const ] ... ;


However, the definition of the constant still remains in the dictionary. Some Forth compilers provide a mechanism for controlling a second dictionary for holding transient words such that this second dictionary can be deleted later in order to recover memory space. However, there is no standard way of doing this.


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 27, 2024 7:51 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
pdragon wrote:
1. use some external preprocessor to inline and remove constants in the forth source before compiling
This is not an unreasonable thing to do. I believe Druzyek wrote a tool to replace all names (variables, constants, words) with short names (not quite what you are suggesting, and his system has multiple banks of RAM so his solution is even more complicated, but this was done to reduce dictionary size). http://forum.6502.org/viewtopic.php?f=9&t=5911&start=15#p73213 Note that this can be done in Forth itself, if you want, as long as you have storage to write the results to so that you can run the result afterwards.

pdragon wrote:
2. use a wordlist to hold all the symbols and then throw it away (can I cleanly recover all the space?)
The entries are compiled into the dictionary as they are encountered. If you were to use something like MARKER to eliminate the unneeded words, it would forget the final words you compiled using those constants as well. There isn't an easy way to keep newly compiled stuff while forgetting older compiled stuff (some Forths can do this, but Tali doesn't currently support it).

pdragon wrote:
3. write some store / fetch symbol words that mange compile #define's in a separate associative array that I can discard after
This might not be a bad way to go - it's what the C compilers do. I'd recommend using ram starting just before the history buffers and growing downwards towards smaller addresses, as the dictionary will be growing upwards towards larger addresses. There isn't an easy way to manage "holes" in the allocated dictionary space, so this avoids that issue entirely by only compiling things into the dictionary that you want there.

pdragon wrote:
Constants in Forth behave differently from their equivalents in other programming languages. In other languages, a constant (such as an EQU in assembler or a #define in C) only exists at compile-time ... Some Forth compilers attempt to optimise constants by in-lining them where they are used.
Tali does not currently inline constants, so they will be compiled as a JSR to where they are defined in the dictionary. It's true that constants are handled differently, but everything in Forth is centered around the dictionary so it should be no surprise that that is where they go.

When you "run" a constant by using its name, it actually runs a little bit of code that looks up its value and places it on the stack. Tali saves constants in memory by compiling a JSR to doconst (the name of that bit of code) and the value is placed in memory just after the JSR. The doconst routine looks at where it was called from, gets the constant value that is next in memory, and then adjusts its return address on the stack before doing an RTS so that it returns to just after the value, esentialy hopping over the value after using it.

pdragon wrote:
You can force Gforth to in-line a constant like this ...
Code:
: someword ... [ my-const ] ... ;

You are missing the important LITERAL that comes right after the [ my-const ]. LITERAL takes a value off the stack and compiles it into the word as a constant literal. The square brackes are just to get you out of compiling mode and back into inerpreting mode so that your constant will RUN and place its value on the stack immediately, rather than COMPILE which would arrange for that to be run later. This ends up using more memory, as you will now have two copies of the constant in memory - one with a name and one without, but it will run very slightly faster.

pdragon wrote:
However, the definition of the constant still remains in the dictionary. Some Forth compilers provide a mechanism for controlling a second dictionary for holding transient words such that this second dictionary can be deleted later in order to recover memory space. However, there is no standard way of doing this.
This is all true. Some Forths have the capability to forget individual words, but Tali is not one of them. You are almost entering the topic of metacompiling at this point, so that might be a topic for you to research (there are several interesting threads on metacompiling).

I think option 1 is likely your fastest path to get what you want.

Option 3 looks interesting, so if you decide to go that route, certainly let us know. If you need help fleshing out what a solution might look like, just ask.

As an option 4, if you have mass storage on your system, you can also use screens as a virtual memory to store data. It would be slower, but you could move all of your named constants there with some helper words to look them up.


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 27, 2024 8:07 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8428
Location: Southern California
Not TaliForth, but six years ago I wrote words to implement multiple temporary buffers in Forth, with no memory fragmentation.  I put my code and description at http://wilsonminesco.com/Forth/ALLOC.html .  One of several intended uses, one which I have not implemented yet, is to put temporary Forth words there, and when they are no longer needed, the buffer is deleted and the link field of the first subsequent non-buffer word's header is repaired to point back to the last word defined before the one(s) in the buffer.  I know it probably sounds confusing, but hopefully it will give you ideas.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Feb 27, 2024 9:53 pm 
Offline

Joined: Wed Aug 21, 2019 6:10 pm
Posts: 217
The simplest solution is to dedicate a special character to begin those constant names and a unique name for assigning those constants, and define a Forth vocabulary to process a Forth script and replace those constants with numeric values.

You probably should also have pragmas for the HEX and DECIMAL base words if you make regular use of them. The emitted script can be kept in sync by simply replacing ``#HEX'' and ``#DECIMAL'' in the script with ``HEX'' and ``DECIMAL''.

#hex 80 #define #width


... #decimal
...
... #width < ...

Script: "... hex ... decimal ... 128 < ..."


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 28, 2024 3:27 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
Tali supports prefix characters on numbers, which allows ignoring the base (unless you are using a weird base).
Code:
%100 \ A binary number (decimal value 4)
#100 \ A decimal number (decimal value 100)
$100 \ A hexadecimal number (decimal value 256)


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 28, 2024 5:38 pm 
Offline

Joined: Wed Aug 21, 2019 6:10 pm
Posts: 217
SamCoVT wrote:
Tali supports prefix characters on numbers, which allows ignoring the base (unless you are using a weird base).
Code:
%100 \ A binary number (decimal value 4)
#100 \ A decimal number (decimal value 100)
$100 \ A hexadecimal number (decimal value 256)


Excellent, that simplifies writing a "compile time constants" preprocessor for Tali.


Top
 Profile  
Reply with quote  
PostPosted: Wed Feb 28, 2024 8:43 pm 
Offline

Joined: Sun May 13, 2018 5:49 pm
Posts: 247
Tali also has a non-standard word CLEAVE that may be useful here. Searching the Tali manual for "tokenize" is the fastest way to find the example for it (as the word cleave is used multiple times in the manual). Here is an excerpt from the manual:

cleave ( addr u -- addr2 u2 addr1 u1 ) - Given a block of character memory with words separated by whitespace, split off the first sub-block and put it in TOS and NOS. Leave the rest lower down on the stack. This allows breaking off single words (or zero-terminated strings in memory, with a different delimiter) for further processing. Use with loops:
Code:
: tokenize ( addr u -- )
    begin
    cleave
    cr type \ <-- processing of single word
  dup 0= until
  2drop ;
For a string such as s" emergency induction port", this gives us:
Code:
emergency
induction
port

The above just prints the items (along with a carriage return), but you could imagine looking at the word and possibly replacing it. You could just print the results, which would need to be run back through the Forth interpreter later - this would let you use the dictionary, then reset Forth and run your processed Forth through to create the final program. If you want it to work in a single pass, you could EVALUATE the word if it's not replaced (this would require storing the name/value pairs somewhere else outside of the dictionary).

If you do the latter, you'll have to handle whether you are in compiling or interpreting mode when you do your substitutions; In both cases you can put the replacement value on the stack, but if you are in COMPILING mode, then you will also want to call LITERAL on it to compile it into the word being compiled. The STATE variable tells you if you are compiling or interpreting at any instant in time.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: