/* proof of concept code... indirect-threaded forth */ .equ cellsize, 4 docol: .long .+cellsize addl $cellsize,%ebp /* make room on return stack */ movl %esi,-cellsize(%ebp) /* save any existing codestream address */ popl %esi /* use new one */ jmp next+cellsize next: .long .+cellsize lodsl /* of which we get the pointer to the first word */ jmp *(%eax) /* and go to it... */ unnest: .long .+cellsize /* ;s or semis, gets you out of the endless NEXT loop */ mov -cellsize(%ebp),%esi /* restore caller address */ subl $cellsize,%ebp /* adjust return stack pointer */ jmp next+cellsize /* now how does this work? say you have a function ave that averages 2 numbers; it calls function + to add them, and then 2 / to get the average. so, : AVE ' + , 10# 2 LIT, ' / , ; in memory it will be the 'call docol', then the code address of +, the code address of LIT, the number 2, the code address of /, and the code address of semis, or unnest. let's also assume, for the sake of simplicity, that this is the only thing your program does, so that right after coldstart this is what is called. now let's define the other words so we can follow the action: */ plus: .long .+cellsize /* + */ popl %eax /* get args off data stack */ popl %ebx addl %ebx,%eax pushl %eax /* answer on data stack */ jmp next+cellsize lit: .long .+cellsize lodsl /* get next number out of instruction stream */ pushl %eax /* place it on the data stack */ jmp next+cellsize divide: .long .+cellsize /* / */ popl %ebx /* get dividend */ popl %eax /* get divisor */ clrl %edx divl %ebx pushl %eax /* quotient on data stack */ jmp next+cellsize /* ok, so here we go, the data stack has on it a 4 and an 8, (presumably put there during coldstart from argv) and ave is called (or jumped to) from coldstart. let's assume it's been called, so that the return stack (the actual machine stack of the x86) has on it the pointer to the end of the coldstart routine, which would probably be a 'RET' to the operating system (yes, that works with windows xp, and eax will go into errorlevel). the next thing that happens is that DOCOL is called, and we are launched into the endless NEXT loop. hmm, wait a minute. these are all lowlevel words except for AVE itself. that means that this proof-of-concept won't actually prove this interpreter will work correctly; let's make another highlevel word to be incorporated into AVE: : NOP ; /* this will just compile into a call to docol followed by unnest... ...but that's good enough for our purposes. we'll insert the NOP into the definition for AVE right from the get-go: : AVE ' NOP , ' + , 10# 2 LIT, ' / , ; so now here we are that NEXT has just called the first highlevel word it found in AVE, which is of course NOP; since NOP in turn calls DOCOL as its first action, you'll see the interaction of the return stack (pointed to by %ebp) the data stream (pointed to by %esi) and the data stack (the actual machine stack, pointed to by %esp). use 'gdb proof_d' to observe what happens; .gdbinit should already be present in this directory. */ nop: .long .+cellsize call docol+cellsize .long unnest ave: .long .+cellsize call docol+cellsize .long nop,plus,lit,2,divide,unnest cold: .long .+cellsize movl $data,%ebp /* for speed comparisons, enable this following line (and below) */ movl $REPS,%ecx /* repeat count */ 10: pushl $4 pushl $8 call docol+4 .long ave,tocode popl %eax /* return the average in errorlevel */ /* for speed comparisons, enable this following line (and above) */ loop 10b .ifeq OS-1 movl %eax,%ebx movl $1,%eax /* Unix exit */ int $0x80 .else /* assume Cygwin */ ret .endif tocode: .long .+cellsize /* useful in direct-threaded forth, drops back to low level */ mov %esi,%ebx /* address of machine code inline with highlevel forth */ mov -cellsize(%ebp),%esi /* restore address of caller, like ;S */ subl $cellsize,%ebp /* adjust return stack */ jmp *%ebx /* jump directly into code routine */ .bss data: .skip 1024, 0 /* make sure at least this much return stack space */ .ifeq OS-2 imagebase = 0x400000 /* surely there's a better way? */ .section .idata /* w2k seems to require a valid import descriptor whether you want one or not... XP doesn't seem to care */ .long 0, 0, 0, 1f-imagebase, 2f-imagebase .long 0, 0, 0, 0, 0 /* null array ends table */ 1: .asciz "KERNEL32.DLL" 2: .long 3f-imagebase, 0 3: .word 0 .asciz "ExitProcess" .endif .global _mainCRTStartup, _start .equ _mainCRTStartup, cold+4 /* for windows */ .equ _start, cold+4 /* for linux */