Merge pull request #25 from ellerh/implement-save-input
[debian/pforth] / fth / utils / clone.fth
1 \ @(#) clone.fth 97/12/10 1.1
2 \ Clone for PForth
3 \
4 \ Create the smallest dictionary required to run an application.
5 \
6 \ Clone decompiles the Forth dictionary starting with the top
7 \ word in the program.  It then moves all referenced secondaries
8 \ into a new dictionary.
9 \
10 \ This work was inspired by the CLONE feature that Mike Haas wrote
11 \ for JForth.  Mike's CLONE disassembled 68000 machine code then
12 \ reassembled it which is much more difficult.
13 \
14 \ Copyright Phil Burk & 3DO 1994
15 \
16 \ O- trap custom 'C' calls
17 \ O- investigate ALITERAL, XLITERAL, use XLITERAL in [']
18
19 anew task-clone.fth
20 decimal
21
22 \ move to 'C'
23 : PRIMITIVE? ( xt -- flag , true if primitive )
24     ['] FIRST_COLON <
25 ;
26
27 : 'SELF ( -- xt , return xt of word being compiled )
28     ?comp
29     latest name>
30     [compile] literal
31 ; immediate
32
33
34 :struct CL.REFERENCE
35     long  clr_OriginalXT    \ original XT of word
36     long  clr_NewXT         \ corresponding XT in cloned dictionary
37     long  clr_TotalSize     \ size including data in body
38 ;struct
39
40 variable CL-INITIAL-REFS \ initial number of refs to allocate
41 100 cl-initial-refs !
42 variable CL-REF-LEVEL    \ level of threading while scanning
43 variable CL-NUM-REFS     \ number of secondaries referenced
44 variable CL-MAX-REFS     \ max number of secondaries allocated
45 variable CL-LEVEL-MAX    \ max level reached while scanning
46 variable CL-LEVEL-ABORT  \ max level before aborting
47 10 cl-level-abort !
48 variable CL-REFERENCES   \ pointer to cl.reference array
49 variable CL-TRACE        \ print debug stuff if true
50
51 \ Cloned dictionary builds in allocated memory but XTs are relative
52 \ to normal code-base, if CL-TEST-MODE true.
53 variable CL-TEST-MODE
54
55 variable CL-INITIAL-DICT \ initial size of dict to allocate
56 20 1024 * cl-initial-dict !
57 variable CL-DICT-SIZE    \ size of allocated cloned dictionary
58 variable CL-DICT-BASE    \ pointer to virtual base of cloned dictionary
59 variable CL-DICT-ALLOC   \ pointer to allocated dictionary memory
60 variable CL-DICT-PTR     \ rel pointer index into cloned dictionary
61 0 cl-dict-base !
62
63
64 : CL.INDENT ( -- )
65     cl-ref-level @ 2* 2* spaces
66 ;
67 : CL.DUMP.NAME ( xt -- )
68     cl.indent
69     >name id. cr
70 ;
71
72 : CL.DICT[] ( relptr -- addr )
73     cl-dict-base @ +
74 ;
75
76 : CL,  ( cell -- , comma into clone dictionary )
77     cl-dict-ptr @ cl.dict[] !
78     cell cl-dict-ptr +!
79 ;
80
81
82 : CL.FREE.DICT ( -- , free dictionary we built into )
83     cl-dict-alloc @ ?dup
84     IF
85         free dup ?error
86         0 cl-dict-alloc !
87     THEN
88 ;
89
90 : CL.FREE.REFS ( -- , free dictionary we built into )
91     cl-references @ ?dup
92     IF
93         free dup ?error
94         0 cl-references !
95     THEN
96 ;
97
98 : CL.ALLOC.REFS ( --  , allocate references to track )
99     cl-initial-refs @  \ initial number of references
100     dup cl-max-refs ! \ maximum allowed
101     sizeof() cl.reference *
102     allocate dup ?error
103     cl-references !
104 ;
105
106 : CL.RESIZE.REFS ( -- , allocate references to track )
107     cl-max-refs @   \ current number of references allocated
108     5 * 4 / dup cl-max-refs ! \ new maximum allowed
109 \ cl.indent ." Resize # references to " dup . cr
110     sizeof() cl.reference *
111     cl-references @ swap resize dup ?error
112     cl-references !
113 ;
114
115
116 : CL.ALLOC.DICT ( -- , allocate dictionary to build into )
117     cl-initial-dict @  \ initial dictionary size
118     dup cl-dict-size !
119     allocate dup ?error
120     cl-dict-alloc !
121 \
122 \ kludge dictionary if testing
123     cl-test-mode @
124     IF
125         cl-dict-alloc @ code-base @ - cl-dict-ptr +!
126         code-base @ cl-dict-base !
127     ELSE
128         cl-dict-alloc @  cl-dict-base !
129     THEN
130     ." CL.ALLOC.DICT" cr
131     ."   cl-dict-alloc = $" cl-dict-alloc @ .hex cr
132     ."   cl-dict-base  = $" cl-dict-base @ .hex cr
133     ."   cl-dict-ptr   = $" cl-dict-ptr @ .hex cr
134 ;
135
136 : CODEADDR>DATASIZE { code-addr -- datasize }
137 \ Determine size of any literal data following execution token.
138 \ Examples are text following (."), or branch offsets.
139     code-addr @
140     CASE
141     ['] (literal) OF cell ENDOF   \ a number
142     ['] 0branch   OF cell ENDOF   \ branch offset
143     ['] branch    OF cell ENDOF
144     ['] (do)      OF    0 ENDOF
145     ['] (?do)     OF cell ENDOF
146     ['] (loop)    OF cell ENDOF
147     ['] (+loop)   OF cell ENDOF
148     ['] (.")      OF code-addr cell+ c@ 1+ ENDOF  \ text
149     ['] (s")      OF code-addr cell+ c@ 1+ ENDOF
150     ['] (c")      OF code-addr cell+ c@ 1+ ENDOF
151     0 swap
152     ENDCASE
153 ;
154
155 : XT>SIZE  ( xt -- wordsize , including code and data )
156     dup >code
157     swap >name
158     dup latest =
159     IF
160         drop here
161     ELSE
162         dup c@ 1+ + aligned 8 + \ get next name
163         name> >code \ where is next word
164     THEN
165     swap -
166 ;
167
168 \ ------------------------------------------------------------------
169 : CL.TRAVERSE.SECONDARY { code-addr ca-process | xt dsize --  }
170 \ scan secondary and pass each code-address to ca-process
171 \ CA-PROCESS ( code-addr -- , required stack action for vector )
172     1 cl-ref-level +!
173     cl-ref-level @ cl-level-abort @ > abort" Clone exceeded CL-ABORT-LEVEL"
174     BEGIN
175         code-addr @ -> xt
176 \ cl.indent ." CL.TRAVERSE.SECONDARY - code-addr = $" code-addr .hex ." , xt = $" xt .hex cr
177         code-addr codeaddr>datasize -> dsize      \ any data after this?
178         code-addr ca-process execute              \ process it
179         code-addr cell+ dsize + aligned -> code-addr  \ skip past data
180 \ !!! Bummer! EXIT called in middle of secondary will cause early stop.
181         xt  ['] EXIT  =                           \ stop when we get to EXIT
182     UNTIL
183     -1 cl-ref-level +!
184 ;
185
186 \ ------------------------------------------------------------------
187
188 : CL.DUMP.XT ( xt -- )
189     cl-trace @
190     IF
191         dup primitive?
192         IF   ." PRI:  "
193         ELSE ." SEC:  "
194         THEN
195         cl.dump.name
196     ELSE
197         drop
198     THEN
199 ;
200
201 \ ------------------------------------------------------------------
202 : CL.REF[] ( index -- clref )
203     sizeof() cl.reference *
204     cl-references @ +
205 ;
206
207 : CL.DUMP.REFS ( -- , print references )
208     cl-num-refs @ 0
209     DO
210         i 3 .r ."  : "
211         i cl.ref[]
212         dup s@ clr_OriginalXT >name id. ."  => "
213         dup s@ clr_NewXT .
214         ." , size = "
215         dup s@ clr_TotalSize . cr
216         drop \ clref
217     loop
218 ;
219
220 : CL.XT>REF_INDEX { xt | indx flag -- index flag , true if found }
221     BEGIN
222 \ cl.indent ." CL.XT>REF_INDEX - indx = " indx . cr
223         indx cl-num-refs @ >=
224         IF
225             true
226         ELSE
227             indx cl.ref[] s@ clr_OriginalXT
228 \ cl.indent ." CL.XT>REF_INDEX - clr_OriginalXT = " dup . cr
229             xt  =
230             IF
231                 true
232                 dup -> flag
233             ELSE
234                 false
235                 indx 1+ -> indx
236             THEN
237         THEN
238     UNTIL
239     indx flag
240 \ cl.indent ." CL.XT>REF_INDEX - " xt >name id. space  indx . flag . cr
241 ;
242
243 : CL.ADD.REF  { xt | clref -- , add referenced secondary to list }
244     cl-references @ 0= abort" CL.ADD.REF - References not allocated!"
245 \
246 \ do we need to allocate more room?
247     cl-num-refs @ cl-max-refs @ >=
248     IF
249         cl.resize.refs
250     THEN
251 \
252     cl-num-refs @ cl.ref[] -> clref    \ index into array
253     xt clref s! clr_OriginalXT
254     0 clref s! clr_NewXT
255     xt xt>size clref s! clr_TotalSize
256 \
257     1 cl-num-refs +!
258 ;
259
260 \ ------------------------------------------------------------------
261
262 \ called by cl.traverse.secondary to compile each piece of secondary
263 : CL.RECOMPILE.SECONDARY { code-addr | xt clref dsize -- ,  }
264 \ recompile to new location
265 \ cl.indent ." CL.RECOMPILE.SECONDARY - enter - " .s cr
266     code-addr @ -> xt
267 \ cl.indent ." CL.RECOMPILE.SECONDARY - xt = $" dup .hex dup >name id. cr
268     xt cl.dump.xt
269     xt primitive?
270     IF
271         xt cl,
272     ELSE
273         xt CL.XT>REF_INDEX
274         IF
275             cl.ref[] -> clref
276             clref s@ clr_NewXT
277             dup 0= abort" CL.RECOMPILE.SECONDARY - unresolved NewXT"
278             cl,
279         ELSE
280             cl.indent ." CL.RECOMPILE.SECONDARY - xt not in ref table!" cr
281             abort
282         THEN
283     THEN
284 \
285 \ transfer any literal data
286     code-addr codeaddr>datasize -> dsize
287     dsize 0>
288     IF
289 \ cl.indent ." CL.RECOMPILE.SECONDARY - copy inline data of size" dsize . cr
290         code-addr cell+  cl-dict-ptr @ cl.dict[]  dsize  move
291         cl-dict-ptr @ dsize + aligned cl-dict-ptr !
292     THEN
293 \ cl.indent ." CL.RECOMPILE.SECONDARY - leave - " .s cr
294 ;
295
296 : CL.RECOMPILE.REF { indx | clref codesize datasize -- }
297 \ all references have been resolved so recompile new secondary
298     depth >r
299     indx cl.ref[] -> clref
300     cl-trace @
301     IF
302         cl.indent
303         clref s@ clr_OriginalXT >name id. ."  recompiled at $"
304         cl-dict-ptr @ .hex cr    \ new address
305     THEN
306     cl-dict-ptr @  clref s! clr_NewXT
307 \
308 \ traverse this secondary and compile into new dictionary
309     clref s@ clr_OriginalXT
310     >code ['] cl.recompile.secondary cl.traverse.secondary
311 \
312 \ determine whether there is any data following definition
313     cl-dict-ptr @
314     clref s@ clr_NewXT - -> codesize \ size of cloned code
315     clref s@ clr_TotalSize \ total bytes
316     codesize - -> datasize
317     cl-trace @
318     IF
319         cl.indent
320         ." Move data: data size = " datasize . ." codesize = " codesize . cr
321     THEN
322 \
323 \ copy any data that followed definition
324     datasize 0>
325     IF
326         clref s@ clr_OriginalXT >code codesize +
327         clref s@ clr_NewXT cl-dict-base @ + codesize +
328         datasize move
329         datasize cl-dict-ptr +!  \ allot space in clone dictionary
330     THEN
331
332     depth r> - abort" Stack depth change in CL.RECOMPILE.REF"
333 ;
334
335 \ ------------------------------------------------------------------
336 : CL.SCAN.SECONDARY ( code-addr -- , scan word and add referenced secondaries to list )
337     depth 1- >r
338 \ cl.indent ." CL.SCAN.SECONDARY - enter - " .s cr
339     cl-ref-level @ cl-level-max @  MAX cl-level-max !
340     @ ( get xt )
341 \ cl.indent ." CL.SCAN.SECONDARY - xt = " dup . dup >name id. cr
342     dup cl.dump.xt
343     dup primitive?
344     IF
345         drop
346 \ cl.indent ." CL.SCAN.SECONDARY - found primitive." cr
347     ELSE
348         dup CL.XT>REF_INDEX
349         IF
350             drop \ indx   \ already referenced once so ignore
351             drop \ xt
352         ELSE
353             >r \ indx
354             dup cl.add.ref
355             >code 'self cl.traverse.secondary   \ use 'self for recursion!
356             r> cl.recompile.ref    \ now that all refs resolved, recompile
357         THEN
358     THEN
359 \ cl.indent ." CL.SCAN.SECONDARY - leave - " .s cr
360     depth r> - abort" Stack depth change in CL.SCAN.SECONDARY"
361 ;
362
363 : CL.CLONE.XT ( xt -- , scan top word and add referenced secondaries to list )
364     dup primitive? abort" Cannot CLONE a PRIMITIVE word!"
365     0 cl-ref-level !
366     0 cl-level-max !
367     0 cl-num-refs !
368     dup cl.add.ref     \ word being cloned is top of ref list
369     >code ['] cl.scan.secondary cl.traverse.secondary
370     0 cl.recompile.ref
371 ;
372
373 \ ------------------------------------------------------------------
374 : CL.XT>NEW_XT ( xt -- xt' , convert normal xt to xt in cloned dict )
375     cl.xt>ref_index 0= abort" not in cloned dictionary!"
376     cl.ref[] s@ clr_NewXT
377 ;
378 : CL.XT>NEW_ADDR ( xt -- addr , addr in cloned dict )
379     cl.xt>New_XT
380     cl-dict-base @ +
381 ;
382
383 : CL.REPORT ( -- )
384     ." Clone scan went " cl-level-max @ . ." levels deep." cr
385     ." Clone scanned " cl-num-refs @ . ." secondaries." cr
386     ." New dictionary size =  " cl-dict-ptr @ cl-dict-base @ - . cr
387 ;
388
389
390 \ ------------------------------------------------------------------
391 : CL.TERM ( -- , cleanup )
392     cl.free.refs
393     cl.free.dict
394 ;
395
396 : CL.INIT ( -- )
397     cl.term
398     0 cl-dict-size !
399     ['] first_colon cl-dict-ptr !
400     cl.alloc.dict
401     cl.alloc.refs
402 ;
403
404 : 'CLONE ( xt -- , clone dictionary from this word )
405     cl.init
406     cl.clone.xt
407     cl.report
408     cl.dump.refs
409     cl-test-mode @
410     IF ." WARNING - CL-TEST-MODE on so we can't save cloned image." cr
411     THEN
412 ;
413
414 : SAVE-CLONE  ( <filename> -- )
415     bl word
416     ." Save cloned image in " dup count type
417     drop ." SAVE-CLONE unimplemented!" \ %Q
418 ;
419
420 : CLONE ( <name> -- )
421     ' 'clone
422 ;
423
424 if.forgotten cl.term
425
426 \ ---------------------------------- TESTS --------------------
427
428
429 : TEST.CLONE ( -- )
430     cl-test-mode @ not abort" CL-TEST-MODE not on!"
431     0 cl.ref[] s@ clr_NewXT  execute
432 ;
433
434
435 : TEST.CLONE.REAL ( -- )
436     cl-test-mode @ abort" CL-TEST-MODE on!"
437     code-base @
438     0 cl.ref[] s@ clr_NewXT  \ get cloned execution token
439     cl-dict-base @ code-base !
440 \ WARNING - code-base munged, only execute primitives or cloned code
441     execute
442     code-base !   \ restore code base for normal
443 ;
444
445
446 : TCL1
447     34 dup +
448 ;
449
450 : TCL2
451     ." Hello " tcl1  . cr
452 ;
453
454 : TCL3
455     4 0
456     DO
457         tcl2
458         i . cr
459         i 100 + . cr
460     LOOP
461 ;
462
463 create VAR1 567 ,
464 : TCL4
465     345 var1 !
466     ." VAR1 = " var1 @ . cr
467     var1 @ 345 -
468     IF
469         ." TCL4 failed!" cr
470     ELSE
471         ." TCL4 succeded! Yay!" cr
472     THEN
473 ;
474
475 \ do deferred words get cloned!
476 defer tcl.vector
477
478 : TCL.DOIT ." Hello Fred!" cr ;
479 ' tcl.doit is tcl.vector
480
481 : TCL.DEFER
482     12 . cr
483     tcl.vector
484     999 dup + . cr
485 ;
486
487 trace-stack on
488 cl-test-mode on
489