1 This log starts with neat stuff: checking and allocating
2 memory!
3
4 Memory alloction in Forth couldn't be less complicated.
5 The application has some amount of memory upon startup.
6 As you use it for variables, constants, strings, and new
7 word definitions, the HERE pointer advances to the next
8 unused spot.
9
10 You check how much memory is left with UNUSED and
11 request more with the deliciously retro-sounding command
12 MORECORE.
13
14 In this Linux interpreter, memory checking and
15 allocation is handled with the brk system call.
16
17 The "break" address (end of memory allocated for us by
18 Linux) minus HERE gives us the amount of unused memory:
19
20 JONESFORTH VERSION 1
21 20643 CELLS REMAINING
22 OK
23 GET-BRK .
24 151916544
25 HERE .
26 134522628
27 UNUSED .
28 20643
29
30 That checks out. Let's add a bit more:
31
32 1024 MORECORE
33 UNUSED .
34 21667
35
36 Sweet!
37
38 I did not have very much fun with the file io words.
39 Though I did manage to create an empty file with:
40
41 S" foo.txt" R/W CREATE-FILE
42 CLOSE-FILE
43 BYE
44 $ ls
45 foo.txt
46
47 But I'm not sure what to do with a file descriptor for
48 writing. Words like TELL are hard-coded to use STDOUT.
49
50 Eh, no big deal. Learning how to read and write files is
51 not one of goals here. :-)
52
53 But you know what _is_ a big deal? The proof-of-concept
54 assembler implemented near the end of jonesforth.f:
55
56 ;CODE - ends a colon word like usual, but it
57 appends a machine code NEXT (just like
58 our NEXT macro in NASM) and then alters
59 the "codeword" link in the just-compiled
60 word to point to the "data" we compiled
61 into the word definition.
62
63 You use it like so:
64
65 : foo <machine code/assembly> ;CODE
66
67 Then Jones defines some assembly mnemonics for things
68 like the registers EAX, ECD, EDX, etc. and assembly
69 mnemonics for PUSH and POP.
70
71 Finally, a fun instruction for the Pentium (and later)
72 x86 CPUs, RDTSC that returns 64 bits worth of clock
73 cycles.
74
75 The assembled word that makes use of the mnemonics looks
76 like this:
77
78
79 : RDTSC ( -- lsb msb )
80 RDTSC ( writes the result in %edx:%eax )
81 EAX PUSH ( push lsb )
82 EDX PUSH ( push msb )
83 ;CODE
84
85 Let's try it!
86
87 RDTSC . .
88 7 193238564
89 RDTSC DROP RDTSC DROP SWAP - .
90 8815
91 RDTSC DROP RDTSC DROP SWAP - .
92 9068
93
94 I'm dropping the most significant bytes since I'm
95 measuring smaller amounts of time (which wouldn't be
96 correct if the least significant bytes rolled over!)
97
98 Apparently a couple instructions takes over 8,000 CPU
99 cycles? Very interesting!
100
101 Well, I could try adding some new x86 instructions, I
102 suppose. I thought about it. But I think the
103 proof-of-concept is plenty.
104
105 One thing's for sure, Forth with an assembler is surely
106 the most flexible programming system ever.
107
108 The final trick in the jonesforth.f file is INLINE:
109
110
111 INLINE can be used to inline an assembler
112 primitive into the current (assembler) word.
113
114 For example:
115
116 : 2DROP INLINE DROP INLINE DROP ;CODE
117
118 Looking at the implementation, it literally copies the
119 machine code from the word to be inlined until the next
120 macro (which is no longer needed since the code can just
121 keep running to the next word and so on.
122
123 What's wild is that this is exactly what I was
124 contemplating when I first learned how the "threaded"
125 code in Forth works. (Threaded code is great when memory
126 and disk space are at an absolute premium. But on our
127 modern machines, a lot of what Forth does to save space
128 seems downright silly.) I thought, "why couldn't I just
129 copy the contents of these words rather than their
130 addresses?" Especially since most of the words are so
131 tiny, often just a handful of machine instructions. It
132 seems silly to JMP to them!
133
134 Anyway, I want to test this out. Reading a hex dump is a
135 pain, but I figure with a ton of repetition, I'll be
136 able to see if the code is, indeed, inlined:
137
138 JONESFORTH VERSION 1
139 20643 CELLS REMAINING
140 OK
141 : 6DUP INLINE DUP INLINE DUP INLINE DUP INLINE DUP INLINE DUP INLINE DUP ;CODE
142 42 6DUP .S
143 42 42 42 42 42 42 42
144
145 It works, now I'll make a silly word to dump memory at
146 a word definition so I can compare them:
147
148 : foo WORD FIND 64 DUMP ;
149 foo DUP
150 804A250 40 A2 4 8 3 44 55 50 6 94 4 8 50 A2 4 8 @....DUP....P...
151 804A260 4 4F 56 45 52 90 90 90 D 94 4 8 5C A2 4 8 .OVER.......\...
152 804A270 3 52 4F 54 15 94 4 8 6C A2 4 8 4 2D 52 4F .ROT....l....-RO
153 804A280 54 90 90 90 1E 94 4 8 78 A2 4 8 5 32 44 52 T.......x....2DR
154 foo 6DUP
155 A011D74 D8 1C 1 A 4 36 44 55 50 0 0 0 84 1D 1 A .....6DUP.......
156 A011D84 8B 4 24 50 8B 4 24 50 8B 4 24 50 8B 4 24 50 ..$P..$P..$P..$P
157 A011D94 8B 4 24 50 8B 4 24 50 AD FF 20 0 74 1D 1 A ..$P..$P.. .t...
158 A011DA4 3 66 6F 6F 5A 90 4 8 C4 A0 4 8 48 A1 4 8 .fooZ.......H...
159
160 Yeah, clearly the $P bit is repeated six times. And it
161 looks like each DUP is 4 bytes of machine code.
162
163 8B 04 24 50
164
165 Oh yeah, I can check that out with GDB, huh?
166
167 (gdb) disassemble /r code_DUP
168 Dump of assembler code for function code_DUP:
169 0x08049406 <+0>: 8b 04 24 mov eax,DWORD PTR [esp]
170 0x08049409 <+3>: 50 push eax
171 0x0804940a <+4>: ad lods eax,DWORD PTR ds:[esi]
172 0x0804940b <+5>: ff 20 jmp DWORD PTR [eax]
173 End of assembler dump.
174
175 Yup, that checks out.
176
177 Well, gosh. This concludes jonesforth/jonesforth.f.
178
179 Next, I'll take a look at the test files in the
180 jonesforth/ dir.
181
182 Next night: okay, so jonesforth/Makefile has this test
183 target:
184
185 test_%.test: test_%.f jonesforth
186 @echo -n "$< ... "
187 @rm -f .$@
188 @cat <(echo ': TEST-MODE ;') jonesforth.f $< <(echo 'TEST') | \
189 ./jonesforth 2>&1 | \
190 sed 's/DSP=[0-9]*//g' > .$@
191 @diff -u .$@ $<.out
192 @rm -f .$@
193 @echo "ok"
194
195 So make isn't my favorite thing, but I understand that
196 it's going to run a selected <test>.f file and write the
197 output to <test>.test and diff it with <test>.out, which
198 contains the expected output.
199
200 The 'TEST-MODE' word definition simply causes JONESFORTH
201 to not display its welcome message. Hmmm...that's a
202 little tricky because my port runs that before anything
203 from STDIN. There are ways to make that work, but I'm
204 thinking I'll just make my test script ignore the
205 welcome instead.
206
207 Then the 'TEST' invocation runs whatever test word was
208 defined in the <test>.f file. Which it could have done
209 itself. But at least I can do that like the makefile
210 does!
211
212 Okay, let's see if we can do some basic script input
213 first:
214
215 cat <(echo 'CR ." BEEP BOOP. Test mode activated." CR ') | ./nasmjf
216
217 JONESFORTH VERSION 1
218 20643 CELLS REMAINING
219 OK
220 BEEP BOOP. Test mode activated.
221 $
222
223 LOL. Awesome.
224
225 I sometimes forget that this is now a "real" program and
226 it would't take much to make it a somewhat useful UNIX
227 citizen...
228
229 Anyway, now I just need to redirect each test file in
230 followed by the TEST invocation.
231
232 I'll use sed to skip the welcome message (and my silly
233 "test mode" message, which I'm totally keeping in
234 there).
235
236 Here's what the output looks like (condensed a bit to
237 make it a bit more compact to look at:
238
239 eeepc:~/nasmjf$ ./test.sh
240 2DROP: 2 1
241 t e s t i n g
242 0 1 0 1 1 0
243 1 0 1 0 0 1
244 1 1 1 0 1 0 1 1 0
245 1 1 1 1 0 1 0 0 1
246 1 0 1 0 1
247 0 1 0 1 0
248 0 1 0
249 1 0 1
250 0 0 1
251 1 0 0
252 0 1 1
253 1 1 0
254 TEST4+0 TEST3+8 CATCH+28 CATCH ( DSP=3218223136 ) TEST2+8 TEST+0
255 TEST4+0 TEST3+20 CATCH+28 CATCH ( DSP=3218223136 ) TEST2+8 TEST+0
256 TEST3 threw exception 26
257 TEST4+0 TEST3+8 TEST2+68 TEST+0
258 TEST4+0 TEST3+20 TEST2+68 TEST+0
259 UNCAUGHT THROW 26
260 123
261 -127
262 7FF77FF7
263 -1111111111101110111111111110111
264 7FF77FF7
265 test_read_file.f.out: ERRNO=2
266 0
267 42 42
268 0
269 1 2
270 1 2 1
271 2 1 3
272 1 3 2
273 2 1
274 4 3 4 3 2 1
275 2 1 4 3
276 0
277 TEST4+0 TEST3+0 TEST2+0 TEST+0
278 3
279 TEST4+0 TEST3+32 TEST2+0 TEST+0
280 TEST4+0 TEST3+0 TEST2+4 TEST+0
281 3
282 TEST4+0 TEST3+32 TEST2+4 TEST+0
283
284 As far as I know, that's all good except the failure to
285 open "test_read_file.f.out" which will be due to the
286 fact that I'm running the tests up a directory from
287 where they would normally be run.
288
289 I'll just go ahead and modify the test:
290
291 - S" test_read_file.f.out" R/O OPEN-FILE
292 + S" jonesforth/test_read_file.f.out" R/O OPEN-FILE
293
294 Now I compare that output with what's expected in
295 Jones's .out files by adding a call to diff to my loop
296 and we'll see if any fail. After a couple tweaks
297 (whitespace, adding the stripping of "DSP=nnnnn" from
298 the stack trace tests using sed, etc.), I was able to get
299 a nice clean run!
300
301 eeepc:~/nasmjf$ ./test.sh
302 Testing: jonesforth/test_assembler.f
303 Testing: jonesforth/test_comparison.f
304 Testing: jonesforth/test_exception.f
305 Testing: jonesforth/test_number.f
306 Testing: jonesforth/test_read_file.f
307 Testing: jonesforth/test_stack.f
308 Testing: jonesforth/test_stack_trace.f
309
310 Well, then I call this port complete! I'm going to clean
311 up the assembly source (which is a royal mess) and see
312 if I can't maybe improve on the comments, etc. And
313 probably the README as well.
314
315 I've got more fun Forth stuff planned next.
316
317 : bye BEGIN ." Goodbye! " AGAIN ;
318 bye
319 Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
320 oodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
321 odbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! G
322 dbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Go
323 bye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goo
324 ye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Good
325 e! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodb
326 ! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodby
327 Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye
328 Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
329 oodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
330 odbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! G
331 dbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Go
332 bye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goo
333 ye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Good
334 e! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodb
335 ! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodby
336 Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye
337 Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
338 oodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye!
339 odbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! G
340 dbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Go
341 bye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goo
342 ye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Good
343 e! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodb
344 ! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodbye! Goodby