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 }