1 ## Mu's instructions and their table-driven translation
3 See http://akkartik.name/akkartik-convivial-20200607.pdf for the complete
4 story. In brief: Mu is a statement-oriented language. Blocks consist of flat
5 lists of instructions. Instructions can have inputs after the operation, and
6 outputs to the left of a '<-'. Inputs and outputs must be variables. They can't
7 include nested expressions. Variables can be literals ('n'), or live in a
8 register ('var/reg') or in memory ('var') at some 'stack-offset' from the 'ebp'
9 register. Outputs must be registers. To modify a variable in memory, pass it in
10 by reference as an input. (Inputs are more precisely called 'inouts'.)
11 Conversely, registers that are just read from must not be passed as outputs.
13 The following chart shows all the instruction forms supported by Mu, along with
14 the SubX instruction they're translated to.
16 ## Integer instructions
18 These instructions use the general-purpose registers.
20 var/eax <- increment => "40/increment-eax"
21 var/ecx <- increment => "41/increment-ecx"
22 var/edx <- increment => "42/increment-edx"
23 var/ebx <- increment => "43/increment-ebx"
24 var/esi <- increment => "46/increment-esi"
25 var/edi <- increment => "47/increment-edi"
26 increment var => "ff 0/subop/increment *(ebp+" var.stack-offset ")"
27 increment *var/reg => "ff 0/subop/increment *" reg
29 var/eax <- decrement => "48/decrement-eax"
30 var/ecx <- decrement => "49/decrement-ecx"
31 var/edx <- decrement => "4a/decrement-edx"
32 var/ebx <- decrement => "4b/decrement-ebx"
33 var/esi <- decrement => "4e/decrement-esi"
34 var/edi <- decrement => "4f/decrement-edi"
35 decrement var => "ff 1/subop/decrement *(ebp+" var.stack-offset ")"
36 decrement *var/reg => "ff 1/subop/decrement *" reg
38 var/reg <- add var2/reg2 => "01/add-to %" reg " " reg2 "/r32"
39 var/reg <- add var2 => "03/add *(ebp+" var2.stack-offset ") " reg "/r32"
40 var/reg <- add *var2/reg2 => "03/add *" reg2 " " reg "/r32"
41 add-to var1, var2/reg => "01/add-to *(ebp+" var1.stack-offset ") " reg "/r32"
42 add-to *var1/reg1, var2/reg2 => "01/add-to *" reg1 " " reg2 "/r32"
43 var/eax <- add n => "05/add-to-eax " n "/imm32"
44 var/reg <- add n => "81 0/subop/add %" reg " " n "/imm32"
45 add-to var, n => "81 0/subop/add *(ebp+" var.stack-offset ") " n "/imm32"
46 add-to *var/reg, n => "81 0/subop/add *" reg " " n "/imm32"
48 var/reg <- subtract var2/reg2 => "29/subtract-from %" reg " " reg2 "/r32"
49 var/reg <- subtract var2 => "2b/subtract *(ebp+" var2.stack-offset ") " reg "/r32"
50 var/reg <- subtract *var2/reg2 => "2b/subtract *" reg2 " " reg1 "/r32"
51 subtract-from var1, var2/reg2 => "29/subtract-from *(ebp+" var1.stack-offset ") " reg2 "/r32"
52 subtract-from *var1/reg1, var2/reg2 => "29/subtract-from *" reg1 " " reg2 "/r32"
53 var/eax <- subtract n => "2d/subtract-from-eax " n "/imm32"
54 var/reg <- subtract n => "81 5/subop/subtract %" reg " " n "/imm32"
55 subtract-from var, n => "81 5/subop/subtract *(ebp+" var.stack-offset ") " n "/imm32"
56 subtract-from *var/reg, n => "81 5/subop/subtract *" reg " " n "/imm32"
58 var/reg <- and var2/reg2 => "21/and-with %" reg " " reg2 "/r32"
59 var/reg <- and var2 => "23/and *(ebp+" var2.stack-offset " " reg "/r32"
60 var/reg <- and *var2/reg2 => "23/and *" reg2 " " reg "/r32"
61 and-with var1, var2/reg => "21/and-with *(ebp+" var1.stack-offset ") " reg "/r32"
62 and-with *var1/reg1, var2/reg2 => "21/and-with *" reg1 " " reg2 "/r32"
63 var/eax <- and n => "25/and-with-eax " n "/imm32"
64 var/reg <- and n => "81 4/subop/and %" reg " " n "/imm32"
65 and-with var, n => "81 4/subop/and *(ebp+" var.stack-offset ") " n "/imm32"
66 and-with *var/reg, n => "81 4/subop/and *" reg " " n "/imm32"
68 var/reg <- or var2/reg2 => "09/or-with %" reg " " reg2 "/r32"
69 var/reg <- or var2 => "0b/or *(ebp+" var2.stack-offset ") " reg "/r32"
70 var/reg <- or *var2/reg2 => "0b/or *" reg2 " " reg "/r32"
71 or-with var1, var2/reg2 => "09/or-with *(ebp+" var1.stack-offset " " reg2 "/r32"
72 or-with *var1/reg1, var2/reg2 => "09/or-with *" reg1 " " reg2 "/r32"
73 var/eax <- or n => "0d/or-with-eax " n "/imm32"
74 var/reg <- or n => "81 1/subop/or %" reg " " n "/imm32"
75 or-with var, n => "81 1/subop/or *(ebp+" var.stack-offset ") " n "/imm32"
76 or-with *var/reg, n => "81 1/subop/or *" reg " " n "/imm32"
78 var/reg <- not => "f7 2/subop/not %" reg
79 not var => "f7 2/subop/not *(ebp+" var.stack-offset ")"
80 not *var/reg => "f7 2/subop/not *" reg
82 var/reg <- xor var2/reg2 => "31/xor-with %" reg " " reg2 "/r32"
83 var/reg <- xor var2 => "33/xor *(ebp+" var2.stack-offset ") " reg "/r32"
84 var/reg <- xor *var2/reg2 => "33/xor *" reg2 " " reg "/r32"
85 xor-with var1, var2/reg => "31/xor-with *(ebp+" var1.stack-offset ") " reg "/r32"
86 xor-with *var1/reg1, var2/reg2 => "31/xor-with *" reg1 " " reg2 "/r32"
87 var/eax <- xor n => "35/xor-with-eax " n "/imm32"
88 var/reg <- xor n => "81 6/subop/xor %" reg " " n "/imm32"
89 xor-with var, n => "81 6/subop/xor *(ebp+" var.stack-offset ") " n "/imm32"
90 xor-with *var/reg, n => "81 6/subop/xor *" reg " " n "/imm32"
92 var/reg <- negate => "f7 3/subop/negate %" reg
93 negate var => "f7 3/subop/negate *(ebp+" var.stack-offset ")"
94 negate *var/reg => "f7 3/subop/negate *" reg
96 var/reg <- shift-left n => "c1/shift 4/subop/left %" reg " " n "/imm32"
97 var/reg <- shift-right n => "c1/shift 5/subop/right %" reg " " n "/imm32"
98 var/reg <- shift-right-signed n => "c1/shift 7/subop/right-signed %" reg " " n "/imm32"
99 shift-left var, n => "c1/shift 4/subop/left *(ebp+" var.stack-offset ") " n "/imm32"
100 shift-left *var/reg, n => "c1/shift 4/subop/left *" reg " " n "/imm32"
101 shift-right var, n => "c1/shift 5/subop/right *(ebp+" var.stack-offset ") " n "/imm32"
102 shift-right *var/reg, n => "c1/shift 5/subop/right *" reg " " n "/imm32"
103 shift-right-signed var, n => "c1/shift 7/subop/right-signed *(ebp+" var.stack-offset ") " n "/imm32"
104 shift-right-signed *var/reg, n => "c1/shift 7/subop/right-signed *" reg " " n "/imm32"
106 var/eax <- copy n => "b8/copy-to-eax " n "/imm32"
107 var/ecx <- copy n => "b9/copy-to-ecx " n "/imm32"
108 var/edx <- copy n => "ba/copy-to-edx " n "/imm32"
109 var/ebx <- copy n => "bb/copy-to-ebx " n "/imm32"
110 var/esi <- copy n => "be/copy-to-esi " n "/imm32"
111 var/edi <- copy n => "bf/copy-to-edi " n "/imm32"
112 var/reg <- copy var2/reg2 => "89/<- %" reg " " reg2 "/r32"
113 copy-to var1, var2/reg => "89/<- *(ebp+" var1.stack-offset ") " reg "/r32"
114 copy-to *var1/reg1, var2/reg2 => "89/<- *" reg1 " " reg2 "/r32"
115 var/reg <- copy var2 => "8b/-> *(ebp+" var2.stack-offset ") " reg "/r32"
116 var/reg <- copy *var2/reg2 => "8b/-> *" reg2 " " reg "/r32"
117 var/reg <- copy n => "c7 0/subop/copy %" reg " " n "/imm32"
118 copy-to var, n => "c7 0/subop/copy *(ebp+" var.stack-offset ") " n "/imm32"
119 copy-to *var/reg, n => "c7 0/subop/copy *" reg " " n "/imm32"
121 var/reg <- copy-byte var2/reg2 => "8a/byte-> %" reg2 " " reg "/r32"
122 "81 4/subop/and %" reg " 0xff/imm32"
123 var/reg <- copy-byte *var2/reg2 => "8a/byte-> *" reg2 " " reg "/r32"
124 "81 4/subop/and %" reg " 0xff/imm32"
125 copy-byte-to *var1/reg1, var2/reg2 => "88/byte<- *" reg1 " " reg2 "/r32"
127 compare var1, var2/reg2 => "39/compare *(ebp+" var1.stack-offset ") " reg2 "/r32"
128 compare *var1/reg1, var2/reg2 => "39/compare *" reg1 " " reg2 "/r32"
129 compare var1/reg1, var2 => "3b/compare<- *(ebp+" var2.stack-offset ") " reg1 "/r32"
130 compare var/reg, *var2/reg2 => "3b/compare<- *" reg " " n "/imm32"
131 compare var/eax, n => "3d/compare-eax-with " n "/imm32"
132 compare var/reg, n => "81 7/subop/compare %" reg " " n "/imm32"
133 compare var, n => "81 7/subop/compare *(ebp+" var.stack-offset ") " n "/imm32"
134 compare *var/reg, n => "81 7/subop/compare *" reg " " n "/imm32"
136 var/reg <- multiply var2 => "0f af/multiply *(ebp+" var2.stack-offset ") " reg "/r32"
137 var/reg <- multiply var2/reg2 => "0f af/multiply %" reg2 " " reg "/r32"
138 var/reg <- multiply *var2/reg2 => "0f af/multiply *" reg2 " " reg "/r32"
140 ## Floating-point operations
142 These instructions operate on either floating-point registers (xreg) or
143 general-purpose registers (reg) in indirect mode.
145 var/xreg <- add var2/xreg2 => "f3 0f 58/add %" xreg2 " " xreg1 "/x32"
146 var/xreg <- add var2 => "f3 0f 58/add *(ebp+" var2.stack-offset ") " xreg "/x32"
147 var/xreg <- add *var2/reg2 => "f3 0f 58/add *" reg2 " " xreg "/x32"
149 var/xreg <- subtract var2/xreg2 => "f3 0f 5c/subtract %" xreg2 " " xreg1 "/x32"
150 var/xreg <- subtract var2 => "f3 0f 5c/subtract *(ebp+" var2.stack-offset ") " xreg "/x32"
151 var/xreg <- subtract *var2/reg2 => "f3 0f 5c/subtract *" reg2 " " xreg "/x32"
153 var/xreg <- multiply var2/xreg2 => "f3 0f 59/multiply %" xreg2 " " xreg1 "/x32"
154 var/xreg <- multiply var2 => "f3 0f 59/multiply *(ebp+" var2.stack-offset ") " xreg "/x32"
155 var/xreg <- multiply *var2/reg2 => "f3 0f 59/multiply *" reg2 " " xreg "/x32"
157 var/xreg <- divide var2/xreg2 => "f3 0f 5e/divide %" xreg2 " " xreg1 "/x32"
158 var/xreg <- divide var2 => "f3 0f 5e/divide *(ebp+" var2.stack-offset ") " xreg "/x32"
159 var/xreg <- divide *var2/reg2 => "f3 0f 5e/divide *" reg2 " " xreg "/x32"
161 There are also some exclusively floating-point instructions:
163 var/xreg <- reciprocal var2/xreg2 => "f3 0f 53/reciprocal %" xreg2 " " xreg1 "/x32"
164 var/xreg <- reciprocal var2 => "f3 0f 53/reciprocal *(ebp+" var2.stack-offset ") " xreg "/x32"
165 var/xreg <- reciprocal *var2/reg2 => "f3 0f 53/reciprocal *" reg2 " " xreg "/x32"
167 var/xreg <- square-root var2/xreg2 => "f3 0f 51/square-root %" xreg2 " " xreg1 "/x32"
168 var/xreg <- square-root var2 => "f3 0f 51/square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
169 var/xreg <- square-root *var2/reg2 => "f3 0f 51/square-root *" reg2 " " xreg "/x32"
171 var/xreg <- inverse-square-root var2/xreg2 => "f3 0f 52/inverse-square-root %" xreg2 " " xreg1 "/x32"
172 var/xreg <- inverse-square-root var2 => "f3 0f 52/inverse-square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
173 var/xreg <- inverse-square-root *var2/reg2 => "f3 0f 52/inverse-square-root *" reg2 " " xreg "/x32"
175 var/xreg <- min var2/xreg2 => "f3 0f 5d/min %" xreg2 " " xreg1 "/x32"
176 var/xreg <- min var2 => "f3 0f 5d/min *(ebp+" var2.stack-offset ") " xreg "/x32"
177 var/xreg <- min *var2/reg2 => "f3 0f 5d/min *" reg2 " " xreg "/x32"
179 var/xreg <- max var2/xreg2 => "f3 0f 5f/max %" xreg2 " " xreg1 "/x32"
180 var/xreg <- max var2 => "f3 0f 5f/max *(ebp+" var2.stack-offset ") " xreg "/x32"
181 var/xreg <- max *var2/reg2 => "f3 0f 5f/max *" reg2 " " xreg "/x32"
183 Remember, when these instructions use indirect mode, they still use an integer
184 register. Floating-point registers can't hold addresses.
186 Most instructions operate exclusively on integer or floating-point operands.
187 The only exceptions are the instructions for converting between integers and
188 floating-point numbers.
190 var/xreg <- convert var2/reg2 => "f3 0f 2a/convert-to-float %" reg2 " " xreg "/x32"
191 var/xreg <- convert var2 => "f3 0f 2a/convert-to-float *(ebp+" var2.stack-offset ") " xreg "/x32"
192 var/xreg <- convert *var2/reg2 => "f3 0f 2a/convert-to-float *" reg2 " " xreg "/x32"
194 Converting floats to ints performs rounding by default. (We don't mess with the
195 MXCSR control register.)
197 var/reg <- convert var2/xreg2 => "f3 0f 2d/convert-to-int %" xreg2 " " reg "/r32"
198 var/reg <- convert var2 => "f3 0f 2d/convert-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
199 var/reg <- convert *var2/reg2 => "f3 0f 2d/convert-to-int *" reg2 " " reg "/r32"
201 There's a separate instruction for truncating the fractional part.
203 var/reg <- truncate var2/xreg2 => "f3 0f 2c/truncate-to-int %" xreg2 " " reg "/r32"
204 var/reg <- truncate var2 => "f3 0f 2c/truncate-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
205 var/reg <- truncate *var2/reg2 => "f3 0f 2c/truncate-to-int *" reg2 " " reg "/r32"
207 There are no instructions accepting floating-point literals. To obtain integer
208 literals in floating-point registers, copy them to general-purpose registers
209 and then convert them to floating-point.
211 One pattern you may have noticed above is that the floating-point instructions
212 above always write to registers. The only exceptions are `copy` instructions,
213 which can write to memory locations.
215 var/xreg <- copy var2/xreg2 => "f3 0f 11/<- %" xreg " " xreg2 "/x32"
216 copy-to var1, var2/xreg => "f3 0f 11/<- *(ebp+" var1.stack-offset ") " xreg "/x32"
217 var/xreg <- copy var2 => "f3 0f 10/-> *(ebp+" var2.stack-offset ") " xreg "/x32"
218 var/xreg <- copy *var2/reg2 => "f3 0f 10/-> *" reg2 " " xreg "/x32"
220 Comparisons must always start with a register:
222 compare var1/xreg1, var2/xreg2 => "0f 2f/compare %" xreg2 " " xreg1 "/x32"
223 compare var1/xreg1, var2 => "0f 2f/compare *(ebp+" var2.stack-offset ") " xreg1 "/x32"
227 In themselves, blocks generate no instructions. However, if a block contains
228 variable declarations, they must be cleaned up when the block ends.
230 Clean up var on the stack => "81 0/subop/add %esp " size-of(var) "/imm32"
231 Clean up var/reg => "8f 0/subop/pop %" reg
233 Clean up var/xreg => "f3 0f 10/-> *esp " xreg "/x32"
234 "81 0/subop/add %esp 4/imm32"
238 Besides having to clean up any variable declarations (see above) between
239 themselves and their target, jumps translate like this:
241 break => "e9/jump break/disp32"
242 break label => "e9/jump " label ":break/disp32"
243 loop => "e9/jump loop/disp32"
244 loop label => "e9/jump " label ":loop/disp32"
246 break-if-= => "0f 84/jump-if-= break/disp32"
247 break-if-= label => "0f 84/jump-if-= " label ":break/disp32"
248 loop-if-= => "0f 84/jump-if-= loop/disp32"
249 loop-if-= label => "0f 84/jump-if-= " label ":loop/disp32"
251 break-if-!= => "0f 85/jump-if-!= break/disp32"
252 break-if-!= label => "0f 85/jump-if-!= " label ":break/disp32"
253 loop-if-!= => "0f 85/jump-if-!= loop/disp32"
254 loop-if-!= label => "0f 85/jump-if-!= " label ":loop/disp32"
256 break-if-< => "0f 8c/jump-if-< break/disp32"
257 break-if-< label => "0f 8c/jump-if-< " label ":break/disp32"
258 loop-if-< => "0f 8c/jump-if-< loop/disp32"
259 loop-if-< label => "0f 8c/jump-if-< " label ":loop/disp32"
261 break-if-> => "0f 8f/jump-if-> break/disp32"
262 break-if-> label => "0f 8f/jump-if-> " label ":break/disp32"
263 loop-if-> => "0f 8f/jump-if-> loop/disp32"
264 loop-if-> label => "0f 8f/jump-if-> " label ":loop/disp32"
266 break-if-<= => "0f 8e/jump-if-<= break/disp32"
267 break-if-<= label => "0f 8e/jump-if-<= " label ":break/disp32"
268 loop-if-<= => "0f 8e/jump-if-<= loop/disp32"
269 loop-if-<= label => "0f 8e/jump-if-<= " label ":loop/disp32"
271 break-if->= => "0f 8d/jump-if->= break/disp32"
272 break-if->= label => "0f 8d/jump-if->= " label ":break/disp32"
273 loop-if->= => "0f 8d/jump-if->= loop/disp32"
274 loop-if->= label => "0f 8d/jump-if->= " label ":loop/disp32"
276 break-if-addr< => "0f 82/jump-if-addr< break/disp32"
277 break-if-addr< label => "0f 82/jump-if-addr< " label ":break/disp32"
278 loop-if-addr< => "0f 82/jump-if-addr< loop/disp32"
279 loop-if-addr< label => "0f 82/jump-if-addr< " label ":loop/disp32"
281 break-if-addr> => "0f 87/jump-if-addr> break/disp32"
282 break-if-addr> label => "0f 87/jump-if-addr> " label ":break/disp32"
283 loop-if-addr> => "0f 87/jump-if-addr> loop/disp32"
284 loop-if-addr> label => "0f 87/jump-if-addr> " label ":loop/disp32"
286 break-if-addr<= => "0f 86/jump-if-addr<= break/disp32"
287 break-if-addr<= label => "0f 86/jump-if-addr<= " label ":break/disp32"
288 loop-if-addr<= => "0f 86/jump-if-addr<= loop/disp32"
289 loop-if-addr<= label => "0f 86/jump-if-addr<= " label ":loop/disp32"
291 break-if-addr>= => "0f 83/jump-if-addr>= break/disp32"
292 break-if-addr>= label => "0f 83/jump-if-addr>= " label ":break/disp32"
293 loop-if-addr>= => "0f 83/jump-if-addr>= loop/disp32"
294 loop-if-addr>= label => "0f 83/jump-if-addr>= " label ":loop/disp32"
296 Similar float variants like `break-if-float<` are aliases for the corresponding
297 `addr` equivalents. The x86 instruction set stupidly has floating-point
298 operations only update a subset of flags.
300 Four sets of conditional jumps are useful for detecting overflow.
302 break-if-carry => "0f 82/jump-if-carry break/disp32"
303 break-if-carry label => "0f 82/jump-if-carry " label "/disp32"
304 loop-if-carry => "0f 82/jump-if-carry break/disp32"
305 loop-if-carry label => "0f 82/jump-if-carry " label "/disp32"
307 break-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
308 break-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
309 loop-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
310 loop-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
312 break-if-overflow => "0f 80/jump-if-overflow break/disp32"
313 break-if-overflow label => "0f 80/jump-if-overflow " label ":break/disp32"
314 loop-if-overflow => "0f 80/jump-if-overflow loop/disp32"
315 loop-if-overflow label => "0f 80/jump-if-overflow " label ":loop/disp32"
317 break-if-not-overflow => "0f 81/jump-if-not-overflow break/disp32"
318 break-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":break/disp32"
319 loop-if-not-overflow => "0f 81/jump-if-not-overflow loop/disp32"
320 loop-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":loop/disp32"
322 All this relies on a convention that every `{}` block is delimited by labels
323 ending in `:loop` and `:break`.
327 The `return` instruction cleans up variable declarations just like an unconditional
328 `jump` to end of function, but also emits a series of copies before the final
329 `jump`, copying each argument of `return` to the register appropriate to the
330 respective function output. This doesn't work if a function output register
331 contains a later `return` argument (e.g. if the registers for two outputs are
332 swapped in `return`), so you can't do that.
334 return => "c3/return"
338 In the following instructions types are provided for clarity even if they must
339 be provided in an earlier 'var' declaration.
343 var/reg: (addr T) <- address var2: T
344 => "8d/copy-address *(ebp+" var2.stack-offset ") " reg "/r32"
348 var/reg: (addr T) <- index arr/rega: (addr array T), idx/regi: int
349 | if size-of(T) is 1, 2, 4 or 8
350 => "81 7/subop/compare %" rega " 0/imm32"
351 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
352 "(__check-mu-array-bounds *" rega " %" regi " " size-of(T) ")"
353 "8d/copy-address *(" rega "+" regi "<<" log2(size-of(T)) "+4) " reg "/r32"
354 var/reg: (addr T) <- index arr: (array T len), idx/regi: int
355 => "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") %" regi " " size-of(T) ")"
356 "8d/copy-address *(ebp+" regi "<<" log2(size-of(T)) "+" (arr.stack-offset + 4) ") " reg "/r32"
357 var/reg: (addr T) <- index arr/rega: (addr array T), n
358 => "81 7/subop/compare %" rega " 0/imm32"
359 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
360 "(__check-mu-array-bounds *" rega " " n " " size-of(T) ")"
361 "8d/copy-address *(" rega "+" (n*size-of(T)+4) ") " reg "/r32"
362 var/reg: (addr T) <- index arr: (array T len), n
363 => "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") " n " " size-of(T) ")"
364 "8d/copy-address *(ebp+" (arr.stack-offset+4+n*size-of(T)) ") " reg "/r32"
366 var/reg: (offset T) <- compute-offset arr: (addr array T), idx/regi: int # arr can be in reg or mem
367 => "69/multiply %" regi " " size-of(T) "/imm32 " reg "/r32"
368 var/reg: (offset T) <- compute-offset arr: (addr array T), idx: int # arr can be in reg or mem
369 => "69/multiply *(ebp+" idx.stack-offset ") " size-of(T) "/imm32 " reg "/r32"
370 var/reg: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
371 => "c7 0/subop/copy %" reg " " n*size-of(T) "/imm32"
372 var/reg: (addr T) <- index arr/rega: (addr array T), o/rego: (offset T)
373 => "81 7/subop/compare %" rega " 0/imm32"
374 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
375 "(__check-mu-array-bounds %" rega " %" rego " 1 \"" function-name "\")"
376 "8d/copy-address *(" rega "+" rego "+4) " reg "/r32"
378 Computing the length of an array is complex.
380 var/reg: int <- length arr/reg2: (addr array T)
381 | if T is byte (TODO)
382 => "8b/-> *" reg2 " " reg "/r32"
383 | if size-of(T) is 4 or 8 or 16 or 32 or 64 or 128
384 => "8b/-> *" reg2 " " reg "/r32"
385 "c1/shift 5/subop/logic-right %" reg " " log2(size-of(T)) "/imm8"
387 x86 has no instruction to divide by a literal, so
388 we need up to 3 extra registers! eax/edx for division and say ecx
395 "8b/-> *" reg2 " eax/r32"
396 "31/xor %edx 2/r32/edx" # sign-extend, but array size can't be negative
397 "b9/copy-to-ecx " size-of(T) "/imm32"
398 "f7 7/subop/idiv-eax-edx-by %ecx"
400 "89/<- %" reg " 0/r32/eax"
410 If a record (product) type T was defined to have elements a, b, c, ... of
411 types T_a, T_b, T_c, ..., then accessing one of those elements f of type T_f:
413 var/reg: (addr T_f) <- get var2/reg2: (addr T), f
414 => "81 7/subop/compare %" reg2 " 0/imm32"
415 "0f 84/jump-if-= __mu-abort-null-get-base-address/disp32"
416 "8d/copy-address *(" reg2 "+" offset(f) ") " reg "/r32"
417 var/reg: (addr T_f) <- get var2: T, f
418 => "8d/copy-address *(ebp+" var2.stack-offset "+" offset(f) ") " reg "/r32"
420 When the base is an address we perform a null check.
424 allocate in: (addr handle T)
425 => "(allocate Heap " size-of(T) " " in ")"
427 populate in: (addr handle array T), num # can be literal or variable on stack or register
428 => "(allocate-array2 Heap " size-of(T) " " num " " in ")"
430 populate-stream in: (addr handle stream T), num # can be literal or variable on stack or register
431 => "(new-stream Heap " size-of(T) " " num " " in ")"
433 # Some miscellaneous helpers to avoid error-prone size computations
436 => "(zero-out " s " " size-of(T) ")"
438 read-from-stream s: (addr stream T), out: (addr T)
439 => "(read-from-stream " s " " out " " size-of(T) ")"
441 write-to-stream s: (addr stream T), in: (addr T)
442 => "(write-to-stream " s " " in " " size-of(T) ")"
444 vim:ft=mu:nowrap:textwidth=0