[project @ 1996-01-08 20:28:12 by partain]
[ghc-hetmet.git] / ghc / docs / NOTES.c-optimisation
1 Optimisation of C-code (runtime and compiled)
2 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
4 - Placing of STG registers in machine registers
5 - Optimisation of interpreter loop (tail calls)
6
7 /* TODO: flags */
8 -OC flag to ghc causes optimisation
9
10
11 ANSI C
12 ~~~~~~
13 For functions with no args we declare them as 
14
15         foo( STG_NO_ARGS )
16
17 rather than foo(), because you can tell ANSI C a little more
18 by making STG_NO_ARGS expand to void.  Maybe something to do with
19 forward declarations?
20
21
22 Optimisation with GCC
23 ~~~~~~~~~~~~~~~~~~~~~
24
25 We are concentrating most optimisation with gcc which allows
26 suitable global register declarations.
27
28
29 REGISTERS:
30
31 See StgOpt.h for a description of register usage
32
33 Note that all modules that reference the STG registers must be
34 compiled the same way so they look at the registers and not the
35 global variables.
36
37
38 TAIL CALLS:
39
40 Seperate modules for tail call optimisations are required.
41 Requitre to partition runtime system code.
42
43 .hc:
44         Modules with tail call routines (most runtime and all compiled)
45         are labeled .hc (literate = .lhc).
46         These are compiled to assember with tail call optimisation on
47         and then post processed with sed (Yuk!)
48
49         All routines which return a continuation (STGJUMP) must be
50         compiled this way. 
51
52         (The only exeption to this is continuations which exit/abort which
53          may live in .c files)
54
55 .c:
56         These modules are not compiled with the tail call optimisation
57         and don't have sed processing. 
58         Sed processing would destroy the code!
59
60         All routines which are not continuations (called and return
61         conventionally) must be compiled this way.
62
63         This includes various parts of the runtime system.
64
65
66
67
68 See Also  ~sansom/work/gcstats/OBSERVATIONS
69
70
71
72
73 Info Recieved from Eliot Miranda:
74
75 Received: from dcs.glasgow.ac.uk (tutuila) by vanuata.dcs.glasgow.ac.uk; Thu, 4 Jul 91 09:40:34 BST
76 Message-Id: <15456.9107040841@dcs.glasgow.ac.uk>
77 X-Comment1: #############################################################
78 X-Comment2: #     uk.ac.glasgow.cs has changed to uk.ac.glasgow.dcs     #
79 X-Comment3: #     If this address does not work please ask your mail    #
80 X-Comment4: #     administrator to update your NRS & mailer tables.     #
81 X-Comment5: #############################################################
82 To: simonpj
83 Cc: sansom
84 Subject: Miranda's original msg
85 Date: Thu, 04 Jul 91 09:41:19 +0100
86 From: Simon L Peyton Jones <simonpj>
87
88
89
90
91
92 >From eliot@.cs.qmw.ac.uk Mon Apr  1 11:16:06 1991
93 From: eliot@.cs.qmw.ac.uk (Eliot Miranda)
94 Newsgroups: comp.compilers
95 Subject: Portable Fast Direct Threaded Code
96 Keywords: interpreter, design
97 Date: 28 Mar 91 12:20:06 GMT
98 Reply-To: Eliot Miranda <eliot@.cs.qmw.ac.uk>
99 Organization: Computer Science Dept, QMW, University of London, UK.
100
101 Various people have asked me for details on how I do threaded code
102 in my dynamic translation Smalltalk-80 VM. So here's the gory details
103 as well as the first published benchmarks for the system.
104
105         How to do "Machine-Independent" Fast Direct Threaded Code:
106
107
108 First off, use C (although other flexible machine-oriented imperative
109 languages would probably work too).
110
111 Global registers:
112         If you can use GCC >v1.33 you can use global register variables to
113 hold the threaded code machine's registers.  If you have various forms of
114 stupid C compiler then you can get global register variables by declaring
115 your globals as register variables in every function, and later editing the
116 assembler generated by the C compiler to remove global register saves &
117 restores (details in [Miranda]).
118
119
120 Threaded Code:
121         Threaded code instructions (TCIs) are written as C procedures. 
122 They are compiled to assembler by the C compiler.  Subsequently a simple
123 editor script is run on the assembler to remove the prologues and epilogues
124 from the threaded code procedures, and possibly to insert direct threaded
125 code jumps.
126
127 How to Identify Threaded Code Instructions:
128         The method I prefer is to segregate TCIs from other procedures &
129 functions in the machine by placing related groups of TCIs in separate
130 source files.   I give my threaded code source files the .tc extension and
131 they have a rule in the makefile that will run the editor script on the
132 assembler.  An alternative is to identify each threaded code procedure with
133 a special prefix which is spotted by the editor script.  This is probably
134 more error prone & doesn't fit well with leaf-routine optimization (see
135 below).
136
137 How to Write Threaded Code Instructions:
138 Each TCI is writen an a void function of no arguments.  It is wise to start
139 and end each TCI with two special macros to replace '{' and '}'.  So, using
140 GCC on the SPARC, given some declarations:
141
142
143         typedef void    (*TCODE)(); /* a TCODE is a pointer to a function */
144         typedef ????    ObjectPointer;
145
146         . . .
147
148         register TCODE  *tcip asm("%g7"); /*threaded code instruction pointer*/
149         register ObjectPointer  *stackPointer asm("%g5");
150
151 e.g. popStack would be written:
152
153         void    popStack()
154         TBEGIN
155                 stackPointer--;
156         TEND
157
158 With GCC TBEGIN is
159
160         #define TBEGIN {
161
162 With stupid C compilers it can be defined to be the list of global register
163 variables.  Further, if you want to build a debuger for your threaded code
164 machine you could compile the system with
165
166         #define TBEGIN { int frig = checkForBreakPoint();
167
168 and ignore lots of warnings about variable frig being unused :-).
169
170 TEND has to do a direct threaded code jump. In my system I want an indirect
171 post-increment jump on tcip; i.e. jump to *tcip++.  On the SPARC with tcip
172 in %g7 the jump is
173
174         ld [%g7],%o0            ! get *tcip
175         jmpl %o0,%g0            ! jump to it
176         add %g7,4,%g7           ! increment tcip in the jump's delay slot
177
178 On the 68k with tcip in a5 the jump is
179
180         movl a5@+,a0
181         jmp a0@
182
183 With GCC this is implemented by the JUMPNEXT macro. On the SPARC:
184         #define JUMPNEXT do{ \
185                 asm("ld [%g7],%o0; jmpl %o0,%g0; add %g7,4,%g7");\
186                 return;}while(0)
187
188 Note the return, it tells the compiler that control does not pass this point.
189 On the 68k:
190         /* SBD = Silent But Deadly = Stack Bug Dummy. gcc has a bug with
191            no-defer-pop. it always depers the pop of the last function call in
192            a routine. SBD is a dummy call to ensure no other previous call gets
193            its pop deferred.
194          */
195         extern  void    SBD P((void));
196
197         #define JUMPNEXT do{ \
198                 asm("movl a5@+,a0; jmp a0@");\
199                 SBD();return;}while(0)
200
201 SBD is then removed by the editor script.
202
203 So TEND is defined to be
204         #define TEND JUMPNEXT; }
205
206 On the SPARC popStack is expanded to
207         void    popStack()
208         {
209                 stackPointer--;
210                 do{asm("ld [%g7],%o0; jmpl %o0,%g0; add
211 %g7,4,%g7");return;}while(0);
212         }
213
214 Its compiled to:
215         _popStack:
216                 !#PROLOGUE# 0
217                 save %sp,-80,%sp
218                 !#PROLOGUE# 1
219                 add %g5,-4,%g5
220                 ld [%g7],%o0; jmpl %o0,%g0; add %g7,4,%g7
221                 ret
222                 restore
223 The editor script then reduces this to:`
224         _popStack:
225                 ! [gotcher]
226                 add %g5,-4,%g5
227                 ld [%g7],%o0; jmpl %o0,%g0; add %g7,4,%g7
228
229 On the 68k you end up with:
230         .globl _popStack
231         _popStack:
232                 subqw #4,a3
233                 movl a5@+,a0; jmp a0@
234
235 Global Register Variables and Stupid C Compilers:
236         Some C compilers are stupid enough to provide straight-forward global
237 registers.  A C compiler on a nat-semi based system I used just allocated
238 registers in the order they were declared.  The assembler syntax was very
239 simple to edit.  Global register variables could thus be obtained easily.
240
241         Some C compilers are stupid but think they're clever.  Sun's SUN3
242 compiler is a case in point. It also allocates registers in the order declared.
243 However, it tries to be clever by removing 'dead assignments' (assignments to
244 subsequently unused register variables).  These compilers are easy to fool.
245 Simply arrange that the register variables are always used before leaving a
246 function.  I do this by always writing RETURN or RETURNV(e) instead of
247 return and return e.  On systems with such stupid C compilers RETURN(e)
248 is defined thus:
249
250         #define RETURNV(e) do{DummyUseRegs(GR1,GR2,GR3); return e;}while(1)
251
252 The call on DummyUseRegs fools the compiler into thinking the registers
253 are live & hence saves assignments to them.  The editor scripts can then
254 remove calls on DumyUseRegs.
255
256         Of course on systems with marginally clever C compilers (SUN4
257 HP-UX etc)
258 you're stuffed.  However, in clever C compilers like GCC and Acorn's C compiler
259 you can declare global registers & everything is clean & wholesome :-).
260
261
262
263 Conditional TCODE Jumps:
264         Say we wanted a conditional tcode jump. This might be writen:
265         void    skipIfTrue()
266         TBEGIN
267                 if (*stackPointer-- == TrueObject) {
268                         tcip += 1;
269                         JUMPNEXT;
270                 }
271         TEND
272
273 How this All Works:
274 With the above scheme, each threaded code procedure runs in the same C
275 stack frame, and jumps directly to the next procedure, eliminating an
276 unnecessary <epilogue, return>, <call, prolog> pair.  Once we establish a
277 stack frame and call the first function away we go.  Assuming that you've
278 produced your first threaded code method (a sequence of pointers to
279 threaded code procedures), and that tcip points at the start, then
280 StartTCMachine might be defined as follows:
281
282         volatile        void
283         StartTCMachine()
284         {       char    *enoughSpaceForAllTCIStackFrames;
285
286                 enoughSpaceForAllTCIStackFrames = alloca(1024);
287
288                 while(1) { (*tcip++)(); }
289         }
290
291 The alloca allocates space on the stack. After the first (*tcip++)()
292 control goes off into threaded code land and never returns.
293
294 Leaf Routine Optimization:
295 The threaded code machine will make calls on support routines e.g.
296 graphics, garbage collector etc.  Any group of routines that dont access
297 the global registers and don't directly or indirectly call routines that
298 need to access the global registers can be optimized.  These routines
299 should be compiled without declaring the global registers.  These routines
300 will then use as many registers as the compiler will give them and will
301 save & restore any registers they use, preserving the values of the global
302 register variables.
303
304
305 Details of my Smalltalk Threaded Code Machine:
306         I use a pair of words for each TCI, a pointer to the procedure followed
307 by an optional operand.  This avoids going out of line to access arguments.
308 e.g. pushLiteral is:
309         void    pushLit()
310         TBEGIN
311                 *++stackPointer = (OOP)*tcip++;
312         TEND
313 where OOP is an ordinary object pointer. So on entry to push lit we have:
314                 <pointer to pushLit>
315         tcip->  <object pointer>
316                 <pointer to next TCI>
317                 <next TCI's operand>
318 and popStack must therefore be written
319         void    popStack()
320         TBEGIN
321                 stackPointer--;
322                 tcip++;
323         TEND
324
325 I dynamically compile Smalltalk-80 bytecodes to threaded code.  I use 128k
326 bytes of memory to hold all threaded code. This 'tspace' is periodically
327 scavenged to reclaim space.  The architecture is similar to
328 [DeutschSchiffman].  Using an eighth of the space used by the Deutch
329 Schifman machine I get around 75% of the performance on the non-graphics
330 benchmarks.  Here are the Smalltalk macro benchmarks for BrouHaHa
331 Smalltalk-80 v2.3.2t running on a monochrome SUN3/60 (20MHz 68020):
332
333         BitBLT                  76.7308
334         TextScanning            222.857
335         ClassOrganizer          80.6667
336         PrintDefinition         59.0278
337         PrintHierachy           142.857
338         AllCallsOn              112.5
339         AllImplementors         130.0
340         Inspect                 116.667
341         Compiler                86.4341
342         Decompiler              101.639
343         KeyboardLookAhead       212.5
344         KeyboardSingle          302.778
345         TextDisplay             148.837
346         TextFormatting          273.81
347         TextEditing             180.342
348         Performance Rating      134.198
349
350 and on the same machine under the same conditions are the timings for
351 ParcPlace Smalltalk-80 V2.3:
352
353         BitBLT                  99.75
354         TextScanning            390.0
355         ClassOrganizer          155.128
356         PrintDefinition         137.097
357         PrintHierachy           192.308
358         AllCallsOn              120.0
359         AllImplementors         108.333
360         Inspect                 146.774
361         Compiler                118.617
362         Decompiler              129.167
363         KeyboardLookAhead       303.571
364         KeyboardSingle          473.913
365         TextDisplay             172.973
366         TextFormatting          442.308
367         TextEditing             285.135
368         Performance Rating      189.504
369
370 134.198/189.504 = 0.708154
371
372 WARNING!! These systems ARE different, these benchmarks are included only
373 to give a feeling for ball-park performance.
374 Example differences:
375         BrouHaHa                                ParcPlace
376         closures                                blue-book BlockContexts
377         immediates:
378           characters, smallints, fixedpoints    immediate smallintegers
379         5844 compiled methods                   5136 compiled methods
380         (5026 ordinary methods)                 (4798 ordinary methods)
381         (818 quick methods)                     (338 quick methods)
382
383
384
385 More Portable File Organization:
386 To keep the code as clean looking as possible all machine-dependencies are
387 isolated in separate files.  e.g. tcode.h gives machine independent
388 definitions for TCODE. It includes machine dependencies from another file:
389
390         /* for debugging purposes; single step breakpoint at start of
391 each tcode
392          */
393         #define DEBUG_FETCH_BREAK int frig = fetchBrk();
394
395         #ifdef FAST
396         #   include "fasttcode.h"
397         #else
398
399         TCODE   *tcip;          /* the tcode ip points at TCODEs */
400
401         #   define JUMPNEXT return
402         #   ifdef BIDebug
403         #       define TBEGIN { DEBUG_FETCH_BREAK
404         #   else
405         #       define TBEGIN {
406         #   endif
407         #   define TEND }
408         #endif
409
410 GCC/SPARC/fasttcode.h:
411         /* tcodeip in g7 */
412         register TCODE  *tcip asm("%g7");
413
414         #define JUMPNEXT do{asm("ld [%g7],%o0; jmpl %o0,%g0; add
415 %g7,4,%g7");return;}while(0)
416
417         #ifdef BIDebug
418         #   define TBEGIN { DEBUG_FETCH_BREAK
419         #else
420         #   define TBEGIN {
421         #endif
422         #define TEND JUMPNEXT; }
423
424 I also don't want to include the necessary definitions for the global registers
425 in every file. So for those non-leaf routines that must avoid using the
426 global registers there's a fastglobal.h file that gives dummy definitions for
427 these registers. e.g. GCC/SPARC/fastglobal.h:
428         /* machine specific FAST defines.
429            Gnu C 1.33 systems can use nice compiler provided global registers.
430          */
431
432         #define BEGIN {
433         #define END }
434         #define RETURN(e) return e
435         #define RETURNV return
436
437         register char   *GlobRegDummy1 asm("a5");
438         register char   *GlobRegDummy2 asm("a4");
439         register char   *GlobRegDummy3 asm("a3");
440         register char   *GlobRegDummy4 asm("d6");
441
442         #ifdef MASKREGISTER
443         register char   *GlobRegDummy5 asm("d7");
444         #endif
445
446 I use symbolic links to set up the machine dependent include files.
447 This has the
448 advantage that if you add a new machine you don't have to remake all
449 the others.
450
451
452 The Tedious Bit:
453 The only tedious bit is writing the sed-scripts. For the SPARC this took 1 day.
454 Here are the sed scripts I use for SUN 3, MAC2AUX (using GAS) and SUN4,
455 all using GCC (v1.33 upwards). There's a problem on the SPARC in that the ABI
456 does not seem to define the status of the global registers.  Some math and
457 library routines stomp on the global registers (beware getwd!), so I've
458 included
459 GCC/SUN4/sed.globreg.bugfix as an example of how to spot the offending math
460 routines:
461
462 SUN3/GCC/lib/sed.tcode.opt:
463 # script to strip prolog & epilog from threaded code under gcc.
464 # WARNING the script may strip a push of a register argument if a call is the
465 # first statement of a function!!
466 #
467 /^_.*:$/{n
468 N
469 N
470 s/      link a6,#[^\n]*\n//
471 /       fmovem #[^\n]*,sp@-/{
472 N
473 s/      fmovem #[^\n]*,sp@-\n//
474 }
475 s/      moveml .*,sp@-\n//
476 s/      movel [ad][0-7],sp@-\n//
477 }
478 /       moveml a6@(-[1-9][0-9]*),#/{N
479 s/      moveml a6@(-[1-9][0-9]*),#[^\n]*\n      unlk a6//
480 }
481 /       movel a6@(-[1-9][0-9]*),[ad][0-7]/{N
482 s/      movel a6@(-[1-9][0-9]*),[ad][0-7]\n     unlk a6//
483 }
484 /       movel sp@+,/d
485 /       moveml sp@+,#/d
486 /       unlk a6/d
487 /       rts/d
488 /       jbsr _SBD/d
489
490 MAC2AUX/GCC/lib.gas/sed.tcode.opt:
491 /COMMENT/{
492 i\
493         script to strip prolog & epilog from threaded code under gcc. WARNING \
494         the script may strip a push of a register argument if a call is the\
495         first statement of a function!!
496 }
497 /^gcc_compiled:/d
498 /^.[^%].*:$/{ n
499 /       link %a6/{
500 N
501 N
502 s/      link %a6,#[x0-9-]*\n//
503 /       fmovem #[^\n]*,%sp@-/{
504 N
505 s/      fmovem #[^\n]*,%sp@-\n//
506 }
507 s/      moveml #[x0-9a-f]*,%sp@-\n//
508 s/      movel %[ad][0-7],%sp@-\n//
509 n
510 }
511 }
512 /       moveml -[1-9][0-9]*%a6@,#/{ N
513 s/      moveml -[1-9][0-9]*%a6@,#[x0-9a-f-]*\n  unlk %a6//
514 }
515 /       movel -[1-9][0-9]*%a6@,%[ad][0-7]/{ N
516 s/      movel -[1-9][0-9]*%a6@,%[ad][0-7]\n     unlk %a6//
517 }
518 /       movel %sp@+,%/d
519 /       moveml %sp@+,#/d
520 /       movel %d0,%a0/{
521 N
522 s/      movel %d0,%a0\n unlk %a6//
523 /       movem*l %a6/{
524 N
525 s/      movel %d0,%a0\n movem*l %a6.*\n unlk %a6//
526 /       fmovem %a6/{
527 N
528 s/      movel %d0,%a0\n movem*l %a6.*\n fmovem %a6.*\n  unlk %a6//
529 }
530 }
531 }
532 /       unlk %a6/d
533 /       rts/d
534 /       jbsr SBD/d
535
536
537 SUN4/GCC/lib/sed.tcode.opt:
538 # script to strip prolog & epilog from threaded code under gcc.
539 #
540 /^_.*:$/{n
541 N
542 N
543 s/      !#PROLOGUE# 0\n save %sp,[-0-9]*,%sp\n  !#PROLOGUE# 1/  ! [gotcher]/
544 }
545 /       ret/d
546 /       restore/d
547
548 SUN4/GCC/lib/sed.globreg.bugfix:
549 # Some of the libc builtin routines (.rem .urem .div & .udiv so far known)
550 # stamp on %g3 which is the maskReg (it contains 0x7FFFFF).
551 # This script reassigns the value of maskReg after each of these routines
552 # has been called.
553 /call[  ]\.div,[0-9]/{n
554 n
555 i\
556         sethi %hi(0x7FFFFF),%g3 ![globregfix]\
557         or %lo(0x7FFFFF),%g3,%g3
558 }
559 /call[  ]\.udiv,[0-9]/{n
560 n
561 i\
562         sethi %hi(0x7FFFFF),%g3 ![globregfix]\
563         or %lo(0x7FFFFF),%g3,%g3
564 }
565 /call[  ]\.rem,[0-9]/{n
566 n
567 i\
568         sethi %hi(0x7FFFFF),%g3 ![globregfix]\
569         or %lo(0x7FFFFF),%g3,%g3
570 }
571 /call[  ]\.urem,[0-9]/{n
572 n
573 i\
574         sethi %hi(0x7FFFFF),%g3 ![globregfix]\
575         or %lo(0x7FFFFF),%g3,%g3
576 }
577
578
579 You can now see why I put "Machine-Independent" in quotes.  Here's the count
580 of machine dependent code for the SPARC:
581
582          25      99     786 fastcache.h
583          68     262    1882 fastglobal.h
584          31     112     906 fasttcode.h
585          28      80     595 ../tcsrc/SUN4/GCC/lib/sed.globreg.bugfix
586           5      34     222 ../tcsrc/SUN4/GCC/lib/sed.peep.opt
587           9      30     173 ../tcsrc/SUN4/GCC/lib/sed.tcode.opt
588         166     617    4564 total
589
590 Of these 166 lines 51 lines are banner headers. 100 odd lines are
591 machine dependent.  A whole VM is around the 20k lines mark. So
592 machine dependencies are down in the 0.5% range.
593
594
595
596 Use this stuff as part of what ever you like.  If you try & assert ownership
597 I'll fight & batter you over the head with the GPL ('bout time we had some
598 serious steel in that thar GPL).
599
600 Share And Enjoy!
601
602 P.S. The BrouHaHa machine is available to educational institutions with a
603 valid ParcPlace Smalltalk-80 licence, subject to a strict non-disclosure
604 agreement.  email me if you want it. I am slow to answer requests!
605 -- 
606 Send compilers articles to compilers@iecc.cambridge.ma.us or
607 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
608
609 >From crowl@cs.rochester.edu Tue Apr  2 10:34:53 1991
610 From: crowl@cs.rochester.edu (Lawrence Crowl)
611 Newsgroups: comp.compilers
612 Subject: Re: Portable Fast Direct Threaded Code
613 Keywords: interpreter, design
614 Date: 31 Mar 91 18:06:35 GMT
615 Reply-To: crowl@cs.rochester.edu (Lawrence Crowl)
616 Organization: Computer Science Department University of Rochester
617
618 In article <3035@redstar.cs.qmw.ac.uk>
619 Eliot Miranda <eliot@.cs.qmw.ac.uk> writes:
620 >The threaded code machine will make calls on support routines, e.g. graphics,
621 >garbage collector etc.  Any group of routines that don't access the global
622 >registers and don't directly or indirectly call routines that need to access
623 >the global registers can be optimized.  These routines should be compiled
624 >without declaring the global registers.  These routines will then use as many
625 >registers as the compiler will give them and will save & restore any
626 >registers they use, preserving the values of the global register variables.
627
628 This scheme assumes that procedure calls use a "callee saves" register
629 convention, and does not work if you allocate the global register
630 variables out of the "caller saves" set of registers.  The problem is that
631 the caller does not save the register (because it is global) and the
632 callee writes over the register (because the caller saved it).  In this
633 situation, the programmer must insert explicit saves and restores of the
634 global register variables.
635
636 The obvious solution to this problem is to allocate all global register
637 variables out of the "callee saves" set of registers.  However, the
638 Alliant has _no_ callee saves registers.  Library routines on the Alliant
639 will trash every register you have.  In addition, implicit library calls
640 to routines like bcopy will also trash your registers.  (I learned this
641 the hard way.)
642
643 The lesson is that calling library routines with global register variables in
644 caller saves registers requires special handling.  It is not automagic.
645 -- 
646   Lawrence Crowl                716-275-9499   University of Rochester
647                       crowl@cs.rochester.edu   Computer Science Department
648                  ...!rutgers!rochester!crowl   Rochester, New York, 14627-0226
649 -- 
650 Send compilers articles to compilers@iecc.cambridge.ma.us or
651 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
652
653 >From Tom.Lane@G.GP.CS.CMU.EDU Wed Apr  3 10:38:09 1991
654 From: Tom.Lane@G.GP.CS.CMU.EDU
655 Newsgroups: comp.compilers
656 Subject: Re: Portable Fast Direct Threaded Code
657 Keywords: interpreter, design
658 Date: 1 Apr 91 15:21:14 GMT
659 Reply-To: Tom.Lane@G.GP.CS.CMU.EDU
660 Organization: Compilers Central
661
662 Lawrence Crowl points out one important problem with Eliot Miranda's
663 scheme for global register use in C.  There's an even more fundamental
664 problem, though: there is *no guarantee whatever* that the compiler will
665 assign the same registers to the global variables in every routine.
666
667 Compilers that do intelligent allocation of variables to registers may
668 refuse to honor the "register" declarations at all if the global variables
669 are not heavily used in a given routine, and in any case the globals need
670 not be assigned to the same registers every time.  Miranda's scheme thus
671 relies heavily on the assumption of a dumb register allocator (or else a
672 compiler that supports global register variable declarations).
673
674 This scheme may be "fast" direct threaded code, but it's hardly "portable".
675 -- 
676                         tom lane
677 Internet: tgl@cs.cmu.edu        BITNET: tgl%cs.cmu.edu@cmuccvma
678 [GCC lets you specify what register to use for global register variables, but
679 that is of course both machine and compiler specific. -John]
680 -- 
681 Send compilers articles to compilers@iecc.cambridge.ma.us or
682 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
683
684 >From pardo@cs.washington.edu Thu Apr  4 17:34:39 1991
685 From: pardo@cs.washington.edu (David Keppel)
686 Newsgroups: comp.compilers
687 Subject: Re: Portable Fast Direct Threaded Code
688 Keywords: interpreter, design, bibliography
689 Date: 2 Apr 91 19:21:25 GMT
690 Reply-To: pardo@cs.washington.edu (David Keppel)
691 Organization: Computer Science & Engineering, U. of Washington, Seattle
692
693 metzger@watson.ibm.com (Perry E. Metzger) writes:
694 >[I'd like a reference on threaded code interpreters.]
695
696 3 citations follow:
697
698 %A James R. Bell
699 %T Threaded Code
700 %J Communications of the ACM (CACM)
701 %V 16
702 %N 2
703 %D June 1973
704 %P 370-372
705 %X Describes the basic idea of threaded code.
706 Compares to hard code (subroutine calls) and interpreters.
707
708 %A Richard H. Eckhouse Jr.
709 %A L. Robert Morris
710 %T Minicomputer Systems  Organization, Programming, and Applications
711 (PDP-11).  2nd Ed.
712 %I Prentice-Hall, Inc.
713 %P 290-298
714 %X Describes threaded code and ``knotted code''.  I (pardo) think that
715 this is a very clear introduction to threaded code.
716
717 %A Peter M. Kogge
718 %T An Architectural Trail to Threaded Code Systems
719 %J IEEE Computer
720 %P 22-33
721 %D March 1982
722 %W rrh (original)
723 %W Pardo (copy)
724 %X Describes how to build a threaded code interpeter/compiler from
725 scratch.
726  * Direct threaded/indirect threaded.
727  * Less than 2:1 performance hit of threading compared to full
728 compilation.
729  * Note about bad compilers contributing to relative OK-ness of
730 threaded code.
731  * Ease of rewriting stuff.
732  * Ease of tuning.
733
734 My favorite of the three is Eckhouse & Morris; however I don't know
735 where to get it.  The pages that I have are photocopies graciously
736 sent to me by a friend.  As the title implies, this book is several
737 years old and undoubtedly out-of-print.
738
739         ;-D on  ( Following this thread of the discussion... )  Pardo
740 -- 
741 Send compilers articles to compilers@iecc.cambridge.ma.us or
742 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
743
744 >From simonpj Fri Apr  5 09:52:33 1991
745 Received: from tutuila.dcs.glasgow.ac.uk by vanuata.cs.glasgow.ac.uk;
746 Fri, 5 Apr 91 09:52:27 BST
747 Message-Id: <2763.9104050851@tutuila.dcs.glasgow.ac.uk>
748 X-Comment1: #############################################################
749 X-Comment2: #     uk.ac.glasgow.cs has changed to uk.ac.glasgow.dcs     #
750 X-Comment3: #     If this address does not work please ask your mail    #
751 X-Comment4: #     administrator to update your NRS & mailer tables.     #
752 X-Comment5: #############################################################
753 From: Simon L Peyton Jones <simonpj>
754 To: eliot@cs.qmw.ac.uk
755 Cc: simonpj, partain
756 Subject: Threaded code
757 Date: Fri, 05 Apr 91 09:51:48 +0100
758
759
760 Eliot 
761
762 I saw your article about threaded code.  Like you and others, we are
763 using C as an assembler, only for a pure functional language, Haskell.
764 I have some brief questions.
765
766 1. By telling GCC not to use a frame pointer, one can eliminate
767 the prolog entirely, can't one?  So why edit it out?
768
769 I guess the answer is going to be local variables, allocated once for
770 all by the StartTCMachine routine.  Still, it seems quite a pain.  I guess
771 one could sacrifice some (perhaps slight) speed by using a bunch of
772 globals instead.
773
774 2. You edit out the epilogue for tidiness only, I take it.  It doesn't
775 cause any problems if you leave it in, does it?
776
777 3. Why do you use no-defer-pop (with the associated bug)?
778
779 4. Why does JUMPNEXT have a loop?  Surely the jump leaves the loop right
780 away.  Presumably you are tricking the compiler somehow.
781
782 Thanks
783
784 Simon L Peyton Jones
785 Glasgow University
786
787
788
789
790 Simon
791 ============================= Address change =======================
792 My email address is now officially:  simonpj@dcs.glasgow.ac.uk
793 This may fail if your local site has out-of-date mail tables.
794 The old address (simonpj@cs.glasgow.ac.uk) will work for quite a long while, 
795 so stay with the old one if the new one fails.
796 ====================================================================
797
798 >From eliot@cs.qmw.ac.uk Fri Apr  5 12:18:18 1991
799 Via: uk.ac.qmw.cs; Fri, 5 Apr 91 12:18:06 BST
800 Received: from aux47 by redstar.cs.qmw.ac.uk id aa26828; 5 Apr 91 12:17 BST
801 Reply-To: eliot@cs.qmw.ac.uk
802 In-Reply-To: Simon L Peyton Jones's mail message
803 <2763.9104050851@tutuila.dcs.glasgow.ac.uk>
804 Message-Id:  <9104051217.aa26828@uk.ac.qmw.cs.redstar>
805 From: Eliot Miranda <eliot@cs.qmw.ac.uk>
806 To: simonpj
807 Cc: partain
808 Subject:  re: Threaded code
809 Date:     Fri, 5 Apr 91 10:54:25 BST
810
811 >
812 >Eliot 
813 >
814 >I saw your article about threaded code.  Like you and others, we are
815 >using C as an assembler, only for a pure functional language, Haskell.
816 >I have some brief questions.
817 >
818 >1. By telling GCC not to use a frame pointer, one can eliminate
819 >the prolog entirely, can't one?  So why edit it out?
820 No, registers local to the procedure will still be saved & stack space
821 allocated for automatic variables. This IS a problem since the
822 threaded-code jump at the end of the procedure will miss the register
823 restores before the epilogue.  Consequently the stack will grow unless
824 these register saves & stack-space allocations are removed.
825 >
826 >I guess the answer is going to be local variables, allocated once for
827 >all by the StartTCMachine routine.  Still, it seems quite a pain.  I guess
828 >one could sacrifice some (perhaps slight) speed by using a bunch of
829 >globals instead.
830 For certain routines, not using register variables will be expensive
831 (e.g. a simple integer arithmetic primitive).
832 >
833 >2. You edit out the epilogue for tidiness only, I take it.  It doesn't
834 >cause any problems if you leave it in, does it?
835 No, but given that the prologue has to be removed & removing the epilogue
836 is as easy (& given finite memory :-) one might as well remove it.
837 >
838 >3. Why do you use no-defer-pop (with the associated bug)?
839 OK. This is again to avoid stack growth.  On conventional stack architectures
840 gcc will try & combine the argument popping code of a sequence of
841 procedure calls.
842 e.g.
843 extern long a, b, c;
844 void doit() {
845         foo(a); bar(b); baz(c);
846 }
847
848 with -O -no-defer-pop one might expect gcc to generate
849
850         link %a6,#0
851         movel a,%sp@-
852         jbsr foo
853         addqw #4,%sp
854         movel b,%sp@-
855         jbsr bar
856         addqw #4,%sp
857         movel c,%sp@-
858         jbsr baz
859         addqw #4,%sp
860         unlk %a6
861         rts
862
863 but because gcc knows that the unlk instruction will roll back the stack
864 in fact gcc generates:
865
866         link %a6,#0
867         movel a,%sp@-
868         jbsr foo
869         addqw #4,%sp
870         movel b,%sp@-
871         jbsr bar
872         addqw #4,%sp
873         movel c,%sp@-
874         jbsr baz
875         unlk %a6
876         rts
877
878 With -O -fdefer-pop gcc optimizes out the pops completely & generates:
879
880         link %a6,#0
881         movel a,%sp@-
882         jbsr foo
883         movel b,%sp@-
884         jbsr bar
885         movel c,%sp@-
886         jbsr baz
887         unlk %a6
888         rts
889
890 with -O -fomit-frame-pointer -fdefer-pop gcc generates:
891
892         movel a,%sp@-
893         jbsr foo
894         movel b,%sp@-
895         jbsr bar
896         movel c,%sp@-
897         jbsr baz
898         addw #12,%sp
899         rts
900
901 & with -O -fomit-frame-pointer -fno-defer-pop gcc generates:
902
903         movel a,%sp@-
904         jbsr foo
905         addqw #4,%sp
906         movel b,%sp@-
907         jbsr bar
908         addqw #4,%sp
909         movel c,%sp@-
910         jbsr baz
911         addqw #4,%sp
912         rts
913
914 All the above cases are as one would wish.  The elimination of all
915 defered pops in the unlk instruction is especially clever.
916
917 However, in the presence of the threaded-code jump the waters darken!
918 Consider what gcc generates for:
919
920         register void (**tcip)() asm("%a5");
921
922         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");return;}while(0)
923
924         extern long a, b;
925         void doit() {
926                 foo(a); bar(b); JUMPNEXT;
927         }
928 with -O -fdefer-pop gcc generates
929
930 doit:
931         link %a6,#0
932         movel a,%sp@-
933         jbsr foo
934         movel b,%sp@-
935         jbsr bar
936 #APP
937         movl %a5@+,%a0; jmp %a0@
938 #NO_APP
939         unlk %a6
940         rts
941
942 This is clearly no good because the arguments to both foo & bar
943 will never be popped. Every time doit() is executed the stack will grow
944 by 8 bytes.  Soon your program will dump core with a very large stack
945 segment!
946
947 with -O -fno-defer-pop gcc generates:
948
949         link %a6,#0
950         movel a,%sp@-
951         jbsr foo
952         addqw #4,%sp
953         movel b,%sp@-
954         jbsr bar
955 #APP
956         movl %a5@+,%a0; jmp %a0@
957 #NO_APP
958         unlk %a6
959         rts
960
961 Again useless because bar's pop has been folded into the unlk
962 which won't be executed.
963
964 with -O -fdefer-pop -fomit-frame-pointer gcc generates
965
966         movel a,%sp@-
967         jbsr foo
968         movel b,%sp@-
969         jbsr bar
970         addqw #8,%sp
971 #APP
972         movl %a5@+,%a0; jmp %a0@
973 #NO_APP
974         rts
975
976 This is great. However, not all functions are susceptible to
977 the omit-frame-pointer optimization (i.e. functions
978 with local variables).  E.g. the code generated for:
979
980         register void (**tcip)() asm("%a5");
981
982         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");return;}while(0)
983
984         extern long a, b;
985         void doit() {
986                 char    large[1024];
987                 foo(a,large); bar(b); JUMPNEXT;
988         }
989
990 with -O -fomit-frame-pointer -fdefer-pop is:
991
992         link %a6,#-1024
993         pea %a6@(-1024)
994         movel a,%sp@-
995         jbsr foo
996         movel b,%sp@-
997         jbsr bar
998 #APP
999         movl %a5@+,%a0; jmp %a0@
1000 #NO_APP
1001         unlk %a6
1002         rts
1003
1004 so in general one can't rely on -fomit-frame-pointer.
1005 For the above example both
1006         -O -fomit-frame-pointer -fno-defer-pop
1007 and
1008         -O -fno-defer-pop
1009 generate:
1010
1011 doit:
1012         link %a6,#-1024
1013         pea %a6@(-1024)
1014         movel a,%sp@-
1015         jbsr foo
1016         addqw #8,%sp
1017         movel b,%sp@-
1018         jbsr bar
1019 #APP
1020         movl %a5@+,%a0; jmp %a0@
1021 #NO_APP
1022         unlk %a6
1023         rts
1024
1025 This is also useless because bar's argument pop has been folded away.
1026 The problem is that gcc will always fold away the last call's argument
1027 pop if the function has a frame pointer, and -fomit-frame-pointer
1028 can't allways get rid of the frame pointer.  In fact, in the presence
1029 of variable sized automatic variables or calls on alloca it would be
1030 very hard (impossible for recursive functions?) to do.
1031
1032 The eatest solution I've come up with is to use -fno-defer-pop
1033 and a dummy function call between the threaded-code jump and
1034 the return:
1035
1036         register void (**tcip)() asm("%a5");
1037
1038         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp
1039 %a0@");SBD();return;}while(0)
1040
1041         extern long a, b;
1042         void doit() {
1043                 foo(a); bar(b); JUMPNEXT;
1044         }
1045 with -O -fno-defer-pop gcc generates:
1046
1047         link %a6,#0
1048         movel a,%sp@-
1049         jbsr foo
1050         addqw #4,%sp
1051         movel b,%sp@-
1052         jbsr bar
1053         addqw #4,%sp
1054 #APP
1055         movl %a5@+,%a0; jmp %a0@
1056 #NO_APP
1057         jbsr SBD
1058         unlk %a6
1059         rts
1060
1061 Now bar's argument pop is not folded because its no longer the last
1062 call in the routine, SBD is.
1063 So the call to SBD
1064         a) prevents gcc's 'last call argument pop fold into unlk' optimization
1065            which prevents uncontrolled stack growth.
1066         b) doesn't get executed because of the jump
1067         c) is trivial to remove from the assembler with a sed-script
1068
1069
1070 >4. Why does JUMPNEXT have a loop?  Surely the jump leaves the loop right
1071 >away.  Presumably you are tricking the compiler somehow.
1072 >
1073 This is old C lore.  The problem is
1074         'How do you write a macro that is a sequence of statements
1075          that can be used wherever a single statement can?'
1076
1077 take the following definition of JUMPNEXT:
1078 #define JUMPNEXT asm("movl %a5@+,%a0; jmp %a0@");return;
1079
1080 Now invoke it here:
1081         if (its_time_to_jump)
1082                 JUMPNEXT;
1083         do_something_else();
1084
1085 This expands to:
1086         if (its_time_to_jump)
1087                 asm("movl %a5@+,%a0; jmp %a0@");
1088         return;
1089         do_something_else();
1090
1091 Not at all whats intended!
1092
1093 There are two tricks I know of (the first I saw in Berkely Smalltalk,
1094 the second in Richard Stallman's gcc manual. I expect they're both
1095 quite old).
1096 The first is to surround your statements with
1097 if (TRUE){statements}else
1098 i.e.
1099 #define JUMPNEXT if(1){asm("movl %a5@+,%a0; jmp %a0@");return;}else
1100 So now we get:
1101         if (its_time_to_jump)
1102                 if (1){
1103                         asm("movl %a5@+,%a0; jmp %a0@");
1104                         return;
1105                 else;
1106         do_something_else();
1107
1108 which works because C binds elses innermost first. However, some
1109 compilers will whine about dangling elses.  The second scheme is
1110 more elegant (-:
1111
1112 Surround your statements with
1113 do{statements}while(FALSE);
1114 which will execute statements precisely once (its NOT a loop).
1115 i.e.
1116 #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");SBD();return;}while(0)
1117 expands to
1118
1119         if (its_time_to_jump)
1120                 do {
1121                         asm("movl %a5@+,%a0; jmp %a0@");
1122                         return;
1123                 while(0);
1124         do_something_else();
1125
1126 which does what's wanted and doesn't incur compiler whines.
1127
1128
1129 >Thanks
1130 >
1131 >Simon L Peyton Jones
1132 >Glasgow University
1133
1134 Eliot Miranda                   email:  eliot@cs.qmw.ac.uk
1135 Dept of Computer Science        Tel:    071 975 5229 (+44 71 975 5229)
1136 Queen Mary Westfield College    ARPA:   eliot%cs.qmw.ac.uk@nsf.ac.uk    
1137 Mile End Road                   UUCP:   eliot@qmw-cs.uucp
1138 LONDON E1 4NS
1139
1140 >From vestal@SRC.Honeywell.COM Fri Apr  5 12:26:11 1991
1141 From: vestal@SRC.Honeywell.COM (Steve Vestal)
1142 Newsgroups: comp.compilers
1143 Subject: Re: Portable Fast Direct Threaded Code
1144 Keywords: interpreter, performance, design
1145 Date: 3 Apr 91 18:23:34 GMT
1146 Reply-To: vestal@SRC.Honeywell.COM (Steve Vestal)
1147 Organization: Honeywell Systems & Research Center
1148 In-Reply-To: pardo@cs.washington.edu's message of 2 Apr 91 19:21:25 GMT
1149
1150 In article <1991Apr2.192125.7464@beaver.cs.washington.edu>
1151 pardo@cs.washington.edu (David Keppel) writes:
1152 [references about threaded code, much stuff deleted]
1153
1154 David> %X Describes how to build a threaded code interpeter/compiler from
1155 David> scratch.
1156 David>  * Less than 2:1 performance hit of threading compared to full
1157 David> compilation.
1158
1159 I have a question about this.  Numbers like this are often cited for
1160 threaded-type code, but in Bell's paper this was for the PDP-11 (whose
1161 addressing modes made it a natural for threaded code).  Paul Klint's
1162 "Interpretation Techniques" paper (Software P&E, v11, 1981) cites a
1163 significant difference for interpreter fetch/decode times on different
1164 architectures.  He cited numbers around 2:1 for the PDP-11, but something
1165 more like 9:1 for a Cyber.  I did a Q&D evaluation of this for a RISC, and
1166 the ratio I guestemated was closer to that Klint gave for the Cyber than
1167 for the PDP-11 (not unexpectedly).
1168
1169 How architecturally dependent is the performance of these techniques
1170 (relative to compiling to native code)?
1171
1172 Steve Vestal
1173 Mail: Honeywell S&RC MN65-2100, 3660 Technology Drive, Minneapolis MN 55418 
1174 Phone: (612) 782-7049                    Internet: vestal@src.honeywell.com
1175 -- 
1176 Send compilers articles to compilers@iecc.cambridge.ma.us or
1177 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1178
1179 >From E.Ireland@massey.ac.nz Fri Apr  5 12:29:20 1991
1180 From: E.Ireland@massey.ac.nz (Evan Ireland)
1181 Newsgroups: comp.lang.functional
1182 Subject: Three address code
1183 Date: 4 Apr 91 21:49:21 GMT
1184 Reply-To: E.Ireland@massey.ac.nz
1185 Organization: Information Sciences, Massey University, New Zealand
1186
1187 I've had no luck with mail, so this is for csdw at Rhodes University.
1188
1189 >
1190 >In an attempt to optimize a functional language, I would like to
1191 >turn the stack based intermediate code into three address code. 
1192 >
1193 >Has anyone done similar conversions?  Any references would be
1194 >greatly appreciated.
1195
1196 I do not have any references, but I thought that one aspect of my FAM
1197 implementation might be of interest.
1198
1199 A number of interpreters and compilers that I have seen implement a stack
1200 pointer in a register or global variable.  Then to implement various stack
1201 operations, they use auto-increment or auto-decrement operations on the stack
1202 pointer register.  Since I generate portable C, and thus cannot assume I have
1203
1204     DATA *f (register DATA *fp)
1205     {
1206         ....
1207     }
1208
1209 Thus I pass to each function the current pointer to top of stack, from which it
1210 can index downwards to find its arguments.  Within the function, I use indexing
1211 operations on fp, e.g. fp[3] = fp[1], to manipulate values on the stack, so I
1212 am not continually manipulating the stack pointer.  If "f" calls another
1213 function, it will pass the address of the current top of stack, e.g. g (&f[5]).
1214
1215 The advantage to me is that I have a register for a stack pointer even though I
1216 am generating portable C code.
1217
1218 Now the relationship to three-address code.  If you adopt such a scheme, and
1219 your three address instructions allow some indexing, you can sometimes generate
1220
1221         ADD     fp[3],f[4],fp[3]
1222
1223 I hope this helps.
1224 _______________________________________________________________________________
1225
1226 E.Ireland@massey.ac.nz          Evan Ireland, School of Information Sciences,
1227   +64 63 69099 x8541          Massey University, Palmerston North, New Zealand.
1228
1229 >From pardo@cs.washington.edu Sat Apr  6 14:32:24 1991
1230 From: pardo@cs.washington.edu (David Keppel)
1231 Newsgroups: comp.compilers
1232 Subject: Re: Portable Fast Direct Threaded Code
1233 Keywords: interpreter, performance, design
1234 Date: 4 Apr 91 17:10:55 GMT
1235 Reply-To: pardo@cs.washington.edu (David Keppel)
1236 Organization: Computer Science & Engineering, U. of Washington, Seattle
1237
1238 >>>[Threaded code vs. compilation]
1239
1240 >pardo@cs.washington.edu (David Keppel) writes:
1241 >>[Less than 2:1 performance hit of threading vs. full compilation.]
1242
1243 Note also that the reference that claimed 2:1 (Peter M. Kogge, IEEE
1244 Computer pp 22-33 March 1982) also attributed part of that ratio to the
1245 poor state of compiler optimization.
1246
1247
1248 vestal@SRC.Honeywell.COM (Steve Vestal) writes:
1249 >[Klint says 2:1 for PDP-11 v. 9:1 for Cyber.
1250 > How architecturally dependent are these techniques?]
1251
1252 Suppose that the statically-compiled code fragments that are threaded
1253 together are called `primitives'.
1254
1255
1256 When the execution time of a primitive is large, then the overhead for the
1257 interpreter can be large and still have a small effect on performance.
1258 The performance of the interpreted code is dominated by the time in a
1259 primitive vs. the overhead of moving between primitives.
1260
1261 When the execution time of the primitives is small, then the overhead for
1262 moving between primitives can be a large fraction of the total execution
1263 time.  Overhead comes from at least two sources:
1264
1265  * Control flow: the address of the the next primitive is loaded
1266    from data memory and the processor executes an indirect jump.
1267
1268  * Register allocation: a primitive is essentially a function with
1269    a fast calling convention (no stack adjustment).  Thus, all the
1270    traditional problems with interprocedural register allocation.
1271
1272 Examples of `large primitives' are ``draw circle'' and ``interpolate
1273 spline''.  Examplees of small primitives are ``push'', ``add'', etc.
1274
1275
1276 * Architectural dependency of control flow
1277
1278 Examples:
1279
1280         Direct jumps in full compilation:
1281
1282                 op1
1283                 op2
1284                 br next         // direct jump
1285         
1286         Indirect jumps for threading for a CISC:
1287
1288                 op1
1289                 op2
1290                 br *(r0)+       // load, increment, jump
1291
1292         Indirect jumps for threading for a RISC:
1293
1294                 ld *r0, r1      // scheduled load
1295                 op1
1296                 op2
1297                 br *r1          // jump
1298                 add r1, #4, r1  // delay slot increment
1299
1300 Direct jumps in full compilation can frequently use one instruction (a
1301 ``near branch'') both to find the address of the next code fragment and
1302 perform the control transfer.  On a CISC, branches are typically two or
1303 three bytes.  On a RISC, branches are typically four bytes.  The threaded
1304 indirect (load, increment, jump) is typically three bytes on a CISC and
1305 twelve bytes (three instructions) on a RISC.
1306
1307 Direct jumps in full compilation take typically one instruction time.
1308 Indirect jumps take at least the following operations: load, increment,
1309 jump indirect.  On a CISC, the three operations can typically be `folded'
1310 in to one instruction.  There may be a load penalty of one instruction
1311 time but the increment is overlapped, so the total time is three machine
1312 units (one `unit' is about one register->register operation).  On a RISC,
1313 the total penalty is three machine units.
1314
1315 Direct jumps take one (I-cache) cycle to fetch both the branch instruction
1316 and the address of the branch target.  Indirect jumps take a D-cache cycle
1317 to fetch the address of the branch target and an I-cache cycle to fetch
1318 the branch instruction.
1319
1320 Direct jumps can take advantage of instruction prefetching since the
1321 address of the next instruction is known at the time that the instruction
1322 prefetch reads the direct jump.  Threaded indirects require an indirect
1323 branch off of a register.  Current RISC and CISC machines are about
1324 equivalent in that they do little prefetching.  Some machines being
1325 designed do more prefetching so the threading overhead for them will be
1326 greater.
1327
1328
1329 * Architectural dependency of register allocation
1330
1331 In a machine with a small number of registers, many of the registers are
1332 in-use in each primitive and the best possible register allocation will
1333 contain many loads and stores.  In a machine with a large number of
1334 registers, the full-compilation implementation can make much better use of
1335 registers than the threaded primitives implementation (again, for small
1336 primitives).  The savings from full compilation are exactly analagous to
1337 the improvements in register allocation from doing inlining of small
1338 procedures.
1339
1340
1341 * Other points to ponder
1342
1343 In some cases the threaded code implementation is substantially smaller
1344 than the full-compilation implementation.  For large functions or a
1345 machine with small caches, the loss of performance from threading might be
1346 overwhelmed by the gain in cache performance.
1347
1348 On RISC machines, procedure call/return is about twice the cost of other
1349 control flow, except for the overhead of register management.  Thus,
1350 call-dense RISC code from full compilation may behave about the same as
1351 threaded code.
1352 -- 
1353 Send compilers articles to compilers@iecc.cambridge.ma.us or
1354 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1355
1356 >From airs!ian@uunet.UU.NET Sat Apr  6 14:32:56 1991
1357 From: airs!ian@uunet.UU.NET (Ian Lance Taylor)
1358 Newsgroups: comp.compilers
1359 Subject: Threaded code
1360 Keywords: books, interpreter, design
1361 Date: 4 Apr 91 07:19:41 GMT
1362 Reply-To: airs!ian@uunet.UU.NET (Ian Lance Taylor)
1363 Organization: Compilers Central
1364
1365 The book ``Threaded Interpretive Languages'' has a quite complete
1366 implementation of a threaded version of Forth in Z80 assembler.  It's
1367 a very complete description of why threaded implementations exist and
1368 how to implement them easily.  It's by R. G. Loeliger and was
1369 published by Byte Books (ISBN 0-07-038360-X).  It was published in
1370 1981, though, and I'm sure it's long out of print.
1371
1372 Ian Taylor              airs!ian@uunet.uu.net               uunet!airs!ian
1373 -- 
1374 Send compilers articles to compilers@iecc.cambridge.ma.us or
1375 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1376
1377 >From firth@sei.cmu.edu Sun Apr  7 14:33:13 1991
1378 From: firth@sei.cmu.edu (Robert Firth)
1379 Newsgroups: comp.compilers
1380 Subject: Re: Portable Fast Direct Threaded Code
1381 Keywords: interpreter, performance, design
1382 Date: 4 Apr 91 13:27:21 GMT
1383 Reply-To: firth@sei.cmu.edu (Robert Firth)
1384 Organization: Software Engineering Institute, Pittsburgh, PA
1385
1386 In article <1991Apr3.182334.16164@src.honeywell.com>
1387 vestal@SRC.Honeywell.COM (Steve Vestal) writes:
1388
1389 >How architecturally dependent is the performance of these techniques
1390 >(relative to compiling to native code)?
1391
1392 [cost of threaded code on PDP-11, RISC &c]
1393
1394 We might have a misunderstanding here, because what I think of as threaded
1395 code doesn't have a decoding and interpretation step.  But I'll talk of
1396 what I know.
1397
1398 A program in threaded code is just an array of addresses, possibly
1399 interspersed with operands.  So the fragment
1400
1401         c := a + b
1402
1403 becomes something like
1404
1405         address of 'load'
1406         address of 'a'
1407         address of 'load'
1408         address of 'b'
1409         address of '+'
1410         address of 'store'
1411         address of 'c'
1412
1413 This implies a very simple virtual stack machine - you can get more clever
1414 by implementing a virtual register machine.
1415
1416 The basic execution thread then does this.  We point a global register at
1417 the table of addresses, and each primitive has the form
1418
1419         treg := treg + address'size
1420         ...
1421         jump (treg)
1422
1423 As you can see, this is great on the PDP-11, since that reduces to one
1424 instruction
1425
1426         MOV (treg)+,PC  ; NOTE TO MAINTAINER: FASTER THAN JMP - DON'T TOUCH!
1427
1428 On a typical RISC machine, it's two cycles, since you can put almost
1429 anything in the delay slot(s) after the jump.
1430
1431 Now, the load instruction, for instance, would say
1432
1433 load:   treg := treg + address'size
1434         load (treg) into tempreg
1435         treg := treg + address'size
1436         push (tempreg) onto simulated stack
1437         jump (treg)
1438
1439 On the PDP-11, that's
1440
1441         MOV @(treg)+, -(SP)
1442         MOV (treg)+, PC
1443
1444 On a RISC, it's much more like
1445
1446         L R0, 4(treg)           ; get operand address
1447         L R0, 0(R0)             ; dereference to get operand
1448         SUBI SP, #4             ; decrement simulated SP
1449         S R0, 0(SP)             ; push operand on stack
1450         ADDI treg, #8           ; step over two addresses (mine & operands)
1451         JR (treg)               ; over to you, Moriarty!
1452
1453 [to fill delay slots, shuffle the above to 132564]
1454
1455 Well, if you have one load delay slot and one branch delay slot, you can
1456 fill all three of them, so that's 6 cycles.  Given that a typical load is
1457 only 1.1 cycles in direct code (90% of the delays filled), this is
1458 certainly a lot more than a 2:1 overhead!  When you add the cost of a
1459 simulated stack (lots of needless loads and stores), I can well believe
1460 10:1 time expansion for simple code.
1461
1462 In fact, it was more than that on the PDP-11, if you compared threaded
1463 code with direct code from a decent compiler.  The big win in the Fortran
1464 compiler came from (a) very compact threaded code, and (b) the floating
1465 point operations were implemented in software, so the overhead of threaded
1466 code was swamped by the cost of floating addition, subtraction &c.
1467
1468 Here's the full code of the above example, so you can see for yourself
1469
1470 Direct:
1471         MOV a, R0
1472         ADD b, R0
1473         MOV R0, c
1474
1475 Threaded:
1476         MOV @(treg)+, -(SP)
1477         MOV (treg)+, PC
1478 *       MOV @(treg)+, -(SP)
1479 *       MOV (treg)+, PC
1480 *       ADD (SP)+,(SP)
1481         MOV (treg)+, PC
1482         MOV (SP)+, @(treg)+
1483         MOV (treg)+, PC
1484
1485 Note that, if you implement a one-address add, you save two instructions,
1486 since the *** bit reduces to
1487
1488         ADD @(treg)+, (SP)
1489
1490 But even then, it's coming out at over 4:1.
1491
1492 What architectural features make threaded code more efficient?  The
1493 fundamental one is main memory that is fast (or not too slow) relative to
1494 registers, since you're doing a lot more fetching.  Another is a set of
1495 address modes with double indirection, since you're accessing most
1496 operands one level of indirection further back.  And good old
1497 autoincrement helps a little, too.  Alas, none of that says 'risc', and
1498 much of it says '1960s'.
1499
1500 Incidentally, if I were to do this again today, I'd definitely simulate a
1501 general-register machine and use a subset of the real machine's registers.
1502 If you take 8 of them, you then have 8 loads and stores, one for each
1503 register, but if you make an absolute rule that nobody even thinks about
1504 touching one of those 8 that doesn't belong to him, then all the good
1505 tricks about register allocation, slaving &c will still work.  If you then
1506 implement the operations as one-address general-register, you have again 8
1507 versions (add into R0, add into R1, ...) and lo! you're programming a very
1508 familiar old friend.
1509
1510 "But that was in another country, and besides, the machine is dead..."
1511
1512 Robert Firth
1513 -- 
1514 Send compilers articles to compilers@iecc.cambridge.ma.us or
1515 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1516
1517 >From bpendlet@bambam.es.com Tue Apr  9 20:35:22 1991
1518 From: bpendlet@bambam.es.com (Bob Pendleton)
1519 Newsgroups: comp.compilers
1520 Subject: Re: Portable Fast Direct Threaded Code
1521 Keywords: interpreter, design
1522 Date: 8 Apr 91 19:48:00 GMT
1523 Reply-To: bpendlet@bambam.es.com (Bob Pendleton)
1524 Organization: Compilers Central
1525
1526 In article <23613@as0c.sei.cmu.edu> you write:
1527
1528 > A program in threaded code is just an array of addresses, possibly
1529 > interspersed with operands.  So the fragment
1530
1531 >       c := a + b
1532
1533 > becomes something like
1534
1535 >       address of 'load'
1536 >       address of 'a'
1537 >       address of 'load'
1538 >       address of 'b'
1539 >       address of '+'
1540 >       address of 'store'
1541 >       address of 'c'
1542
1543 > This implies a very simple virtual stack machine - you can get more clever
1544 > by implementing a virtual register machine.
1545
1546 About 10 years ago I was working on a lisp compler that compiled to
1547 threaded code. I was trying to get small code and still have some
1548 performance. (Since I wanted to run the code on a Z80 or 8080 small was
1549 important. My how things change :-)
1550
1551 I found that the 3 most common operations in threaded code were load,
1552 store, and execute. So I put those operations with the operands. This
1553 made the operands look rather like classes with load, store, and
1554 execute as virtual functions. If you let the evaluate operation
1555 subsume the load and execute operations the threaded code for
1556
1557         c := a + b;
1558
1559 becomes
1560
1561         address of 'a.evaluate()'
1562         address of 'b.evaluate()'
1563         address of '+'
1564         address of 'c.store()'
1565
1566 and
1567
1568         g := F(x, y);
1569
1570 becomes
1571
1572         address of 'x.evaluate()'
1573         address of 'y.evaluate()'
1574         address of 'F.evaluate()'
1575         address of 'g.store()'
1576
1577
1578 Which is much smaller than the original version of threaded code.
1579
1580 Later, while working on a homebrew version of FORTH I gave up on
1581 threaded code completely. I found, like most who have expolored it,
1582 that symbolic execution of RPN code is a nice way to generated machine
1583 code. Machine code that runs much faster than threaded code, and that
1584 the machine code, even on an 8080, was only about 25% bigger than
1585 threaded code.
1586 -- 
1587               Bob Pendleton
1588    bpendlet@dsd.es.com or decwrl!esunix!bpendlet or utah-cs!esunix!bpendlet
1589 [The DEC PDP-11 Fortran compiler did something similar, writing load routines
1590 for commonly used variables. -John]
1591 -- 
1592 Send compilers articles to compilers@iecc.cambridge.ma.us or
1593 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1594
1595 >From pardo@june.cs.washington.edu Wed Apr 24 09:26:32 1991
1596 From: pardo@june.cs.washington.edu (David Keppel)
1597 Newsgroups: comp.compilers
1598 Subject: Re: Fast Interpreted Code
1599 Keywords: interpreter, threaded code
1600 Date: 23 Apr 91 02:06:21 GMT
1601 Reply-To: pardo@june.cs.washington.edu (David Keppel)
1602 Organization: Computer Science & Engineering, U. of Washington, Seattle
1603
1604 ssinghani@viewlogic.com (Sunder Singhani) writes:
1605 >[Our threaded code isn't fast enough.  What's faster?]
1606
1607 As far as I know, threaded code gives the fastest primitives/second
1608 dispatch rate on a variety of architectures.  The general techniques for
1609 making things faster (that I know of!) are to change things so that the
1610 dispatch rate can go down without changing the work that gets done (or use
1611 hardware, but we'll ignore that for the moment.)
1612
1613 * Use a different v-machine instruction set
1614
1615   The overhead of interpreting is almost nothing in generic PostScript
1616   imaging code because all the time is spent in non-interpretded
1617   primitives.  If you can characterize your key operations (perhaps
1618   info in [Davidson & Fraser ??, Fraser, Myers & Wendt 84] can help
1619   you analyze for common operations instead of the more normal time in
1620   routines) then you can re-code your virtual instruction set to have
1621   as primintives oeprations that are performed frequently.
1622
1623 * Dynamic compilation to native machine code
1624
1625   This is what is done in ParcPlace System's Smalltalk-80
1626   implementation,  [Deutsch & Schiffman 84] also Insignia Solution's
1627   8086 interpreter.
1628
1629   Dynamic compilation suffers from the need to do compilation at
1630   runtime: a compiler that produces better code will take longer to
1631   run and the compile time contributes to the overall program runtime.
1632   Also, program text isn't shared, even if multiple instances are
1633   running simultaneously.
1634  
1635 * Native-coding key routines
1636
1637   If you believe that your program spends 80% of its time in a few key
1638   routines, then compiling just these routines -- statically, adding
1639   them to the primitive set, statically adding them as library
1640   routines, or dynamically -- can improve performance substantially
1641   [Pittman 87].
1642
1643
1644 5 Citations follow:
1645
1646 %A Robert Bedichek
1647 %T Some Efficient Architecture Simulation Techniques
1648 %J Winter '90 USENIX Conference
1649 %D 26 October, 1989
1650 %W Robert Bedichek.
1651 %W Pardo has a copy.
1652 %X Describes a simulator that uses threaded-code techniques to emulate
1653 a Motorola 88000.  Each 88k instruction is executed in about 20 host
1654 (68020) instructions.  Discusses techniques used to get the simulation
1655 down from several thousand host instructions in many other
1656 simulators.
1657
1658 %A Jack W. Davidson
1659 %A Chris W. Fraser
1660 %T Eliminating Redundant Object Code
1661 %J POPL9
1662 %P 128-132
1663
1664 %A Peter Deutsch
1665 %A Alan M. Schiffman
1666 %T Efficient Implementation of the Smalltalk-80 System
1667 %J 11th Annual Symposium on Principles of Programming Languages
1668 (POPL 11)
1669 %D January 1984
1670 %P 297-302
1671 %X Dynamic translatin of p-code to n-code (native code).
1672 Resons for not using straight p-code or straight n-code:
1673  * p-code is smaller than n-code (<= 5X).
1674  * The debugger can debug p-code, improving portability.
1675  * Native code is faster (<= 2X).  Reasons include
1676 special fetch/decode/dispatch hardware;
1677 p-machine and n-machine may be very different, e.g.,
1678 stack machine vs. register-oriented.
1679  * Threaded code does reduce the cost of p-code fetch/decode.
1680 Does not help with operand decoding.
1681 Does not allow optimizations to span more than one instruction.
1682 [pardo: that's not technically true, but each optimized
1683 instruction must exist in addition to the unoptimized version.
1684 That leads to exponential blowup of the p-code.  Example: delayed
1685 branch and non-delayed branch versions of Robert Bedichek's 88k
1686 simulator.]
1687  The system characteristics:
1688  * The time to translate to n-code via macro expansion is about the
1689 same as the execute time to interpret the p-code.
1690  * (pg 300:) Self-modifying code (SMC) is deprecated but used in a
1691 ``well-confined'' way.  Could indirect at more cost.  Use SMC on the
1692 machines where it works, indirection where SMC.
1693 doesn't.
1694  * Performance is compared to a ``straightforward'' interpreter.
1695 What's that?
1696
1697 %A Christopher W. Fraser
1698 %A Eugene W. Myers
1699 %A Alan L. Wendt
1700 %T Analyzing and Compressing Assembly Code
1701 %J CCC84
1702 %P 117-121
1703
1704 %A Thomas Pittman
1705 %T Two-Level Hybrid Interpreter/Native Code Execution for Combined
1706 Space-Time Program Efficiency
1707 %D 1987
1708 %J ACM SIGPLAN
1709 %P 150-152
1710 %X Talks about native code execution vs. various kinds of interpreting
1711 and encoding of key routines in assembly.
1712
1713
1714 Hope this helps!
1715
1716         ;-D on  ( This is all open to interpretation )  Pardo
1717 -- 
1718 Send compilers articles to compilers@iecc.cambridge.ma.us or
1719 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
1720
1721 >From eliot@cs.qmw.ac.uk Tue Apr 30 15:55:17 1991
1722 From: eliot@cs.qmw.ac.uk (Eliot Miranda)
1723 Newsgroups: comp.compilers,gnu.gcc.bug,alt.sources
1724 Subject: re: Threaded Code
1725 Keywords: design, interpreter
1726 Date: 5 Apr 91 11:43:51 GMT
1727 Reply-To: Eliot Miranda <eliot@cs.qmw.ac.uk>
1728 Followup-To: comp.compilers
1729 Organization: Computer Science Dept, QMW, University of London, UK.
1730
1731 I recently posted articles to comp.compilers & alt.sources on how
1732 to write threaded code machines in C.  I received the following questions
1733 from Simon Peyton Jones at Glasgow.  They are specific to GCC.
1734 Since they have non-obvious answers & since the answers suggest
1735 augmentation of the GCC compiler I'm posting my response to Simon.
1736
1737 >From: Simon L Peyton Jones <simonpj@cs.gla.ac.uk>
1738 >
1739 >I saw your article about threaded code.  Like you and others, we are
1740 >using C as an assembler, only for a pure functional language, Haskell.
1741 >I have some brief questions.
1742 >
1743 >1. By telling GCC not to use a frame pointer, one can eliminate
1744 >the prolog entirely, can't one?  So why edit it out?
1745
1746 No, registers local to the procedure will still be saved & stack space
1747 allocated for automatic variables. This IS a problem since the
1748 threaded-code jump at the end of the procedure will miss the register
1749 restores before the epilogue.  Consequently the stack will grow unless
1750 these register saves & stack-space allocations are removed.  Also
1751 GCC can not always eliminate the frame pointer.
1752
1753 >I guess the answer is going to be local variables, allocated once for
1754 >all by the StartTCMachine routine.  Still, it seems quite a pain.  I guess
1755 >one could sacrifice some (perhaps slight) speed by using a bunch of
1756 >globals instead.
1757 For certain routines, not using register variables will be expensive
1758 (e.g. a simple integer arithmetic primitive).
1759
1760 >2. You edit out the epilogue for tidiness only, I take it.  It doesn't
1761 >cause any problems if you leave it in, does it?
1762 No, but given that the prologue has to be removed & removing the epilogue
1763 is as easy (& given finite memory :-) one might as well remove it.
1764 >
1765 >3. Why do you use no-defer-pop (with the associated bug)?
1766 OK. This is again to avoid stack growth.  On conventional stack architectures
1767 gcc will try & combine the argument popping code of a sequence of
1768 procedure calls.
1769 e.g.
1770 extern long a, b, c;
1771 void doit() {
1772         foo(a); bar(b); baz(c);
1773 }
1774
1775 with -O -no-defer-pop one might expect gcc to generate
1776
1777         link %a6,#0
1778         movel a,%sp@-
1779         jbsr foo
1780         addqw #4,%sp
1781         movel b,%sp@-
1782         jbsr bar
1783         addqw #4,%sp
1784         movel c,%sp@-
1785         jbsr baz
1786         addqw #4,%sp
1787         unlk %a6
1788         rts
1789
1790 but because gcc knows that the unlk instruction will roll back the stack
1791 in fact gcc generates:
1792
1793         link %a6,#0
1794         movel a,%sp@-
1795         jbsr foo
1796         addqw #4,%sp
1797         movel b,%sp@-
1798         jbsr bar
1799         addqw #4,%sp
1800         movel c,%sp@-
1801         jbsr baz
1802         unlk %a6
1803         rts
1804
1805 With -O -fdefer-pop gcc optimizes out the pops completely & generates:
1806
1807         link %a6,#0
1808         movel a,%sp@-
1809         jbsr foo
1810         movel b,%sp@-
1811         jbsr bar
1812         movel c,%sp@-
1813         jbsr baz
1814         unlk %a6
1815         rts
1816
1817 with -O -fomit-frame-pointer -fdefer-pop gcc generates:
1818
1819         movel a,%sp@-
1820         jbsr foo
1821         movel b,%sp@-
1822         jbsr bar
1823         movel c,%sp@-
1824         jbsr baz
1825         addw #12,%sp
1826         rts
1827
1828 & with -O -fomit-frame-pointer -fno-defer-pop gcc generates:
1829
1830         movel a,%sp@-
1831         jbsr foo
1832         addqw #4,%sp
1833         movel b,%sp@-
1834         jbsr bar
1835         addqw #4,%sp
1836         movel c,%sp@-
1837         jbsr baz
1838         addqw #4,%sp
1839         rts
1840
1841 All the above cases are as one would wish.  The elimination of all
1842 defered pops in the unlk instruction is especially clever.
1843
1844 However, in the presence of the threaded-code jump the waters darken!
1845 Consider what gcc generates for:
1846
1847         register void (**tcip)() asm("%a5");
1848
1849         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");return;}while(0)
1850
1851         extern long a, b;
1852         void doit() {
1853                 foo(a); bar(b); JUMPNEXT;
1854         }
1855 with -O -fdefer-pop gcc generates
1856
1857 doit:
1858         link %a6,#0
1859         movel a,%sp@-
1860         jbsr foo
1861         movel b,%sp@-
1862         jbsr bar
1863 #APP
1864         movl %a5@+,%a0; jmp %a0@
1865 #NO_APP
1866         unlk %a6
1867         rts
1868
1869 This is clearly no good because the arguments to both foo & bar
1870 will never be popped. Every time doit() is executed the stack will grow
1871 by 8 bytes.  Soon your program will dump core with a very large stack
1872 segment!
1873
1874 with -O -fno-defer-pop gcc generates:
1875
1876         link %a6,#0
1877         movel a,%sp@-
1878         jbsr foo
1879         addqw #4,%sp
1880         movel b,%sp@-
1881         jbsr bar
1882 #APP
1883         movl %a5@+,%a0; jmp %a0@
1884 #NO_APP
1885         unlk %a6
1886         rts
1887
1888 Again useless because bar's pop has been folded into the unlk
1889 which won't be executed.
1890
1891 with -O -fdefer-pop -fomit-frame-pointer gcc generates
1892
1893         movel a,%sp@-
1894         jbsr foo
1895         movel b,%sp@-
1896         jbsr bar
1897         addqw #8,%sp
1898 #APP
1899         movl %a5@+,%a0; jmp %a0@
1900 #NO_APP
1901         rts
1902
1903 This is great. However, not all functions are susceptible to
1904 the omit-frame-pointer optimization (i.e. functions
1905 with local variables).  E.g. the code generated for:
1906
1907         register void (**tcip)() asm("%a5");
1908
1909         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");return;}while(0)
1910
1911         extern long a, b;
1912         void doit() {
1913                 char    large[1024];
1914                 foo(a,large); bar(b); JUMPNEXT;
1915         }
1916
1917 with -O -fomit-frame-pointer -fdefer-pop is:
1918
1919         link %a6,#-1024
1920         pea %a6@(-1024)
1921         movel a,%sp@-
1922         jbsr foo
1923         movel b,%sp@-
1924         jbsr bar
1925 #APP
1926         movl %a5@+,%a0; jmp %a0@
1927 #NO_APP
1928         unlk %a6
1929         rts
1930
1931 so in general one can't rely on -fomit-frame-pointer.
1932 For the above example both
1933         -O -fomit-frame-pointer -fno-defer-pop
1934 and
1935         -O -fno-defer-pop
1936 generate:
1937
1938 doit:
1939         link %a6,#-1024
1940         pea %a6@(-1024)
1941         movel a,%sp@-
1942         jbsr foo
1943         addqw #8,%sp
1944         movel b,%sp@-
1945         jbsr bar
1946 #APP
1947         movl %a5@+,%a0; jmp %a0@
1948 #NO_APP
1949         unlk %a6
1950         rts
1951
1952 This is also useless because bar's argument pop has been folded away.  The
1953 problem is that gcc will always fold away the last call's argument pop if
1954 the function has a frame pointer, and -fomit-frame-pointer can't allways
1955 get rid of the frame pointer.  In fact, in the presence of variable sized
1956 automatic variables or calls on alloca it would be very hard (impossible
1957 for recursive functions?) to do.
1958
1959 The neatest solution I've come up with is to use -fno-defer-pop and a
1960 dummy function call between the threaded-code jump and the return:
1961
1962         register void (**tcip)() asm("%a5");
1963
1964         #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp
1965 %a0@");SBD();return;}while(0)
1966
1967         extern long a, b;
1968         void doit() {
1969                 foo(a); bar(b); JUMPNEXT;
1970         }
1971 with -O -fno-defer-pop gcc generates:
1972
1973         link %a6,#0
1974         movel a,%sp@-
1975         jbsr foo
1976         addqw #4,%sp
1977         movel b,%sp@-
1978         jbsr bar
1979         addqw #4,%sp
1980 #APP
1981         movl %a5@+,%a0; jmp %a0@
1982 #NO_APP
1983         jbsr SBD
1984         unlk %a6
1985         rts
1986
1987 Now bar's argument pop is not folded because its no longer the last
1988 call in the routine, SBD is.
1989 So the call to SBD
1990         a) prevents gcc's 'last call argument pop fold into unlk' optimization
1991            which prevents uncontrolled stack growth.
1992         b) doesn't get executed because of the jump
1993         c) is trivial to remove from the assembler with a sed-script
1994
1995
1996 One an try to use -fcaller-saves, but this surrounds calls with unnecessary
1997 register saves & restores that for the code to be optimal have to be
1998 edited out.
1999
2000 >4. Why does JUMPNEXT have a loop?  Surely the jump leaves the loop right
2001 >away.  Presumably you are tricking the compiler somehow.
2002
2003 This is old C lore.  The problem is
2004         'How do you write a macro that is a sequence of statements
2005          that can be used wherever a single statement can?'
2006
2007 take the following definition of JUMPNEXT:
2008 #define JUMPNEXT asm("movl %a5@+,%a0; jmp %a0@");return;
2009
2010 Now invoke it here:
2011         if (its_time_to_jump)
2012                 JUMPNEXT;
2013         do_something_else();
2014
2015 This expands to:
2016         if (its_time_to_jump)
2017                 asm("movl %a5@+,%a0; jmp %a0@");
2018         return;
2019         do_something_else();
2020
2021 Not at all whats intended!
2022
2023 There are two tricks I know of (the first I saw in Berkely Smalltalk,
2024 the second in Richard Stallman's gcc manual. I expect they're both
2025 quite old).
2026 The first is to surround your statements with
2027 if (TRUE){statements}else
2028 i.e.
2029 #define JUMPNEXT if(1){asm("movl %a5@+,%a0; jmp %a0@");return;}else
2030 So now we get:
2031         if (its_time_to_jump)
2032                 if (1){
2033                         asm("movl %a5@+,%a0; jmp %a0@");
2034                         return;
2035                 else;
2036         do_something_else();
2037
2038 which works because C binds elses innermost first. However, some
2039 compilers will whine about dangling elses.  The second scheme is
2040 more elegant (-:
2041
2042 Surround your statements with
2043 do{statements}while(FALSE);
2044 which will execute statements precisely once (its NOT a loop).
2045 i.e.
2046 #define JUMPNEXT do{asm("movl %a5@+,%a0; jmp %a0@");SBD();return;}while(0)
2047 expands to
2048
2049         if (its_time_to_jump)
2050                 do {
2051                         asm("movl %a5@+,%a0; jmp %a0@");
2052                         return;
2053                 while(0);
2054         do_something_else();
2055
2056 which does what's wanted and doesn't incur compiler whines.
2057
2058
2059 >Thanks
2060 >
2061 >Simon L Peyton Jones, Glasgow University
2062
2063 More and more people are taking the 'use C as an assembler' route, and
2064 more and more people are using GCC to do it (because its code quality is
2065 good, it had global register variables, and it has an excellent asm
2066 facility).  The threaded-code in C idea is also becomming more popular.
2067 But as the code above demonstrates, one does have to side-step
2068 optimizations and develop system-specific assembler editing scripts.
2069
2070 I'd like to ask Richard Stallman & the GCC development team for
2071         -fno-prolog -fno-epilog
2072 flags that instruct gcc to generate
2073         a) no register saves or restores
2074         b) no automatic variable allocation
2075         c) no procedure linkage/frame creation
2076
2077 Then the optimal 'Threaded-Code Machine in GCC C' can be compiled without
2078 any assembler editing scripts at all.
2079
2080 Also nice would be a way of telling GCC that an asm statement
2081 changed the flow of control so GCC could
2082         a) warn about not-reached code
2083         b) eliminate unnecessary code (do more code folding)
2084 -- 
2085 Eliot Miranda                   email:  eliot@cs.qmw.ac.uk
2086 Dept of Computer Science        Tel:    071 975 5229 (+44 71 975 5229)
2087 Queen Mary Westfield College    ARPA:   eliot%cs.qmw.ac.uk@nsf.ac.uk    
2088 Mile End Road                   UUCP:   eliot@qmw-cs.uucp
2089 LONDON E1 4NS
2090 -- 
2091 Send compilers articles to compilers@iecc.cambridge.ma.us or
2092 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
2093
2094 >From brennan@bcsaic.boeing.com Sat May  4 11:28:41 1991
2095 From: brennan@bcsaic.boeing.com (Michael D Brennan)
2096 Newsgroups: comp.compilers
2097 Subject: re: Threaded code
2098 Keywords: interpreter, design
2099 Date: 2 May 91 19:50:23 GMT
2100 Reply-To: brennan@bcsaic.boeing.com (Michael D Brennan)
2101 Organization: Boeing Aerospace & Electronics, Seattle WA
2102
2103 Another method for obtaining threaded byte code for an interpreter
2104 is to edit the assembler output of a big switch
2105 rather than editing the prologue and epilogue off functions calls.
2106
2107 You don't need gcc, global vars in registers, works with smart and
2108 dumb compilers, and all optimization can be turned on.
2109
2110 For example:
2111
2112 This C routine executes (unthreaded) byte code for an interpreter 
2113 that can add, subtract and print.
2114
2115 #define  HALT     0
2116 #define  PUSH     1
2117 #define  ADD      2
2118 #define  SUB      3
2119 #define  PRINT    4
2120
2121 static int stack[32] ;
2122
2123 void  execute(code_ptr)
2124   register int *code_ptr ;
2125 {
2126   register int *stack_ptr = stack - 1 ;
2127
2128
2129   while ( 1 )
2130   {
2131     switch( *code_ptr++ )
2132     {
2133       case HALT   :  return ;
2134       case PUSH   :
2135             * ++ stack_ptr = *code_ptr++ ;
2136             break ;
2137
2138       case  ADD   :
2139             stack_ptr-- ;
2140             *stack_ptr += stack_ptr[1] ;
2141             break ;
2142
2143       case  SUB   :
2144             stack_ptr-- ;
2145             *stack_ptr -= stack_ptr[1] ;
2146             break ;
2147
2148       case   PRINT  :
2149              printf("%d\n", *stack_ptr--);
2150              break ;
2151      }
2152   }
2153 }
2154
2155 -------------------------------------------------------
2156
2157 to interpret 2 + (3 - 4)
2158
2159 the front end "compiles" in  int code[]
2160
2161 PUSH, 2, PUSH, 3, PUSH, 4, SUB, ADD, PRINT, HALT
2162
2163 and calls  execute(code).
2164
2165 ------------------------------------------------------
2166
2167 The difference between this and the threaded code discussed over
2168 the last few weeks is the switch gets compiled as
2169
2170       jmp       TABLE[ *code_ptr++ ]
2171
2172 where TABLE is the jump table generated by the compiler which holds
2173 the addresses of the case labels.
2174
2175 With threading, the transitions between functions become
2176
2177       jmp       *code_ptr++
2178
2179
2180 but this is easy to get by editing the assembler output to
2181 export the case label and recode the switch.
2182
2183 --------------------------------------------------
2184
2185 For example on a SPARC:
2186
2187 code_ptr is %o0
2188 stack_ptr is %i5
2189
2190
2191         .....
2192                               ! case PUSH
2193 L77004:
2194         ld      [%i0],%o1
2195         inc     4,%i5
2196         inc     4,%i0
2197         b       L77008
2198         st      %o1,[%i5]
2199
2200         .....
2201
2202                              !  the switch, doesn't change structure
2203                              !  as you add new op codes
2204
2205 L77008:
2206         mov     %i0,%i4
2207         ld      [%i4],%i4
2208         inc     4,%i0
2209         cmp     %i4,4
2210         bgu     L77008
2211         sll     %i4,2,%i4
2212         sethi   %hi(L2000000),%o1
2213         or      %o1,%lo(L2000000),%o1   ! [internal]
2214         ld      [%i4+%o1],%o0
2215         jmp     %o0
2216         nop
2217 L2000000:                 !  the jump TABLE
2218         .word   L77003    !  HALT  etc
2219         .word   L77004
2220         .word   L77005
2221         .word   L77006
2222         .word   L77007
2223
2224
2225 -------------------------------------------
2226 modify by adding global labels and edit the switch
2227         
2228
2229
2230         .....
2231                               ! case PUSH
2232 _push :
2233 L77004:
2234         ld      [%i0],%o1
2235         inc     4,%i5
2236         inc     4,%i0
2237         b       L77008
2238         st      %o1,[%i5]
2239
2240         .....
2241
2242                           !  the edited switch
2243 L77008:
2244         mov     %i0,%i4
2245         ld      [%i4],%i4
2246         inc     4,%i0
2247         jmp     %i4
2248         nop
2249                           !  remove TABLE
2250
2251 -------------------------------------------
2252
2253 For another example on an Intel 8088 
2254
2255 stack_ptr is si
2256 code_ptr  is di
2257
2258    ;      while ( 1 )
2259    ;      {
2260    ;        switch( *code_ptr++ )
2261    ;    
2262 @1@50:
2263         mov     bx,di
2264         inc     di
2265         inc     di
2266         mov     bx,word ptr [bx]
2267         cmp     bx,3
2268         ja      short @1@50
2269         shl     bx,1
2270         jmp     word ptr cs:@1@C738[bx]
2271
2272
2273 @1@122:
2274    ;    
2275    ;          case PUSH   :
2276    ;                * ++ stack_ptr = *code_ptr++ ;
2277    ;    
2278         inc     si
2279         inc     si
2280         mov     ax,word ptr [di]
2281         mov     word ptr [si],ax
2282         inc     di
2283         inc     di
2284    ;    
2285    ;                break ;
2286    ;    
2287         jmp     short @1@50   
2288    ;    
2289
2290         ....
2291
2292 @1@C738 label   word       ;  jump TABLE
2293         dw      @1@194     ;  HALT
2294         dw      @1@122     ;  PUSH   etc
2295         dw      @1@146
2296
2297         ....
2298
2299 ------------------------------------------------
2300
2301 edited the jump can be computed inline
2302
2303    ;      while ( 1 )
2304    ;      {
2305    ;        switch( *code_ptr++ )
2306    ;    
2307 @1@50:      ; switch code is replaced by code only executed once
2308    
2309         inc     di
2310         inc     di
2311         jmp     [di-2]
2312
2313         .....
2314
2315 _push :
2316 @1@122:
2317    ;    
2318    ;          case PUSH   :
2319    ;                * ++ stack_ptr = *code_ptr++ ;
2320    ;    
2321         inc     si
2322         inc     si
2323         mov     ax,word ptr [di]
2324         mov     word ptr [si],ax
2325         inc     di
2326         inc     di
2327    ;    
2328    ;                break ;
2329    ;         
2330         inc     di         ;  jmp  to *code_ptr++  inline
2331         inc     di
2332         jmp     [di-2]
2333    ;    
2334         ....
2335
2336 ----------------------------------------------
2337
2338 the "front end" has defines
2339
2340 typedef  void (*TCODE)() ;
2341
2342 extern  void  halt(), push(), add(), sub(), print() ;
2343
2344 TCODE  code[CODESIZE] ;
2345
2346 in the array code[], the front end compiles
2347
2348
2349 push, 2, push, 3, push, 4, sub, add, print, halt
2350
2351 and calls execute(code).
2352
2353
2354 --
2355 Mike Brennan
2356 brennan@bcsaic.boeing.com
2357 -- 
2358 Send compilers articles to compilers@iecc.cambridge.ma.us or
2359 {ima | spdcc | world}!iecc!compilers.  Meta-mail to compilers-request.
2360
2361