Refactor controller runtime code generation
[lsnes.git] / src / library / controller-parse-i386.cpp
blob7b6e932c198248fd3353f874e3d72dfd427f26d7
1 #include "controller-data.hpp"
2 #include "controller-parse-asmgen.hpp"
3 #include "assembler-intrinsics-i386.hpp"
5 using namespace assembler_intrinsics;
7 namespace codegen
9 int calc_mask_bit(uint8_t mask)
11 switch(mask) {
12 case 0x01: return 0;
13 case 0x02: return 1;
14 case 0x04: return 2;
15 case 0x08: return 3;
16 case 0x10: return 4;
17 case 0x20: return 5;
18 case 0x40: return 6;
19 case 0x80: return 7;
20 default: return -1;
24 template<> void emit_serialize_prologue(I386& a, assembler::label_list& labels)
26 //Variable assignments:
27 //SI: State block.
28 //DX: Output buffer.
29 //AX: Write position counter.
31 //On amd64, the ABI places the parameters of serialize() into those registers, but on i386, the parameters
32 //are passed on stack. So load the parameters into correct registers. Also, save the registers beforehand.
33 if(a.is_i386()) {
34 a.push_reg(I386::reg_si);
35 a.push_reg(I386::reg_dx);
36 a.mov_reg_regmem(I386::reg_si, I386::reg_sp[16]);
37 a.mov_reg_regmem(I386::reg_dx, I386::reg_sp[20]);
39 //Set write position to 0.
40 a.xor_reg_regmem(I386::reg_ax, I386::reg_ax);
43 template<> void emit_serialize_button(I386& a, assembler::label_list& labels, int32_t offset, uint8_t mask,
44 uint8_t ch)
46 //Test the bit with specified mask on specified position. If set, set CL to 1, otherwise to 0.
47 a.test_regmem_imm8(I386::reg_si[offset], mask);
48 a.setnz_regmem(I386::reg_cx);
49 //Negate CL. This causes CL to become 0xFF or 0x00.
50 a.neg_regmem8(I386::reg_cx);
51 //AND CL with ch-'.' and add '.'. This results in ch if CL was 0xFF and '.' if CL was 0x00.
52 a.and_regmem_imm8(I386::reg_cx, ch - '.');
53 a.add_regmem_imm8(I386::reg_cx, '.');
54 //Write the byte to write position and advance write position.
55 a.mov_regmem_reg8(I386::reg_dx[I386::reg_ax], I386::reg_cx);
56 a.inc_regmem(I386::reg_ax);
59 template<> void emit_serialize_axis(I386& a, assembler::label_list& labels, int32_t offset)
61 //Get reference to write_axis_value() routine.
62 assembler::label& axisprint = labels.external((void*)write_axis_value);
64 //Save the state registers.
65 if(a.is_i386()) a.push_reg(I386::reg_di);
66 a.push_reg(I386::reg_si);
67 a.push_reg(I386::reg_dx);
68 a.push_reg(I386::reg_ax);
69 //Load the axis value from controller state (i386/amd64 is LE, so endianess is right).
70 a.mov_reg_regmem16(I386::reg_si, I386::reg_si[offset]);
71 //The high bits of SI are junk. Mask those off. This will become the second parameter of axis_print().
72 a.and_regmem_imm(I386::reg_si, 0xFFFF);
73 //Set first parameter of axis_print() to current write position.
74 a.lea_reg_mem(I386::reg_di, I386::reg_dx[I386::reg_ax]);
75 //If on i386, push the parameters into stack, as parameters are passed there.
76 if(a.is_i386()) a.push_reg(I386::reg_si);
77 if(a.is_i386()) a.push_reg(I386::reg_di);
78 //Call the axis_print() routine.
79 a.call_addr(axisprint);
80 //If on i386, clean up the passed parameters.
81 if(a.is_i386()) a.add_regmem_imm(I386::reg_sp, 8);
82 //Restore the state. We restore ax to cx, so we preserve the return value of axis_print().
83 a.pop_reg(I386::reg_cx);
84 a.pop_reg(I386::reg_dx);
85 a.pop_reg(I386::reg_si);
86 if(a.is_i386()) a.pop_reg(I386::reg_di);
87 //Add the old pointer to return value of axis_print() and make that the new pointer. Addition is commutative,
88 //so new = old + delta and new = delta + old are the same.
89 a.add_reg_regmem(I386::reg_ax, I386::reg_cx);
92 template<> void emit_serialize_pipe(I386& a, assembler::label_list& labels)
94 //Write '|' to write position and adavance write position.
95 a.mov_regmem_imm8(I386::reg_dx[I386::reg_ax], '|');
96 a.inc_regmem(I386::reg_ax);
99 template<> void emit_serialize_epilogue(I386& a, assembler::label_list& labels)
101 //If on i386, restore the saved registers.
102 if(a.is_i386()) a.pop_reg(I386::reg_dx);
103 if(a.is_i386()) a.pop_reg(I386::reg_si);
104 //Exit routine. The pointer in AX is correct for return value.
105 a.ret();
108 template<> void emit_deserialize_prologue(I386& a, assembler::label_list& labels)
110 //Variable assignments:
111 //SI: State block.
112 //DX: Input buffer.
113 //AX: read position counter.
115 //On amd64, the ABI places the parameters of deserialize() into those registers, but on i386, the parameters
116 //are passed on stack. So load the parameters into correct registers. Also, save the registers beforehand.
117 if(a.is_i386()) {
118 a.push_reg(I386::reg_si);
119 a.push_reg(I386::reg_dx);
120 a.mov_reg_regmem(I386::reg_si, I386::reg_sp[16]);
121 a.mov_reg_regmem(I386::reg_dx, I386::reg_sp[20]);
123 //Set read position to 0.
124 a.xor_reg_regmem(I386::reg_ax, I386::reg_ax);
127 template<> void emit_deserialize_clear_storage(I386& a, assembler::label_list& labels, int32_t size)
129 int32_t i;
130 //Write four zero bytes at once if there is space for so.
131 for(i = 0; i < size - 3; i += 4)
132 a.mov_regmem_imm32(I386::reg_si[i], 0);
133 //Write the rest one byte at a time.
134 for(; i < size; i++)
135 a.mov_regmem_imm8(I386::reg_si[i], 0);
138 template<> void emit_deserialize_button(I386& a, assembler::label_list& labels, int32_t offset,
139 uint8_t mask, assembler::label& next_pipe, assembler::label& end_deserialize)
141 assembler::label& clear_button = labels;
143 //Load the next input byte into CL.
144 a.mov_reg_regmem8(I386::reg_cx, I386::reg_dx[I386::reg_ax]);
145 //Check for special values '|', '\r', '\n', '\0'.
146 a.cmp_regmem_imm8(I386::reg_cx, '|');
147 a.jz_long(next_pipe);
148 a.cmp_regmem_imm8(I386::reg_cx, '\r');
149 a.jz_long(end_deserialize);
150 a.cmp_regmem_imm8(I386::reg_cx, '\n');
151 a.jz_long(end_deserialize);
152 a.cmp_regmem_imm8(I386::reg_cx, '\0');
153 a.jz_long(end_deserialize);
154 //Check for released values ' ' and '.'. If found, skip setting the bit.
155 a.cmp_regmem_imm8(I386::reg_cx, ' ');
156 a.jz_short(clear_button);
157 a.cmp_regmem_imm8(I386::reg_cx, '.');
158 a.jz_short(clear_button);
159 //Pressed. Set the button bit.
160 a.or_regmem_imm8(I386::reg_si[offset], mask);
161 a.label(clear_button);
162 //Advance the read pointer.
163 a.inc_regmem(I386::reg_ax);
166 template<> void emit_deserialize_axis(I386& a, assembler::label_list& labels, int32_t offset)
168 //Get reference to read_axis_value() routine.
169 assembler::label& axisread = labels.external((void*)read_axis_value);
171 //Save status registers.
172 if(a.is_i386()) a.push_reg(I386::reg_di);
173 a.push_reg(I386::reg_si);
174 a.push_reg(I386::reg_dx);
175 a.push_reg(I386::reg_ax);
176 //Set first parameter of read_axis_value() to start of serialization input buffer.
177 //Set the second parameter to be reference to saved read counter on stack. On i386, push the parameters
178 //to proper places.
179 a.mov_reg_regmem(I386::reg_di, I386::reg_dx);
180 a.mov_reg_regmem(I386::reg_si, I386::reg_sp);
181 if(a.is_i386()) a.push_reg(I386::reg_si);
182 if(a.is_i386()) a.push_reg(I386::reg_di);
183 //Call read_axis_value().
184 a.call_addr(axisread);
185 //Clean up the paraemters from stack (i386 only).
186 if(a.is_i386()) a.add_regmem_imm(I386::reg_sp, 8);
187 //Temporarily put the return value into CX.
188 a.mov_reg_regmem(I386::reg_cx, I386::reg_ax);
189 //Restore the status registers. Note that AX (read position) has been modified by reference.
190 a.pop_reg(I386::reg_ax);
191 a.pop_reg(I386::reg_dx);
192 a.pop_reg(I386::reg_si);
193 if(a.is_i386()) a.pop_reg(I386::reg_di);
194 //Write the axis value into controller state.
195 a.mov_regmem_reg16(I386::reg_si[offset], I386::reg_cx);
198 template<> void emit_deserialize_skip_until_pipe(I386& a, assembler::label_list& labels, assembler::label& next_pipe, assembler::label& deserialize_end)
200 assembler::label& loop = labels;
201 //While...
202 a.label(loop);
203 //Load next character.
204 a.mov_reg_regmem8(I386::reg_cx, I386::reg_dx[I386::reg_ax]);
205 //If it is '|', handle as pipe.
206 a.cmp_regmem_imm8(I386::reg_cx, '|');
207 a.jz_long(next_pipe);
208 //If it is '\r', \n' or '\0', handle as end of input.
209 a.cmp_regmem_imm8(I386::reg_cx, '\r');
210 a.jz_long(deserialize_end);
211 a.cmp_regmem_imm8(I386::reg_cx, '\n');
212 a.jz_long(deserialize_end);
213 a.cmp_regmem_imm8(I386::reg_cx, '\0');
214 a.jz_long(deserialize_end);
215 //Not interesting: Advance read position and go back to loop.
216 a.inc_regmem(I386::reg_ax);
217 a.jmp_short(loop);
220 template<> void emit_deserialize_skip(I386& a, assembler::label_list& labels)
222 //Increment the read position to skip the byte.
223 a.inc_regmem(I386::reg_ax);
226 template<> void emit_deserialize_special_blank(I386& a, assembler::label_list& labels)
228 //AX is the pending return value. Set that.
229 a.mov_reg_imm(I386::reg_ax, DESERIALIZE_SPECIAL_BLANK);
232 template<> void emit_deserialize_epilogue(I386& a, assembler::label_list& labels)
234 //Restore the saved registers on i386.
235 if(a.is_i386()) a.pop_reg(I386::reg_dx);
236 if(a.is_i386()) a.pop_reg(I386::reg_si);
237 //Exit routine. The pointer in AX is correct for return value (changed to DESERIALIZE_SPECIAL_BLANK if
238 //needed).
239 a.ret();
242 template<> void emit_read_prologue(I386& a, assembler::label_list& labels)
244 //Variable assignments:
245 //SI: State block.
246 //DX: Controller number
247 //CX: Button index.
248 //AX: Pending return value.
250 //Save registers if i386 and load the parameters from stack into variables.
251 if(a.is_i386()) {
252 a.push_reg(I386::reg_si);
253 a.push_reg(I386::reg_dx);
254 a.push_reg(I386::reg_cx);
255 a.mov_reg_regmem(I386::reg_si, I386::reg_sp[20]);
256 a.mov_reg_regmem(I386::reg_dx, I386::reg_sp[24]);
257 a.mov_reg_regmem(I386::reg_cx, I386::reg_sp[28]);
259 //Zero out the pending return value.
260 a.xor_reg_regmem(I386::reg_ax, I386::reg_ax);
263 template<> void emit_read_epilogue(I386& a, assembler::label_list& labels)
265 //Restore the saved registers on i386.
266 if(a.is_i386()) a.pop_reg(I386::reg_cx);
267 if(a.is_i386()) a.pop_reg(I386::reg_dx);
268 if(a.is_i386()) a.pop_reg(I386::reg_si);
269 //Exit routine. AX is the pending return.
270 a.ret();
273 template<> void emit_read_dispatch(I386& a, assembler::label_list& labels,
274 unsigned controllers, unsigned ilog2controls, assembler::label& end)
276 //Get reference to table after dispatch code.
277 assembler::label& table = labels;
278 //Is the controller number out of range? If yes, jump to epilogue (pending return register is 0).
279 a.cmp_regmem_imm(I386::reg_dx, controllers);
280 a.jae_long(end);
281 //Is the control number out of range? If yes, jump to epilogue (pending return register is 0).
282 a.cmp_regmem_imm(I386::reg_cx, 1 << ilog2controls);
283 a.jae_long(end);
284 //The numbers are in range. Compute 1-D index as controller * (2^ilog2controls) + control into DX.
285 a.shl_regmem_imm(I386::reg_dx, ilog2controls);
286 a.add_reg_regmem(I386::reg_dx, I386::reg_cx);
287 //Load base address of jump table to CX.
288 a.mov_reg_addr(I386::reg_cx, table);
289 //Jump to entry in position DX in table (doing words to bytes conversion).
290 a.jmp_regmem(I386::reg_cx[I386::reg_dx * a.wordsize()]);
291 //The table comes after this routine.
292 a.label(table);
295 template<> assembler::label& emit_read_label(I386& a, assembler::label_list& labels)
297 //Get reference to whatever code is called.
298 assembler::label& l = labels;
299 //Write address entry.
300 a.address(l);
301 //Return the newly created reference.
302 return l;
305 template<> void emit_read_label_bad(I386& a, assembler::label_list& labels, assembler::label& b)
307 //Write address entry for specified label.
308 a.address(b);
311 template<> void emit_read_button(I386& a, assembler::label_list& labels, assembler::label& l,
312 assembler::label& end, int32_t offset, uint8_t mask)
314 //Declare the label jumping into this code.
315 a.label(l);
316 //The pending return is 0, so we can just set low byte of it to 1 if button bit is set.
317 a.test_regmem_imm8(I386::reg_si[offset], mask);
318 a.setnz_regmem(I386::reg_ax); //Really AL.
319 //Jump to epilogue.
320 a.jmp_long(end);
323 template<> void emit_read_axis(I386& a, assembler::label_list& labels, assembler::label& l,
324 assembler::label& end, int32_t offset)
326 //Declare the label jumping into this code.
327 a.label(l);
328 //Just read the axis value. i386/amd64 is LE, so endianess is correct.
329 a.mov_reg_regmem16(I386::reg_ax, I386::reg_si[offset]);
330 //Jump to epilogue.
331 a.jmp_long(end);
334 template<> void emit_write_prologue(I386& a, assembler::label_list& labels)
336 //Variable assignments:
337 //SI: State block.
338 //DX: Controller number
339 //CX: Button index.
340 //AX: Value to write.
342 //Save registers if i386 and load the parameters from stack into variables.
343 if(a.is_i386()) {
344 a.push_reg(I386::reg_si);
345 a.push_reg(I386::reg_dx);
346 a.push_reg(I386::reg_cx);
347 a.push_reg(I386::reg_ax);
348 a.mov_reg_regmem(I386::reg_si, I386::reg_sp[24]);
349 a.mov_reg_regmem(I386::reg_dx, I386::reg_sp[28]);
350 a.mov_reg_regmem(I386::reg_cx, I386::reg_sp[32]);
351 a.mov_reg_regmem(I386::reg_ax, I386::reg_sp[36]);
352 } else {
353 //On amd64, the fifth parameter is in r8. Copy it to ax to be consistent with i386 case.
354 a.mov_reg_regmem(I386::reg_ax, I386::reg_r8);
358 template<> void emit_write_epilogue(I386& a, assembler::label_list& labels)
360 //Restore saved registers on i386.
361 if(a.is_i386()) a.pop_reg(I386::reg_ax);
362 if(a.is_i386()) a.pop_reg(I386::reg_cx);
363 if(a.is_i386()) a.pop_reg(I386::reg_dx);
364 if(a.is_i386()) a.pop_reg(I386::reg_si);
365 //Return. This function has no return value.
366 a.ret();
369 template<> void emit_write_button(I386& a, assembler::label_list& labels, assembler::label& l,
370 assembler::label& end, int32_t offset, uint8_t mask)
372 //See if mask can be done as shift.
373 int mask_bit = calc_mask_bit(mask);
374 //Label jumping to this code.
375 a.label(l);
376 //Is the write value nonzero? If not, set AL to 1 (otherwise set to 0).
377 //We don't need write value after this, so freely trash it.
378 a.cmp_regmem_imm(I386::reg_ax, 0);
379 a.setnz_regmem(I386::reg_ax);
380 //Set AL to be mask if write value was nonzero.
381 if(mask_bit >= 0) {
382 //Shift the 0 or 1 from LSB to correct place.
383 a.shl_regmem_imm8(I386::reg_ax, mask_bit);
384 } else {
385 //Negate the number to 0xFF or 0x00 and then mask the bits not in mask, giving either mask or 0.
386 a.neg_regmem8(I386::reg_ax);
387 a.and_regmem_imm8(I386::reg_ax, mask);
389 //Clear the bits in mask.
390 a.and_regmem_imm8(I386::reg_si[offset], ~mask);
391 //If needed, set the bits back.
392 a.or_regmem_reg8(I386::reg_si[offset], I386::reg_ax);
393 //Jump to epilogue.
394 a.jmp_long(end);
397 template<> void emit_write_axis(I386& a, assembler::label_list& labels, assembler::label& l,
398 assembler::label& end, int32_t offset)
400 //Label jumping to this code.
401 a.label(l);
402 //Write the write value to axis value. i386/amd64 is LE, so endianess is correct.
403 a.mov_regmem_reg16(I386::reg_si[offset], I386::reg_ax);
404 //Jump to epilogue.
405 a.jmp_long(end);