Alright, continuing from the resolution I made at the end of Dev Log 12, here's the TODO plan for making strings work in exported ELF executables. (Spoiler: it's Moore-style strings, the dang guy was right again!!!) [ ] Strings go right into the compiled function (not a separate data area) [ ] Instruction to push string address on the stack [ ] Instruction to skip over the string itself I'm still not happy about this "jump" logic being needed because it feels like one of those assembly language "tricks". But I don't see any way out of it, either. First, I gotta see if I can jump over a string. ; WIP: ; jump over string instruction mov byte [edx], 0xEB ; i386 opcode for JMP rel8 mov byte [edx+1], 4 ; jump this many bytes mov byte [edx+2], 0xDE mov byte [edx+3], 0xAD mov byte [edx+4], 0xBE mov byte [edx+5], 0xEF add edx, 6 ; update "here" What I've got here is a "string" of data 0xDEADBEEF that I want to jump over. I put that code in my QUOTE word and kept re-running a little test script until I got the bugs worked out: stringtest.sh ------------------------------------------ #!/usr/bin/bash set -e ./build.sh echo ': f "foo" ;' \ '"f" find inspect' \ '"f" find dump-word' \ 'f say' | ./meow5 ------------------------------------------ The output once it stopped crashing: f: 11 bytes IMMEDIATE COMPILE eb 4 de ad be ef 68 98 c1 4 8 foo You can see the "eb 4" opcode and operand to jump over the next four bytes. And the next four bytes are the deadbeef data. The next part, starting with 68, is the existing QUOTE functionality: it's the opcode to push the address of the string onto the stack. Okay, that's a nice little test. Now I need to actually store the string here in the compiled function. A couple nights later... So I went up a couple wrong alleys, even on the jump-over-string issue. For one thing, i386 doesn't have an opcode to directly get the instruction pointer (if you can believe it). So a common trick is to use `call` to do a relative jump to the next address *and* push the next address on the stack. But then I got to thinking: well, if `call` is going to jump anyway, I can use that to get the current address of the string at runtime *AND* jump over it at the same time! And sure enough, that works perfectly. I made a stand-alone program to test the concept and also to demo it with a nice little complete "hello world": https://ratfactor.com/cards/asm-data-in-text Then I "ported" that concept to Meow5, embedding the opcode for the call and the distance of the relative "jump" (the call does the jumping) after the string has been copied into the compiled word's machine code and the length has been determined. I had plenty of trial and error and I had to restore my ELF writing word "make_elf". But it didn't take that long. Now I can finally make the canonical test program and the namesake...but there's a funny catch. I make a meow word: $ ./meow5 : meow "Meow.\n" print ; It runs in the interpreter: meow Meow. Then I make a meow3 word: : meow3 meow meow meow exit ; It contains "exit" so the executable I export will exit properly and not segfault! But I can't run it now or the interpreter will exit! Now I'll export that word: make_elf meow3 Wrote to "meow3". And let's see what happens when I run it in the interpreter: meow3 Meow. Meow. Meow. $ Yup, prints three meows and exits! Now, how about that executable? $ ls -l meow3 -rwxr-xr-x 1 dave users 227 Nov 9 19:23 meow3 Two hundred and twenty-seven bytes. Not bad. :-) (A "hello world!" is just 144 bytes!) Aaaaaaand now for the moment of truth: $ ./meow3 Meow. Meow. Meow. Yeah!!! Okay, I said there was a catch earlier. It's this: why didn't I call the word "meow5" and have it print 5 meows? The answer is funny: it's because the exported program is given the same name as the word that is exported...so exporting a "meow5" program in the same directory as "meow5"...well, you can see the problem. Obviously I can solve this by running it in another directory or something. One of the things I realized earlier was that I don't have some really basic words like "dup" (when I wanted to use them!). So I'll probably add some of those. And some words can't be compiled (like "say", which has an absolute reference to a newline character in the running interpreter, which will be a segfault in an exported executable. So I'm going to document and test stuff and play around with it. But this is it for the log. I consider this a success. The End. 2023-11-09 Then: Ha ha, nope! I couldn't leave it alone! Yeah, this works, but I think I'm really close to having an actual programming language if I just add some branching... See you in log14.txt LOL