home *** CD-ROM | disk | FTP | other *** search
/ Executor 2.0 / executorv2.0.iso / pc / dos / extra / docs / maillist / text / archive.94 / text0227.txt < prev    next >
Encoding:
Internet Message Format  |  1996-03-31  |  17.0 KB

  1. Received: from sloth.swcp.com (sloth.swcp.com [198.59.115.25]) by nacm.com (8.6.9/8.6.9) with ESMTP id TAA24752 for <executor@nacm.com>; Wed, 2 Nov 1994 19:22:16 -0800
  2. Received: from iclone.UUCP (uucp@localhost) by sloth.swcp.com (8.6.9/8.6.9) with UUCP id UAA08918 for nacm.com!executor; Wed, 2 Nov 1994 20:23:45 -0700
  3. Received: by iclone (NX5.67d/NX3.0M)
  4.     id AA05898; Wed, 2 Nov 94 20:17:26 -0700
  5. Date: Wed, 2 Nov 94 20:17:26 -0700
  6. From: "Clifford T. Matthews" <iclone!ctm@sloth.swcp.com>
  7. Message-Id: <9411030317.AA05898@iclone>
  8. Received: by NeXT.Mailer (1.100)
  9. Received: by NeXT Mailer (1.100)
  10. To: executor@nacm.com
  11. Subject: Synthetic CPU paper (technical -- boring)
  12. Sender: Executor-Owner@nacm.com
  13. Precedence: bulk
  14.  
  15. Hi Folks,
  16.  
  17. Here's the paper we presented to the Apple MAE group on Monday.  Some  
  18. of you may find this quite interesting, most of you will find it  
  19. impenetrable and hence boring.
  20.  
  21.     --Cliff
  22.     ctm@ardi.com
  23.  
  24.  
  25.             Syn68k:  ARDI's dynamically compiling 68LC040 emulator
  26.  
  27.  
  28. I.  Overview
  29.  
  30. This document is meant to give a concise technical summary of how
  31. syn68k works.
  32.  
  33. "Syn68k", ARDI's 68LC040 emulator, is both highly portable and
  34. fast.  The portable core of syn68k, which works by dynamically
  35. compiling 680x0 code into an efficient interpreted form, was designed
  36. to run on all major CPU's.  On supported architectures, syn68k can
  37. also translate 680x0 code into native code that the host processor
  38. can run directly.
  39.  
  40.  
  41. II.  Syngen
  42.  
  43. ARDI's "syngen" system analyzes a lisp-like file describing the
  44. bit patterns and semantics of the 680x0 instruction set and produces
  45. lookup tables and C code for the runtime system to use.  This
  46. process takes place only when syn68k is built, so we can afford
  47. extensive analysis here.  The code and tables generated by syngen
  48. depend somewhat on the characteristics of the host processor; for
  49. example, on a little endian machine it is advantageous to byte swap
  50. some extracted 680x0 operands at compile time instead of at runtime.
  51.  
  52. The 680x0 description file can describe multiple ways to emulate
  53. any particular 680x0 opcode.  The runtime system looks at what CC
  54. bits are live after the instruction and chooses the fastest variant
  55. it can legally use.  In the following example, we have two CC
  56. variants of lsrw; one computes no CC bits, and the other computes
  57. all of them:
  58.  
  59.  
  60. (defopcode lsrw_ea
  61.   (list 68000 amode_alterable_memory () (list "1110001011mmmmmm"))
  62.   (list "-----" "-----" dont_expand
  63.     (assign $1.muw (>> $1.muw 1)))
  64.   (list "CN0XZ" "-----" dont_expand
  65.     (list
  66.      (assign ccx (assign ccc (& $1.muw 1)))
  67.      (ASSIGN_NNZ_WORD (assign $1.muw (>> $1.muw 1))))))
  68.  
  69.  
  70. The 680x0 description file can also specify which 680x0 operands
  71. should be "expanded" to become implicitly known by the corresponding
  72. synthetic opcode.  For example, fully expanding out "addl dx,dy"
  73. would result in 64 synthetic opcodes, one for each combination of
  74. data register operands.  This results in smaller and faster synthetic
  75. opcodes at the expense of increasing the total number of synthetic
  76. opcodes.  To conserve space, we only expand out common 680x0 opcodes.
  77. On host architectures where we can compile to native code, we don't
  78. waste space by "expanding out" common synthetic opcodes.
  79.  
  80.  
  81. III.  Test suites
  82.  
  83. ARDI has a large set of test suites that try thousands upon thousands
  84. of variations of 680x0 opcodes and compare the results to those
  85. generated by a real 68040.  These test suites have proven to be an
  86. invaluable debugging tool, both as new features are added and as
  87. we have ported syn68k to other architectures (notably 80x86, 680x0,
  88. i860 and Alpha).  Our native code support is so recent that our
  89. test suites do not yet adequately test all of the situations that
  90. arise when generating native code, but we plan to extend them in
  91. the near future.
  92.  
  93.  
  94. IV.  Interpreted code
  95.  
  96. Our interpreted code consists of contiguous sequences of "synthetic
  97. opcodes" and their operands.  Syngen can generate ANSI C, but when
  98. compiled with gcc it uses C language extensions that make synthetic
  99. opcodes pointers to the C code responsible for interpreting that
  100. opcode.  This "threaded interpreting" entirely eliminates switch
  101. dispatch and loop overhead.
  102.  
  103. To illustrate the above points, here is the assembly language
  104. generated for the synthetic opcode that would handle a fully expanded
  105. "addl d0,d1" when no CC bit values are required.  This is what
  106. gcc's 80x86 output looks like (edited for readability) after we
  107. run our interpreter through ARDI's Pentium-specific instruction
  108. scheduling Perl script:
  109.  
  110.     movl    _d0,%eax    ; fetch d0
  111.     movl    (%esi),%edi    ; fetch next synthetic opcode
  112.     addl    %eax,_d1    ; do the add
  113.     addl    $4,%esi        ; increment synthetic PC
  114.     jmp    *%edi        ; jump to next synthetic opcode  
  115. handler
  116.  
  117. We must emphasize that the preceding example is not native code
  118. generated by our emulator, but merely a snippet of what gcc generates
  119. for our interpreter.  This gives you some idea of the efficiency
  120. of the portable component of our emulator.
  121.  
  122.  
  123.  V.  Native code
  124.  
  125. Syn68k supports optional architecture-specific native code  
  126. extensions.
  127. On systems where they are present, the runtime system tries to
  128. generate native code whenever possible.  In those rare cases when
  129. it cannot, it reverts to our interpreted code.  Since syn68k supports
  130. both native and synthetic code, the runtime system automatically
  131. inserts gateways between the two whenever there is a transition.
  132. This approach allows us to gradually phase in native code handlers
  133. for most 680x0 instructions while leaving tricky and unimportant
  134. rare cases alone.
  135.  
  136. Although our native code compilation engine is not  
  137. architecture-specific,
  138. to date we have only implemented an 80x86 back end.  The 80x86
  139. architecture has achieved such important status in the industry
  140. that it makes sense for us to describe how we generate native code
  141. for it, even though many of these techniques would not be necessary
  142. on RISC architectures.
  143.  
  144. We are glad that we implemented the most difficult back end first.
  145. We believe that, were we to have started with a RISC back end, we
  146. would have quite possibly architected a system where retrofitting
  147. the exotic mechanisms necessary for efficient 80x86 support was
  148. difficult.
  149.  
  150. Three major problems make translating 680x0 code to 80x86 code  
  151. difficult:
  152.  
  153. 1) The 80x86 has only 8 registers, while the 680x0 has 16.
  154. 2) The 80x86 is little endian, while the 680x0 is big endian.
  155. 3) The 80x86 does not have general-purpose postincrement and  
  156. predecrement
  157.    operators, which are used frequently in 680x0 code.
  158.  
  159. On the other hand, several factors make the job easier:
  160.  
  161. 1) The 80x86 has all of the CISC addressing modes commonly used in  
  162. 680x0 code.
  163. 2) The 80x86 has CC bits that map directly to their 680x0  
  164. counterparts
  165.    (except for the 680x0's X bit).
  166. 3) The 80x86 supports 8-, 16- and 32-bit operations, (although it can  
  167. only
  168.    support 8 bit operations on four of its registers).
  169. 4) The 80x86 and 680x0 have analagous conditional branch  
  170. instructions.
  171. 5) The 80x86 allows unaligned memory accesses without substantial  
  172. overhead.
  173.  
  174. The toughest problem is the lack of registers.  On 32-register RISC
  175. architectures it's easy to allocate one RISC register for each
  176. 680x0 register, but on the 80x86 a different approach is needed.
  177. The obvious solution is to perform full-blown inter-block register
  178. allocation, but we fear that using traditional compiler techniques
  179. would be unacceptably slow.
  180.  
  181. For now, we have adopted a simple constraint: between basic blocks,
  182. all registers and live CC bits must reside in their canonical home
  183. in memory.  Within a block, anything goes.  So what liberties does
  184. syn68k take within a block?
  185.  
  186. The 80x86 register set is treated as a cache for recently used
  187. 680x0 registers, and the 80x86 CC bits are used as a cache for the
  188. 680x0 CC bits.  At any particular point within a block, each 680x0
  189. register is either sitting in its memory home or is cached in an
  190. 80x86 register, and each live 680x0 CC bit is either cached in its
  191. 80x86 equivalent or stored in its memory home.  Cached registers
  192. may be in canonical form, may be byte swapped, may have only their
  193. low two bytes swapped, or may be offset by a known constant from
  194. their actual value.
  195.  
  196. Each 680x0 instruction can require that 680x0 registers be cached
  197. in particular ways; the compilation engine generates the minimal
  198. code needed to satisfy those constraints and then calls a sequence
  199. of routines to generate the native code.  As each 680x0 instruction
  200. is processed, each 680x0 register's cache status is updated.  Dirty
  201. registers are canonicalized and spilled back to memory at the end
  202. of each block (or when we run out of 80x86 registers and we need
  203. to make room).
  204.  
  205. We allow 680x0 registers to be cached with varying byte orders and
  206. offsets so that we can perform the optimizations of lazy byte
  207. swapping and lazy constant offsetting.  If the 680x0 program loads
  208. a register from memory and then ends up writing it out later, we
  209. avoid unnecessary byte swaps by not canonicalizing the value
  210. immediately.  Lazy constant offsetting mitigates the overhead of
  211. postincrement and predecrement side effects.  For example, this
  212. 680x0 code:
  213.  
  214.     pea        0x1
  215.     pea        0x2
  216.     pea        0x3
  217.     pea        0x4
  218.     ...
  219.  
  220. becomes this 80x86 code:
  221.  
  222.     movl    _a7,%edi
  223.     movl    $0x01000000,-4(%edi)    ; "push" big-endian constant
  224.     movl    $0x02000000,-8(%edi)
  225.     movl    $0x03000000,-12(%edi)
  226.     movl    $0x04000000,-16(%edi)
  227.     ... <more uses of a7 may follow, and they'll use %edi>
  228.     subl    $16,%edi
  229.     movl    $edi,_a7
  230.     ...
  231.  
  232.  
  233. As mentioned above, we use the 80x86 condition code bits as a cache
  234. for the real 680x0 CC bits.  Although live cached CC bits are
  235. occasionally spilled back to memory because some 80x86 instruction
  236. is about to clobber them, this trick almost always works.  Using
  237. 80x86 CC bits, we can frequently get away with extremely concise
  238. code sequences; for example, a 680x0 compare and conditional branch
  239. becomes an 80x86 compare and conditional branch.
  240.  
  241.  
  242. VI.  Self-modifying code
  243.  
  244. Like most dynamically compiling emulators, syn68k doesn't detect
  245. self-modifying code; the overhead is too high.  Fortunately,
  246. self-modifying programs don't work on the real 68040 either.  We
  247. rely on the program making explicit system calls to flush the caches
  248. whenever 680x0 code may have been modified or created.  Some programs
  249. (like HyperCard) flush the caches very often, which can cause real
  250. performance headaches if code is continuously recompiled.  We have
  251. solved this problem by checksumming 680x0 blocks as they are compiled
  252. and only decompiling blocks which fail their checksums.  This
  253. optimization alone sped up some HyperCard stacks by a factor of
  254. three or so.
  255.  
  256.  
  257.  
  258. VII.  Responsiveness
  259.  
  260. Responsiveness is a concern for any dynamic compiler.  Fortunately,
  261. syn68k is largely driven by automatically generated lookup tables
  262. so compilation speed is good.  Like other dynamic compilers, syn68k
  263. only bothers to compile 680x0 code when it encounters it for the
  264. first time.
  265.  
  266. When syn68k encounters new code, it compiles other 680x0 code that
  267. it can reach from there but does not compile through jsr's.  Only
  268. when a jsr is actually executed does syn68k compile the target
  269. routine.  Once that target routine is compiled, syn68k modifies
  270. the jsr handler to point directly to the target routine so that
  271. the jsr will be extremely fast the second time it is executed.
  272. We've found that lazily compiling through jsr's does a good job of
  273. avoiding compilation lag that might annoy the user.
  274.  
  275. Syn68k does not attempt to generate native code for a basic block
  276. until that block (or a nearby one) has been executed 50 times.
  277. This saves memory and some compilation time, although we haven't
  278. noticed any particular sluggishness when compiling to native code.
  279.  
  280.  
  281. VIII.  Other optimizations
  282.  
  283. Syn68k maintains an internal "jsr stack" to speed up the common
  284. case of jsr/rts.  We realize that the rts address might have been
  285. fiddled with, so the rts handler verifies at runtime that the rts
  286. address matches the tag on top of the jsr stack.  If it matches,
  287. syn68k does a fast jump.  If it doesn't, syn68k looks up the code
  288. corresponding to the rts address in a hash table.
  289.  
  290.  
  291. IX.  Neat hacks
  292.  
  293. The low-level code generation routines for the 80x86 back end are
  294. machine generated from assembly language templates.  Thousands of
  295. operand permutations for 80x86 instructions of interest are run
  296. through the system's assembler and analyzed to derive the rules
  297. the assembler uses to create binaries.  Those rules are encapsulated
  298. into C code and compiled into syn68k so we can generate binaries
  299. on the fly.  Here is a sample template:
  300.  
  301.   { "i386_leal_indoff", "", "", "", "", "-",
  302.       "leal %0(%1),%2",
  303.       { "offset", "base", "dst" },
  304.       { { SIZE_32, CONSTANT, IN }, { SIZE_32, REGISTER, IN },
  305.         { SIZE_32, REGISTER, OUT } } },
  306.  
  307. This approach has saved us countless hours of debugging and allows
  308. our system to automatically perform the same optimizations as the
  309. host system's assembler.
  310.  
  311. We've annotated our 80x86 descriptions with information about
  312. Pentium pairability so that future versions of syn68k can schedule
  313. the native code we generate (we already schedule our main interpreter
  314. when we build syn68k).
  315.  
  316.  
  317. X.  Future optimizations
  318.  
  319. We are working on a simple inter-block register allocation algorithm.
  320.  
  321. By relocating most 680x0 register->80x86 register moves to the
  322. beginning of each block, we can improve Pentium pairability and
  323. reduce 80486 and Pentium address generation pipeline stalls.
  324.  
  325. Now that we compile to native code, A-line trap overhead is becoming
  326. significant.  We may soon compile A-line traps to native code that
  327. directly calls the appropriate ROMlib routine with the appropriate
  328. arguments (checking at runtime to make sure that neither the trap
  329. nor the A-line vector has been patched out, of course).
  330.  
  331.  
  332. XI.  Code examples
  333.  
  334. Here are two sample 680x0 code sequences from real applications,
  335. and the 80x86 code that syn68k generates for them.  We chose these
  336. code sequences specifically to showcase several of the techniques
  337. we use, so you shouldn't use them as a substitute for benchmarks.
  338. Not all 680x0 code translates as well as these examples do, but
  339. these examples are far from exotic.
  340.  
  341.  
  342. Example 1 (Solarian):
  343.  
  344. 680x0 code:
  345.  
  346.     addqb    #1,a4@(1)
  347.     movel    #0,d0
  348.     moveb    a4@,d0
  349.     swap    d0
  350.     clrw    d0
  351.     swap    d0
  352.     asll    #2,d0
  353.     lea    a5@(-13462),a0
  354.     addal    d0,a0
  355.     moveal    a0@,a0
  356.     movel    #0,d0
  357.     moveb    a4@(1),d0
  358.     cmpw    a0@,d0
  359.     bcs    0x3fffee2
  360.  
  361.  
  362. 80x86 code:
  363.  
  364.     movl    _a4,%edi        ; addqb #1,a4@(1)
  365.     addb    $0x1,0x1(%edi)
  366.     xorl    %ebx,%ebx        ; movel #0,d0
  367.     movb    (%edi),%bl        ; moveb a4@,d0
  368.     rorl    $0x10,%ebx        ; swap d0
  369.     xorw    %bx,%bx            ; clrw d0
  370.     rorl    $0x10,%ebx        ; swap d0
  371.     shll    $0x2,%ebx        ; asll #2,d0
  372.     movl    _a5,%esi        ; lea a5@(-13462),a0
  373.     leal    0xffffcb6a(%esi),%edx
  374.     addl    %ebx,%edx        ; addal d0,a0
  375.     movl    (%edx),%edx        ; moveal a0@,a0
  376.     xorl    %ebx,%ebx        ; movel #0,d0
  377.     movb    0x1(%edi),%bl        ; moveb a4@(1),d0
  378.     bswap    %edx            ; cmpw a0@,d0
  379.     movw    (%edx),%cx
  380.     rorw    $0x8,%cx
  381.     cmpw    %cx,%bx
  382.     movl    %edx,_a0        ; <spill dirty 68k
  383.     movl    %ebx,_d0        ;  registers back to memory>
  384.     jb    0x6fae0c        ; bcs 0x3fffee2
  385.     jmp    0x6faf0c        ; <go to "fall through" code>
  386.  
  387.  
  388.  
  389.  
  390. Example 2 (PageMaker):
  391.  
  392. 680x0 code:
  393.  
  394.     movel    #0,d2
  395.     moveb    d0,d2
  396.     lslw    #8,d0
  397.     orw    d0,d2
  398.     movel    d2,d0
  399.     swap    d2
  400.     orl    d2,d0
  401.     movel    a0,d2
  402.     lsrb    #1,d2
  403.     bcc    0x3fffed4
  404.  
  405. 80x86 code:
  406.  
  407.     xorl    %ebx,%ebx        ; movel #0,d2
  408.     movl    _d0,%edx        ; moveb d0,d2
  409.     movb    %dl,%bl
  410.     shlw    $0x8,%dx        ; lslw #8,d0
  411.     orw    %dx,%bx            ; orw d0,d2
  412.     movl    %ebx,%edx        ; movel d2,d0
  413.     rorl    $0x10,%ebx        ; swap d2
  414.     orl    %ebx,%edx        ; orl d2,d0
  415.     movl    _a0,%ecx        ; movel a0,d2
  416.     movl    %ecx,%ebx
  417.     shrb    %bl            ; lsrb #1,d2
  418.     movl    %ebx,_d2        ; <spill dirty 68k
  419.     movl    %edx,_d0        ;  registers back to memory>
  420.     jae    0x3b734c        ; bcc 0x3fffed4
  421.     jmp    0x43d48c        ; <go to "fall through" 68k  
  422. code>
  423.  
  424.  
  425. XII.  Benchmarks
  426.  
  427. These performance numbers were computed with Speedometer 3.23.
  428. We've removed the floating point tests from the list since they do
  429. not measure syn68k's speed.  Syn68k contains no special provisions
  430. for Speedometer's benchmarks and we believe that these numbers are
  431. indicative of syn68k's performance for many other CPU-intensive
  432. programs.
  433.  
  434.  
  435.         Quadra    Pentium    486DX4    486DX/2
  436.                   610     90MHz     75MHz     66MHz
  437.         ------    ------    ------    ------
  438. CPU        16.018    28.833    15.727    13.840
  439.  
  440. Dhrystones    19.586    21.886    12.084     9.424
  441. Tower        18.909    27.130    12.235    11.556
  442. Quicksort    17.759    27.105    15.606    13.919
  443. Bubble sort    18.409    31.154    19.286    16.875
  444. Queens        19.083    38.167    19.083    18.320
  445. Puzzle        22.083    44.167    23.661    21.032
  446. Permutations    21.019    28.564    11.604    12.242
  447. Int. Matrix    24.200    26.469    19.369    16.608
  448. Sieve        23.362    60.290    33.982    30.145
  449.         ------    ------    ------    ------
  450. Average        20.490    33.881    18.582    16.680
  451.  
  452.  
  453.  
  454. Preliminary analysis suggests that we average a roughly 3:1
  455. instruction count increase when translating to 80x86 code.  We have
  456. not yet taken rigorous measurements, but the 3:1 figure is lent
  457. some credence by the fact that our 75MHz 486DX4 gets nearly the
  458. same performance as our Quadra 610.  We believe that inter-block
  459. register allocation will noticeably improve this ratio.
  460.  
  461.