1 /// Provides interface to MSABI/SystemV ABI as well as generic register shenanigans.
2 module tern.experimental.assembly;
3 
4 import std.traits;
5 
6 public:
7 static:
8 version (Windows) // MSABI
9 {
10     const uint a0    = rcx - 30;
11     const uint a1    = rdx - 30;
12     const uint a2    = r8 - 30;
13     const uint a3    = r9 - 30;
14     const uint a4    = 4;
15     const uint a5    = 5;
16 }
17 else // SystemV
18 {
19     const uint a0    = rdi - 30;
20     const uint a1    = rsi - 30;
21     const uint a2    = rdx - 30;
22     const uint a3    = rcx - 30;
23     const uint a4    = r8 - 30;
24     const uint a5    = r9 - 30;
25 }
26 const uint a6    = 6;
27 const uint a7    = 7;
28 const uint a8    = 8;
29 const uint a9    = 9;
30 const uint a10   = 10;
31 const uint a11   = 11;
32 const uint a12   = 12;
33 const uint a13   = 13;
34 const uint a14   = 14;
35 const uint a15   = 15;
36 const uint a16   = 16;
37 const uint a17   = 17;
38 const uint a18   = 18;
39 const uint a19   = 19;
40 
41 const uint eax   = 52;
42 const uint ebx   = 53;
43 const uint ecx   = 54;
44 const uint edx   = 55;
45 const uint esi   = 56;
46 const uint edi   = 57;
47 const uint ebp   = 58;
48 const uint esp   = 59;
49 
50 const uint rax   = 66;
51 const uint rbx   = 67;
52 const uint rcx   = 68;
53 const uint rdx   = 69;
54 const uint rsi   = 70;
55 const uint rdi   = 71;
56 const uint r8    = 72;
57 const uint r9    = 73;
58 const uint r10   = 74;
59 const uint r11   = 75;
60 const uint r12   = 76;
61 const uint r13   = 77;
62 const uint r14   = 78;
63 const uint r15   = 79;
64 
65 const uint xmm0  = 86;
66 const uint xmm1  = 87;
67 const uint xmm2  = 88;
68 const uint xmm3  = 89;
69 const uint xmm4  = 90;
70 const uint xmm5  = 91;
71 const uint xmm6  = 92;
72 const uint xmm7  = 93;
73 const uint xmm8  = 94;
74 const uint xmm9  = 95;
75 const uint xmm10 = 96;
76 const uint xmm11 = 97;
77 const uint xmm12 = 98;
78 const uint xmm13 = 99;
79 const uint xmm14 = 101;
80 const uint xmm15 = 103;
81 
82 /*const string[][uint] arrayPair = [
83 rcx: ["RCX", "RDX"], 
84 rdx: ["RDX", "R8"], 
85 r8: ["R8", "R9"],
86 ];*/
87 
88 // MSABI
89 version (Windows)
90 {
91     /// True if `T` is a floating point to the ABI, otherwise, false.
92     alias isFloat(T) = Alias!(__traits(isFloating, T));
93     /// True if `T` is a native structure to the ABI, otherwise, false.
94     alias isNative(T) = Alias!(__traits(isScalar, T) || ((is(T == struct) || is(T == union) || is(T == enum)) && (T.sizeof == 1 || T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8)));
95     /// True if `T` would be paired into multiple registers, otherwise, false.
96     alias isPair(T) = Alias!false;
97     /// True if `T` would spill into the stack after pairing, otherwise, false.
98     alias isOverflow(T) = Alias!false;
99     /// True if `T` would be split into several XMM registers, otherwise, false.
100     alias isSplit(T) = Alias!(isFloat!T && T.sizeof > 8);
101 }
102 // SystemV
103 else
104 {
105     /// True if `T` is a floating point to the ABI, otherwise, false.
106     alias isFloat(T) = Alias!(__traits(isFloating, T) || isSomeString!T);
107     /// True if `T` is a native structure to the ABI, otherwise, false.
108     alias isNative(T) = Alias!(__traits(isScalar, T) || ((is(T == struct) || is(T == union) || is(T == enum)) && (T.sizeof <= 8)));
109     /// True if `T` would be paired into multiple registers, otherwise, false.
110     alias isPair(T) = Alias!(!isFloat!T && is(T == struct) && T.sizeof > 8 && T.sizeof <= 32);
111     /// True if `T` would spill into the stack after pairing, otherwise, false.
112     alias isOverflow(T) = Alias!(!isFloat!T && T.sizeof > 16 && T.sizeof <= 32);
113     /// True if `T` would be split into several XMM registers, otherwise, false.
114     alias isSplit(T) = Alias!(isFloat!T && T.sizeof > 8);
115 }
116 
117 /// Alias type to act as a floating point (`float`)
118 alias FLOAT = float;
119 /// Alias type to act as a native (`size_t`)
120 alias NATIVE = size_t;
121 /// Alias type to act as an array (`void[]`)
122 alias ARRAY = void[];
123 /// Struct to act as a reference (`ubyte[33]`)
124 public struct REFERENCE { ubyte[33] bytes; }
125 /// Struct to act as an inout, reference with special treatment by `mov`.
126 public struct INOUT { ubyte[33] bytes; }
127 
128 public:
129 static:
130 pure:
131 /** 
132  * Creates a mixin for preparing the stack for `COUNT` arguments.
133  *
134  * Params:
135  *  COUNT = The number of arguments to prepare for.
136  */
137 string prep(uint COUNT)()
138 {
139     version (Windows)
140     {
141         static if (COUNT > 4)
142             return "mixin(\"asm { xor R12, R12; sub RSP, "~((COUNT - 4) * 16 + 40).to!string~"; }\");";
143     }
144     else
145     {
146         static if (COUNT > 6)
147             return "mixin(\"asm { xor R12, R12; sub RSP, "~((COUNT - 6) * 16 + 40).to!string~"; }\");";
148     }
149 }
150 
151 /** 
152  * Creates a mixin for restoring the stack after a call with `COUNT` arguments.
153  *
154  * Params:
155  *  COUNT = The number of arguments to restore for.
156  */
157 string rest(uint COUNT)()
158 {
159     version (Windows)
160     {
161         static if (COUNT > 4)
162             return "mixin(\"asm { add RSP, "~((COUNT - 4) * 16 + 40).to!string~"; }\");";
163     }
164     else
165     {
166         static if (COUNT > 6)
167             return "mixin(\"asm { add RSP, "~((COUNT - 6) * 16 + 40).to!string~"; }\");";
168     }
169 }
170 
171 shared ubyte[8] movBuff;
172 /** 
173  * All chained uses of mov must be enclosed in a scope using `{..}`  
174  * Failure to do this will result in registers being overwritten by other movs, as this template uses `scope (exit)` for inline asm!
175  *
176  * Does not automatically prepare the stack for you, and R10, R11, R12 & XMM8 are used as scratch registers.  
177  * Use `prep!(uint)` to prepare the stack and rest!(uint) to restore the stack.
178  *
179  * Params:
180  *  ID = Register (or argument index) to put `VAR` into.
181  *  VAR = Value to put into `ID`.
182  *  AS = Type to emulate `VAR` as, defaults to void for the same type as `VAR` is.
183  *  _LINE = Used for preventing collisions when storing high/low of `VAR`. Change to a different value if getting errors.
184  *
185  * Example:
186  *  ```d
187  *  {
188  *   mixin(mov!(rax, a));
189  *   mixin(mov!(rbx, b));
190  *   mixin(mov!(rcx, a, void, true));
191  * }
192  * ```
193  */
194 // TODO: isOverflow!T
195 //       Fix stack arguments
196 public template mov(uint ID, alias VAR, AS = void, uint _LINE = __LINE__)
197 {
198     immutable string LINE = _LINE.to!string;
199     immutable string[uint] register = [ 
200         eax: "EAX",
201         ebx: "EBX",
202         ecx: "ECX",
203         edx: "EDX",
204         esi: "ESI",
205         edi: "EDI",
206         ebp: "EBP",
207         esp: "ESP",
208         rax: "RAX",
209         rbx: "RBX",
210         rcx: "RCX",
211         rdx: "RDX",
212         rsi: "RSI",
213         rdi: "RDI",
214         r8: "R8",
215         r9: "R9",
216         r10: "R10",
217         r11: "R11",
218         r12: "R12",
219         r13: "R13",
220         r14: "R14",
221         r15: "R15",
222         xmm0: "XMM0",
223         xmm1: "XMM1",
224         xmm2: "XMM2",
225         xmm3: "XMM3",
226         xmm4: "XMM4",
227         xmm5: "XMM5",
228         xmm6: "XMM6",
229         xmm7: "XMM7",
230         xmm8: "XMM8",
231         xmm9: "XMM9",
232         xmm10: "XMM10",
233         xmm11: "XMM11",
234         xmm12: "XMM12",
235         xmm13: "XMM13",
236         xmm14: "XMM14",
237         xmm15: "XMM15",
238     ];
239     immutable string[][uint] pair = [
240         eax: ["EAX", "EBX"],
241         ecx: ["ECX", "EDX"],
242         esi: ["ESI", "EDI"],
243         ebp: ["EBP", "ESP"],
244         rax: ["RAX", "RBX"],
245         rcx: ["RCX", "RDX"], 
246         rsi: ["RSI", "RDI"],
247         r8: ["R8", "R9"],
248         r10: ["R10", "R11"], 
249         r12: ["R12", "R13"],
250         r14: ["R14", "R15"],
251     ];
252 
253     static if (is(AS == void))
254         alias T = typeof(VAR);
255     else
256         alias T = AS;
257     alias RT = typeof(VAR);
258 
259     static if (ID >= eax)
260     {
261         pure string mov()
262         {
263             static if (isFloat!T)
264             {
265                 static if (ID < xmm0 || ID > xmm15)
266                     pragma(msg, "Floats can only be stored in XMM registers, not "~register[ID]~", UB may occur!");
267 
268                 static if (isSplit!T)
269                     return "ulong high"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"])[0];
270                     ulong low"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof - 8 >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[8.."~(RT.sizeof >= 16 ? 16 : RT.sizeof).to!string~"])[0];
271                     mixin(\"asm { pinsrq "~register[ID]~", high"~__traits(identifier, VAR)~LINE~", 0; }\");
272                     mixin(\"asm { pinsrq "~register[ID]~", low"~__traits(identifier, VAR)~LINE~", 1; }\");";
273 
274                 return "ulong high"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"])[0];
275                 scope (exit) mixin(\"asm { movq "~register[ID]~", high"~__traits(identifier, VAR)~LINE~"; }\");";
276                 
277             }
278             else static if (isPair!T)
279             {
280                 static if (ID !in pair)
281                     throw new Throwable("Cannot put "~fullyQualifiedName!T~" into "~register[ID]~", as it is a pairing type and "~register[ID]~" has no pair!");
282 
283                 pragma(msg, fullyQualifiedName!T~" is being put into register "~register[ID]~" but is being paired with register "~pair[ID][1]~", this behavior may be unintentional!");
284 
285                 return "ulong high"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"])[0];
286                 ulong low"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof - 8 >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[8.."~(RT.sizeof >= 16 ? 16 : RT.sizeof).to!string~"])[0];
287                 scope (exit) mixin(\"asm { mov"~(ID >= xmm0 ? "q " : " ")~pair[ID][0]~", high"~__traits(identifier, VAR)~LINE~"; }\");
288                 scope (exit) mixin(\"asm { mov"~(ID >= xmm0 ? "q " : " ")~pair[ID][1]~", low"~__traits(identifier, VAR)~LINE~"; }\");";
289             }   
290             else static if (isNative!T)
291             {
292                 return "ulong high"~__traits(identifier, VAR)~LINE~" = *cast(ulong*)&((movBuff = new ubyte[8])[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"] = (cast(ubyte*)&"~__traits(identifier, VAR)~")[0.."~(RT.sizeof >= 8 ? 8 : RT.sizeof).to!string~"])[0];
293                 scope (exit) mixin(\"asm { mov"~(ID >= xmm0 ? "q " : " ")~register[ID]~", high"~__traits(identifier, VAR)~LINE~"; }\");";
294             }
295             else
296             {
297                 // Wrong?
298                 // MSABI: Array e0 ptr -> array length (sequential)
299                 // SYSV: Array e0 ptr (no len)
300                 static if (is (T == INOUT))
301                     return "ulong high"~__traits(identifier, VAR)~LINE~" = cast(ulong)&"~__traits(identifier, VAR)~";
302                     scope (exit) mixin(\"asm { mov"~(ID >= xmm0 ? "q " : " ")~register[ID]~", high"~__traits(identifier, VAR)~LINE~"; }\");";
303 
304                 return fullyQualifiedName!(typeof(VAR))~" tval"~__traits(identifier, VAR)~LINE~" = "~__traits(identifier, VAR)~".dup;
305                 ulong hightval"~__traits(identifier, VAR)~LINE~" = cast(ulong)&tval"~__traits(identifier, VAR)~LINE~";
306                 scope (exit) mixin(\"asm { mov"~(ID >= xmm0 ? "q " : " ")~register[ID]~", hightval"~__traits(identifier, VAR)~LINE~"; }\");";
307             }
308         }
309     }
310     else static if (ID > 30)
311     {
312         pure string mov()
313         {
314             static if (isFloat!T)
315                 // XMM0 is offset by 18 from RAX
316                 return mov!(ID + 48, VAR, AS, _LINE);
317             else
318                 return mov!(ID + 30, VAR, AS, _LINE);
319         }
320     }
321     else // Stack
322     {
323         pure string mov()
324         {
325             version (Windows)
326                 uint offset = ((ID - 4) * 8 + 32);
327             else
328                 uint offset = ((ID - 6) * 8 + 32);
329 
330             static if (isFloat!T)
331             {
332                 return mov!(xmm8, VAR, AS, _LINE)[0..$-5]~" add RSP, R12; mov [RSP + "~offset.to!string~"], XMM8; sub RSP, R12; }\");";
333             }
334             else static if (isPair!T)
335             {
336                 return mov!(r10, VAR, AS, _LINE)[0..$-5]~" add RSP, R12; mov [RSP + "~offset.to!string~"], R10; mov [RSP + "~(offset + 8).to!string~"], R11; sub RSP, R12; add R12, 8; }\");";
337             }
338             else
339             {
340                 return mov!(r10, VAR, AS, _LINE)[0..$-5]~" add RSP, R12; mov [RSP + "~offset.to!string~"], R10; sub RSP, R12; }\");";
341             }
342         }
343     }
344 }
345 
346 /// ditto
347 // Precision past 2 decimals is lost if `T` is a floating point.
348 public template mov(uint ID, T, T val, AS = void, uint _LINE = __LINE__)
349 {
350     immutable string LINE = _LINE.to!string;
351     pure string mov()
352     {
353         static if (isSomeString!T)
354             const string mix = T.stringof~" tval"~val.to!string.mangle()~LINE~" =  \""~val.to!string~"\";";
355         else static if (isSomeChar!T)
356             const string mix = T.stringof~" tval"~val.to!string.mangle()~LINE~" = '"~val.to!string~"';";
357         else
358             const string mix = T.stringof~" tval"~val.to!string.mangle()~LINE~" = "~val.to!string~";";
359         mixin(mix);
360         return mix~"\n"~mov!(ID, mixin("tval"~val.to!string.mangle()~LINE), AS, _LINE);
361     }
362 }