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 memory-safe statement-oriented language where most
5 statements translate to a single instruction of machine code. Blocks consist of
6 flat lists of instructions. Instructions can have inputs after the operation,
7 and outputs to the left of a '<-'. Inputs and outputs must be variables. They
8 can't include nested expressions. Variables can be literals ('n'), or live in a
9 register ('var/reg') or in memory ('var') at some 'stack-offset' from the 'ebp'
10 register. Outputs must be registers. To modify a variable in memory, pass it in
11 by reference as an input. (Inputs are more precisely called 'inouts'.)
12 Conversely, registers that are just read from must not be passed as outputs.
14 The following chart shows all the instruction forms supported by Mu, along with
15 the SubX instruction they're translated to.
17 ## Integer instructions
19 These instructions use the general-purpose registers.
21 var/eax <- increment => "40/increment-eax"
22 var/ecx <- increment => "41/increment-ecx"
23 var/edx <- increment => "42/increment-edx"
24 var/ebx <- increment => "43/increment-ebx"
25 var/esi <- increment => "46/increment-esi"
26 var/edi <- increment => "47/increment-edi"
27 increment var => "ff 0/subop/increment *(ebp+" var.stack-offset ")"
28 increment *var/reg => "ff 0/subop/increment *" reg
30 var/eax <- decrement => "48/decrement-eax"
31 var/ecx <- decrement => "49/decrement-ecx"
32 var/edx <- decrement => "4a/decrement-edx"
33 var/ebx <- decrement => "4b/decrement-ebx"
34 var/esi <- decrement => "4e/decrement-esi"
35 var/edi <- decrement => "4f/decrement-edi"
36 decrement var => "ff 1/subop/decrement *(ebp+" var.stack-offset ")"
37 decrement *var/reg => "ff 1/subop/decrement *" reg
39 var/reg <- add var2/reg2 => "01/add-to %" reg " " reg2 "/r32"
40 var/reg <- add var2 => "03/add *(ebp+" var2.stack-offset ") " reg "/r32"
41 var/reg <- add *var2/reg2 => "03/add *" reg2 " " reg "/r32"
42 add-to var1, var2/reg => "01/add-to *(ebp+" var1.stack-offset ") " reg "/r32"
43 add-to *var1/reg1, var2/reg2 => "01/add-to *" reg1 " " reg2 "/r32"
44 var/eax <- add n => "05/add-to-eax " n "/imm32"
45 var/reg <- add n => "81 0/subop/add %" reg " " n "/imm32"
46 add-to var, n => "81 0/subop/add *(ebp+" var.stack-offset ") " n "/imm32"
47 add-to *var/reg, n => "81 0/subop/add *" reg " " n "/imm32"
49 var/reg <- subtract var2/reg2 => "29/subtract-from %" reg " " reg2 "/r32"
50 var/reg <- subtract var2 => "2b/subtract *(ebp+" var2.stack-offset ") " reg "/r32"
51 var/reg <- subtract *var2/reg2 => "2b/subtract *" reg2 " " reg1 "/r32"
52 subtract-from var1, var2/reg2 => "29/subtract-from *(ebp+" var1.stack-offset ") " reg2 "/r32"
53 subtract-from *var1/reg1, var2/reg2 => "29/subtract-from *" reg1 " " reg2 "/r32"
54 var/eax <- subtract n => "2d/subtract-from-eax " n "/imm32"
55 var/reg <- subtract n => "81 5/subop/subtract %" reg " " n "/imm32"
56 subtract-from var, n => "81 5/subop/subtract *(ebp+" var.stack-offset ") " n "/imm32"
57 subtract-from *var/reg, n => "81 5/subop/subtract *" reg " " n "/imm32"
59 var/reg <- and var2/reg2 => "21/and-with %" reg " " reg2 "/r32"
60 var/reg <- and var2 => "23/and *(ebp+" var2.stack-offset " " reg "/r32"
61 var/reg <- and *var2/reg2 => "23/and *" reg2 " " reg "/r32"
62 and-with var1, var2/reg => "21/and-with *(ebp+" var1.stack-offset ") " reg "/r32"
63 and-with *var1/reg1, var2/reg2 => "21/and-with *" reg1 " " reg2 "/r32"
64 var/eax <- and n => "25/and-with-eax " n "/imm32"
65 var/reg <- and n => "81 4/subop/and %" reg " " n "/imm32"
66 and-with var, n => "81 4/subop/and *(ebp+" var.stack-offset ") " n "/imm32"
67 and-with *var/reg, n => "81 4/subop/and *" reg " " n "/imm32"
69 var/reg <- or var2/reg2 => "09/or-with %" reg " " reg2 "/r32"
70 var/reg <- or var2 => "0b/or *(ebp+" var2.stack-offset ") " reg "/r32"
71 var/reg <- or *var2/reg2 => "0b/or *" reg2 " " reg "/r32"
72 or-with var1, var2/reg2 => "09/or-with *(ebp+" var1.stack-offset " " reg2 "/r32"
73 or-with *var1/reg1, var2/reg2 => "09/or-with *" reg1 " " reg2 "/r32"
74 var/eax <- or n => "0d/or-with-eax " n "/imm32"
75 var/reg <- or n => "81 1/subop/or %" reg " " n "/imm32"
76 or-with var, n => "81 1/subop/or *(ebp+" var.stack-offset ") " n "/imm32"
77 or-with *var/reg, n => "81 1/subop/or *" reg " " n "/imm32"
79 var/reg <- not => "f7 2/subop/not %" reg
80 not var => "f7 2/subop/not *(ebp+" var.stack-offset ")"
81 not *var/reg => "f7 2/subop/not *" reg
83 var/reg <- xor var2/reg2 => "31/xor-with %" reg " " reg2 "/r32"
84 var/reg <- xor var2 => "33/xor *(ebp+" var2.stack-offset ") " reg "/r32"
85 var/reg <- xor *var2/reg2 => "33/xor *" reg2 " " reg "/r32"
86 xor-with var1, var2/reg => "31/xor-with *(ebp+" var1.stack-offset ") " reg "/r32"
87 xor-with *var1/reg1, var2/reg2 => "31/xor-with *" reg1 " " reg2 "/r32"
88 var/eax <- xor n => "35/xor-with-eax " n "/imm32"
89 var/reg <- xor n => "81 6/subop/xor %" reg " " n "/imm32"
90 xor-with var, n => "81 6/subop/xor *(ebp+" var.stack-offset ") " n "/imm32"
91 xor-with *var/reg, n => "81 6/subop/xor *" reg " " n "/imm32"
93 var/reg <- negate => "f7 3/subop/negate %" reg
94 negate var => "f7 3/subop/negate *(ebp+" var.stack-offset ")"
95 negate *var/reg => "f7 3/subop/negate *" reg
97 var/reg <- shift-left n => "c1/shift 4/subop/left %" reg " " n "/imm32"
98 var/reg <- shift-right n => "c1/shift 5/subop/right %" reg " " n "/imm32"
99 var/reg <- shift-right-signed n => "c1/shift 7/subop/right-signed %" reg " " n "/imm32"
100 shift-left var, n => "c1/shift 4/subop/left *(ebp+" var.stack-offset ") " n "/imm32"
101 shift-left *var/reg, n => "c1/shift 4/subop/left *" reg " " n "/imm32"
102 shift-right var, n => "c1/shift 5/subop/right *(ebp+" var.stack-offset ") " n "/imm32"
103 shift-right *var/reg, n => "c1/shift 5/subop/right *" reg " " n "/imm32"
104 shift-right-signed var, n => "c1/shift 7/subop/right-signed *(ebp+" var.stack-offset ") " n "/imm32"
105 shift-right-signed *var/reg, n => "c1/shift 7/subop/right-signed *" reg " " n "/imm32"
107 var/eax <- copy n => "b8/copy-to-eax " n "/imm32"
108 var/ecx <- copy n => "b9/copy-to-ecx " n "/imm32"
109 var/edx <- copy n => "ba/copy-to-edx " n "/imm32"
110 var/ebx <- copy n => "bb/copy-to-ebx " n "/imm32"
111 var/esi <- copy n => "be/copy-to-esi " n "/imm32"
112 var/edi <- copy n => "bf/copy-to-edi " n "/imm32"
113 var/reg <- copy var2/reg2 => "89/<- %" reg " " reg2 "/r32"
114 copy-to var1, var2/reg => "89/<- *(ebp+" var1.stack-offset ") " reg "/r32"
115 copy-to *var1/reg1, var2/reg2 => "89/<- *" reg1 " " reg2 "/r32"
116 var/reg <- copy var2 => "8b/-> *(ebp+" var2.stack-offset ") " reg "/r32"
117 var/reg <- copy *var2/reg2 => "8b/-> *" reg2 " " reg "/r32"
118 var/reg <- copy n => "c7 0/subop/copy %" reg " " n "/imm32"
119 copy-to var, n => "c7 0/subop/copy *(ebp+" var.stack-offset ") " n "/imm32"
120 copy-to *var/reg, n => "c7 0/subop/copy *" reg " " n "/imm32"
122 var/reg <- copy-byte var2/reg2 => "8a/byte-> %" reg2 " " reg "/r32"
123 "81 4/subop/and %" reg " 0xff/imm32"
124 var/reg <- copy-byte *var2/reg2 => "8a/byte-> *" reg2 " " reg "/r32"
125 "81 4/subop/and %" reg " 0xff/imm32"
126 copy-byte-to *var1/reg1, var2/reg2 => "88/byte<- *" reg1 " " reg2 "/r32"
128 compare var1, var2/reg2 => "39/compare *(ebp+" var1.stack-offset ") " reg2 "/r32"
129 compare *var1/reg1, var2/reg2 => "39/compare *" reg1 " " reg2 "/r32"
130 compare var1/reg1, var2 => "3b/compare<- *(ebp+" var2.stack-offset ") " reg1 "/r32"
131 compare var/reg, *var2/reg2 => "3b/compare<- *" reg " " n "/imm32"
132 compare var/eax, n => "3d/compare-eax-with " n "/imm32"
133 compare var/reg, n => "81 7/subop/compare %" reg " " n "/imm32"
134 compare var, n => "81 7/subop/compare *(ebp+" var.stack-offset ") " n "/imm32"
135 compare *var/reg, n => "81 7/subop/compare *" reg " " n "/imm32"
137 var/reg <- multiply var2 => "0f af/multiply *(ebp+" var2.stack-offset ") " reg "/r32"
138 var/reg <- multiply var2/reg2 => "0f af/multiply %" reg2 " " reg "/r32"
139 var/reg <- multiply *var2/reg2 => "0f af/multiply *" reg2 " " reg "/r32"
141 ## Floating-point operations
143 These instructions operate on either floating-point registers (xreg) or
144 general-purpose registers (reg) in indirect mode.
146 var/xreg <- add var2/xreg2 => "f3 0f 58/add %" xreg2 " " xreg1 "/x32"
147 var/xreg <- add var2 => "f3 0f 58/add *(ebp+" var2.stack-offset ") " xreg "/x32"
148 var/xreg <- add *var2/reg2 => "f3 0f 58/add *" reg2 " " xreg "/x32"
150 var/xreg <- subtract var2/xreg2 => "f3 0f 5c/subtract %" xreg2 " " xreg1 "/x32"
151 var/xreg <- subtract var2 => "f3 0f 5c/subtract *(ebp+" var2.stack-offset ") " xreg "/x32"
152 var/xreg <- subtract *var2/reg2 => "f3 0f 5c/subtract *" reg2 " " xreg "/x32"
154 var/xreg <- multiply var2/xreg2 => "f3 0f 59/multiply %" xreg2 " " xreg1 "/x32"
155 var/xreg <- multiply var2 => "f3 0f 59/multiply *(ebp+" var2.stack-offset ") " xreg "/x32"
156 var/xreg <- multiply *var2/reg2 => "f3 0f 59/multiply *" reg2 " " xreg "/x32"
158 var/xreg <- divide var2/xreg2 => "f3 0f 5e/divide %" xreg2 " " xreg1 "/x32"
159 var/xreg <- divide var2 => "f3 0f 5e/divide *(ebp+" var2.stack-offset ") " xreg "/x32"
160 var/xreg <- divide *var2/reg2 => "f3 0f 5e/divide *" reg2 " " xreg "/x32"
162 There are also some exclusively floating-point instructions:
164 var/xreg <- reciprocal var2/xreg2 => "f3 0f 53/reciprocal %" xreg2 " " xreg1 "/x32"
165 var/xreg <- reciprocal var2 => "f3 0f 53/reciprocal *(ebp+" var2.stack-offset ") " xreg "/x32"
166 var/xreg <- reciprocal *var2/reg2 => "f3 0f 53/reciprocal *" reg2 " " xreg "/x32"
168 var/xreg <- square-root var2/xreg2 => "f3 0f 51/square-root %" xreg2 " " xreg1 "/x32"
169 var/xreg <- square-root var2 => "f3 0f 51/square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
170 var/xreg <- square-root *var2/reg2 => "f3 0f 51/square-root *" reg2 " " xreg "/x32"
172 var/xreg <- inverse-square-root var2/xreg2 => "f3 0f 52/inverse-square-root %" xreg2 " " xreg1 "/x32"
173 var/xreg <- inverse-square-root var2 => "f3 0f 52/inverse-square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
174 var/xreg <- inverse-square-root *var2/reg2 => "f3 0f 52/inverse-square-root *" reg2 " " xreg "/x32"
176 var/xreg <- min var2/xreg2 => "f3 0f 5d/min %" xreg2 " " xreg1 "/x32"
177 var/xreg <- min var2 => "f3 0f 5d/min *(ebp+" var2.stack-offset ") " xreg "/x32"
178 var/xreg <- min *var2/reg2 => "f3 0f 5d/min *" reg2 " " xreg "/x32"
180 var/xreg <- max var2/xreg2 => "f3 0f 5f/max %" xreg2 " " xreg1 "/x32"
181 var/xreg <- max var2 => "f3 0f 5f/max *(ebp+" var2.stack-offset ") " xreg "/x32"
182 var/xreg <- max *var2/reg2 => "f3 0f 5f/max *" reg2 " " xreg "/x32"
184 Remember, when these instructions use indirect mode, they still use an integer
185 register. Floating-point registers can't hold addresses.
187 Most instructions operate exclusively on integer or floating-point operands.
188 The only exceptions are the instructions for converting between integers and
189 floating-point numbers.
191 var/xreg <- convert var2/reg2 => "f3 0f 2a/convert-to-float %" reg2 " " xreg "/x32"
192 var/xreg <- convert var2 => "f3 0f 2a/convert-to-float *(ebp+" var2.stack-offset ") " xreg "/x32"
193 var/xreg <- convert *var2/reg2 => "f3 0f 2a/convert-to-float *" reg2 " " xreg "/x32"
195 Converting floats to ints performs rounding by default. (We don't mess with the
196 MXCSR control register.)
198 var/reg <- convert var2/xreg2 => "f3 0f 2d/convert-to-int %" xreg2 " " reg "/r32"
199 var/reg <- convert var2 => "f3 0f 2d/convert-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
200 var/reg <- convert *var2/reg2 => "f3 0f 2d/convert-to-int *" reg2 " " reg "/r32"
202 There's a separate instruction for truncating the fractional part.
204 var/reg <- truncate var2/xreg2 => "f3 0f 2c/truncate-to-int %" xreg2 " " reg "/r32"
205 var/reg <- truncate var2 => "f3 0f 2c/truncate-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
206 var/reg <- truncate *var2/reg2 => "f3 0f 2c/truncate-to-int *" reg2 " " reg "/r32"
208 There are no instructions accepting floating-point literals. To obtain integer
209 literals in floating-point registers, copy them to general-purpose registers
210 and then convert them to floating-point.
212 One pattern you may have noticed above is that the floating-point instructions
213 above always write to registers. The only exceptions are `copy` instructions,
214 which can write to memory locations.
216 var/xreg <- copy var2/xreg2 => "f3 0f 11/<- %" xreg " " xreg2 "/x32"
217 copy-to var1, var2/xreg => "f3 0f 11/<- *(ebp+" var1.stack-offset ") " xreg "/x32"
218 var/xreg <- copy var2 => "f3 0f 10/-> *(ebp+" var2.stack-offset ") " xreg "/x32"
219 var/xreg <- copy *var2/reg2 => "f3 0f 10/-> *" reg2 " " xreg "/x32"
221 Comparisons must always start with a register:
223 compare var1/xreg1, var2/xreg2 => "0f 2f/compare %" xreg2 " " xreg1 "/x32"
224 compare var1/xreg1, var2 => "0f 2f/compare *(ebp+" var2.stack-offset ") " xreg1 "/x32"
228 In themselves, blocks generate no instructions. However, if a block contains
229 variable declarations, they must be cleaned up when the block ends.
231 Clean up var on the stack => "81 0/subop/add %esp " size-of(var) "/imm32"
232 Clean up var/reg => "8f 0/subop/pop %" reg
234 Clean up var/xreg => "f3 0f 10/-> *esp " xreg "/x32"
235 "81 0/subop/add %esp 4/imm32"
239 Besides having to clean up any variable declarations (see above) between
240 themselves and their target, jumps translate like this:
242 break => "e9/jump break/disp32"
243 break label => "e9/jump " label ":break/disp32"
244 loop => "e9/jump loop/disp32"
245 loop label => "e9/jump " label ":loop/disp32"
247 break-if-= => "0f 84/jump-if-= break/disp32"
248 break-if-= label => "0f 84/jump-if-= " label ":break/disp32"
249 loop-if-= => "0f 84/jump-if-= loop/disp32"
250 loop-if-= label => "0f 84/jump-if-= " label ":loop/disp32"
252 break-if-!= => "0f 85/jump-if-!= break/disp32"
253 break-if-!= label => "0f 85/jump-if-!= " label ":break/disp32"
254 loop-if-!= => "0f 85/jump-if-!= loop/disp32"
255 loop-if-!= label => "0f 85/jump-if-!= " label ":loop/disp32"
257 break-if-< => "0f 8c/jump-if-< break/disp32"
258 break-if-< label => "0f 8c/jump-if-< " label ":break/disp32"
259 loop-if-< => "0f 8c/jump-if-< loop/disp32"
260 loop-if-< label => "0f 8c/jump-if-< " label ":loop/disp32"
262 break-if-> => "0f 8f/jump-if-> break/disp32"
263 break-if-> label => "0f 8f/jump-if-> " label ":break/disp32"
264 loop-if-> => "0f 8f/jump-if-> loop/disp32"
265 loop-if-> label => "0f 8f/jump-if-> " label ":loop/disp32"
267 break-if-<= => "0f 8e/jump-if-<= break/disp32"
268 break-if-<= label => "0f 8e/jump-if-<= " label ":break/disp32"
269 loop-if-<= => "0f 8e/jump-if-<= loop/disp32"
270 loop-if-<= label => "0f 8e/jump-if-<= " label ":loop/disp32"
272 break-if->= => "0f 8d/jump-if->= break/disp32"
273 break-if->= label => "0f 8d/jump-if->= " label ":break/disp32"
274 loop-if->= => "0f 8d/jump-if->= loop/disp32"
275 loop-if->= label => "0f 8d/jump-if->= " label ":loop/disp32"
277 break-if-addr< => "0f 82/jump-if-addr< break/disp32"
278 break-if-addr< label => "0f 82/jump-if-addr< " label ":break/disp32"
279 loop-if-addr< => "0f 82/jump-if-addr< loop/disp32"
280 loop-if-addr< label => "0f 82/jump-if-addr< " label ":loop/disp32"
282 break-if-addr> => "0f 87/jump-if-addr> break/disp32"
283 break-if-addr> label => "0f 87/jump-if-addr> " label ":break/disp32"
284 loop-if-addr> => "0f 87/jump-if-addr> loop/disp32"
285 loop-if-addr> label => "0f 87/jump-if-addr> " label ":loop/disp32"
287 break-if-addr<= => "0f 86/jump-if-addr<= break/disp32"
288 break-if-addr<= label => "0f 86/jump-if-addr<= " label ":break/disp32"
289 loop-if-addr<= => "0f 86/jump-if-addr<= loop/disp32"
290 loop-if-addr<= label => "0f 86/jump-if-addr<= " label ":loop/disp32"
292 break-if-addr>= => "0f 83/jump-if-addr>= break/disp32"
293 break-if-addr>= label => "0f 83/jump-if-addr>= " label ":break/disp32"
294 loop-if-addr>= => "0f 83/jump-if-addr>= loop/disp32"
295 loop-if-addr>= label => "0f 83/jump-if-addr>= " label ":loop/disp32"
297 Similar float variants like `break-if-float<` are aliases for the corresponding
298 `addr` equivalents. The x86 instruction set stupidly has floating-point
299 operations only update a subset of flags.
301 Four sets of conditional jumps are useful for detecting overflow.
303 break-if-carry => "0f 82/jump-if-carry break/disp32"
304 break-if-carry label => "0f 82/jump-if-carry " label "/disp32"
305 loop-if-carry => "0f 82/jump-if-carry break/disp32"
306 loop-if-carry label => "0f 82/jump-if-carry " label "/disp32"
308 break-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
309 break-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
310 loop-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
311 loop-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
313 break-if-overflow => "0f 80/jump-if-overflow break/disp32"
314 break-if-overflow label => "0f 80/jump-if-overflow " label ":break/disp32"
315 loop-if-overflow => "0f 80/jump-if-overflow loop/disp32"
316 loop-if-overflow label => "0f 80/jump-if-overflow " label ":loop/disp32"
318 break-if-not-overflow => "0f 81/jump-if-not-overflow break/disp32"
319 break-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":break/disp32"
320 loop-if-not-overflow => "0f 81/jump-if-not-overflow loop/disp32"
321 loop-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":loop/disp32"
323 All this relies on a convention that every `{}` block is delimited by labels
324 ending in `:loop` and `:break`.
328 The `return` instruction cleans up variable declarations just like an unconditional
329 `jump` to end of function, but also emits a series of copies before the final
330 `jump`, copying each argument of `return` to the register appropriate to the
331 respective function output. This doesn't work if a function output register
332 contains a later `return` argument (e.g. if the registers for two outputs are
333 swapped in `return`), so you can't do that.
335 return => "c3/return"
339 In the following instructions types are provided for clarity even if they must
340 be provided in an earlier 'var' declaration.
344 var/reg: (addr T) <- address var2: T
345 => "8d/copy-address *(ebp+" var2.stack-offset ") " reg "/r32"
349 var/reg: (addr T) <- index arr/rega: (addr array T), idx/regi: int
350 | if size-of(T) is 1, 2, 4 or 8
351 => "81 7/subop/compare %" rega " 0/imm32"
352 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
353 "(__check-mu-array-bounds *" rega " %" regi " " size-of(T) ")"
354 "8d/copy-address *(" rega "+" regi "<<" log2(size-of(T)) "+4) " reg "/r32"
355 var/reg: (addr T) <- index arr: (array T len), idx/regi: int
356 => "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") %" regi " " size-of(T) ")"
357 "8d/copy-address *(ebp+" regi "<<" log2(size-of(T)) "+" (arr.stack-offset + 4) ") " reg "/r32"
358 var/reg: (addr T) <- index arr/rega: (addr array T), n
359 => "81 7/subop/compare %" rega " 0/imm32"
360 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
361 "(__check-mu-array-bounds *" rega " " n " " size-of(T) ")"
362 "8d/copy-address *(" rega "+" (n*size-of(T)+4) ") " reg "/r32"
363 var/reg: (addr T) <- index arr: (array T len), n
364 => "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") " n " " size-of(T) ")"
365 "8d/copy-address *(ebp+" (arr.stack-offset+4+n*size-of(T)) ") " reg "/r32"
367 var/reg: (offset T) <- compute-offset arr: (addr array T), idx/regi: int # arr can be in reg or mem
368 => "69/multiply %" regi " " size-of(T) "/imm32 " reg "/r32"
369 var/reg: (offset T) <- compute-offset arr: (addr array T), idx: int # arr can be in reg or mem
370 => "69/multiply *(ebp+" idx.stack-offset ") " size-of(T) "/imm32 " reg "/r32"
371 var/reg: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
372 => "c7 0/subop/copy %" reg " " n*size-of(T) "/imm32"
373 var/reg: (addr T) <- index arr/rega: (addr array T), o/rego: (offset T)
374 => "81 7/subop/compare %" rega " 0/imm32"
375 "0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
376 "(__check-mu-array-bounds %" rega " %" rego " 1 \"" function-name "\")"
377 "8d/copy-address *(" rega "+" rego "+4) " reg "/r32"
379 Computing the length of an array is complex.
381 var/reg: int <- length arr/reg2: (addr array T)
382 | if T is byte (TODO)
383 => "8b/-> *" reg2 " " reg "/r32"
384 | if size-of(T) is 4 or 8 or 16 or 32 or 64 or 128
385 => "8b/-> *" reg2 " " reg "/r32"
386 "c1/shift 5/subop/logic-right %" reg " " log2(size-of(T)) "/imm8"
388 x86 has no instruction to divide by a literal, so
389 we need up to 3 extra registers! eax/edx for division and say ecx
396 "8b/-> *" reg2 " eax/r32"
397 "31/xor %edx 2/r32/edx" # sign-extend, but array size can't be negative
398 "b9/copy-to-ecx " size-of(T) "/imm32"
399 "f7 7/subop/idiv-eax-edx-by %ecx"
401 "89/<- %" reg " 0/r32/eax"
411 If a record (product) type T was defined to have elements a, b, c, ... of
412 types T_a, T_b, T_c, ..., then accessing one of those elements f of type T_f:
414 var/reg: (addr T_f) <- get var2/reg2: (addr T), f
415 => "81 7/subop/compare %" reg2 " 0/imm32"
416 "0f 84/jump-if-= __mu-abort-null-get-base-address/disp32"
417 "8d/copy-address *(" reg2 "+" offset(f) ") " reg "/r32"
418 var/reg: (addr T_f) <- get var2: T, f
419 => "8d/copy-address *(ebp+" var2.stack-offset "+" offset(f) ") " reg "/r32"
421 When the base is an address we perform a null check.
425 allocate in: (addr handle T)
426 => "(allocate Heap " size-of(T) " " in ")"
428 populate in: (addr handle array T), num # can be literal or variable on stack or register
429 => "(allocate-array2 Heap " size-of(T) " " num " " in ")"
431 populate-stream in: (addr handle stream T), num # can be literal or variable on stack or register
432 => "(new-stream Heap " size-of(T) " " num " " in ")"
434 # Some miscellaneous helpers to avoid error-prone size computations
437 => "(zero-out " s " " size-of(T) ")"
439 read-from-stream s: (addr stream T), out: (addr T)
440 => "(read-from-stream " s " " out " " size-of(T) ")"
442 write-to-stream s: (addr stream T), in: (addr T)
443 => "(write-to-stream " s " " in " " size-of(T) ")"
445 vim:ft=mu:nowrap:textwidth=0