feat(asm): SSE2 + AVX2/VEX encoders
This commit is contained in:
parent
d840f40e89
commit
52fd1d5b3b
|
|
@ -88,7 +88,7 @@ end;
|
|||
const
|
||||
REG_COUNT = 66;
|
||||
|
||||
RegNames: array[0..65] of string = (
|
||||
RegNames: array[0..89] of string = (
|
||||
'rax', 'rcx', 'rdx', 'rbx', 'rsp', 'rbp', 'rsi', 'rdi',
|
||||
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15',
|
||||
'eax', 'ecx', 'edx', 'ebx', 'esp', 'ebp', 'esi', 'edi',
|
||||
|
|
@ -97,10 +97,13 @@ const
|
|||
'al', 'cl', 'dl', 'bl', 'spl', 'bpl', 'sil', 'dil',
|
||||
'r8b', 'r9b', 'r10b', 'r11b',
|
||||
'xmm0', 'xmm1', 'xmm2', 'xmm3', 'xmm4', 'xmm5', 'xmm6', 'xmm7',
|
||||
'ah', 'ch', 'dh', 'bh'
|
||||
'ah', 'ch', 'dh', 'bh',
|
||||
'xmm8', 'xmm9', 'xmm10', 'xmm11', 'xmm12', 'xmm13', 'xmm14', 'xmm15',
|
||||
'ymm0', 'ymm1', 'ymm2', 'ymm3', 'ymm4', 'ymm5', 'ymm6', 'ymm7',
|
||||
'ymm8', 'ymm9', 'ymm10', 'ymm11', 'ymm12', 'ymm13', 'ymm14', 'ymm15'
|
||||
);
|
||||
|
||||
RegCodes: array[0..65] of Integer = (
|
||||
RegCodes: array[0..89] of Integer = (
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
|
|
@ -109,10 +112,13 @@ const
|
|||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11,
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
4, 5, 6, 7
|
||||
4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7,
|
||||
8, 9, 10, 11, 12, 13, 14, 15
|
||||
);
|
||||
|
||||
RegWidths: array[0..65] of Integer = (
|
||||
RegWidths: array[0..89] of Integer = (
|
||||
64, 64, 64, 64, 64, 64, 64, 64,
|
||||
64, 64, 64, 64, 64, 64, 64, 64,
|
||||
32, 32, 32, 32, 32, 32, 32, 32,
|
||||
|
|
@ -121,10 +127,13 @@ const
|
|||
8, 8, 8, 8, 8, 8, 8, 8,
|
||||
8, 8, 8, 8,
|
||||
128, 128, 128, 128, 128, 128, 128, 128,
|
||||
8, 8, 8, 8
|
||||
8, 8, 8, 8,
|
||||
128, 128, 128, 128, 128, 128, 128, 128,
|
||||
256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256
|
||||
);
|
||||
|
||||
RegIsXmm: array[0..65] of Integer = (
|
||||
RegIsXmm: array[0..89] of Integer = (
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
@ -133,7 +142,10 @@ const
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0
|
||||
0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
);
|
||||
|
||||
type
|
||||
|
|
@ -177,7 +189,7 @@ type
|
|||
TParsedLine = record
|
||||
Kind: TLineKind;
|
||||
Mnemonic: string; { instruction mnemonic or directive name }
|
||||
Op1, Op2: TOperand; { up to two operands }
|
||||
Op1, Op2, Op3: TOperand; { up to three operands (Op3 for VEX/imm forms) }
|
||||
NumOps: Integer;
|
||||
RawLine: string; { original line text }
|
||||
LineNum: Integer;
|
||||
|
|
@ -305,7 +317,7 @@ var
|
|||
I: Integer;
|
||||
begin
|
||||
I := 0;
|
||||
while I < 66 do
|
||||
while I <= High(RegNames) do
|
||||
begin
|
||||
if RegNames[I] = AName then
|
||||
begin
|
||||
|
|
@ -483,10 +495,12 @@ begin
|
|||
end
|
||||
else
|
||||
Result.Kind := opMem;
|
||||
{ Check for index,scale }
|
||||
{ Check for index,scale. GNU as allows whitespace after the commas,
|
||||
e.g. `(%rdi, %rdx)` and `(%rdi, %rdx, 4)`. }
|
||||
if (P < Length(S)) and (S[P]= Ord(',')) then
|
||||
begin
|
||||
P := P + 1;
|
||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do P := P + 1;
|
||||
if (P < Length(S)) and (S[P]= Ord('%')) then
|
||||
begin
|
||||
P := P + 1;
|
||||
|
|
@ -498,9 +512,11 @@ begin
|
|||
end;
|
||||
LookupReg(RegName, Result.Index, IdxW, IdxXmm);
|
||||
end;
|
||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do P := P + 1;
|
||||
if (P < Length(S)) and (S[P]= Ord(',')) then
|
||||
begin
|
||||
P := P + 1;
|
||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do P := P + 1;
|
||||
Result.Scale := Integer(ParseInt(S, P));
|
||||
end;
|
||||
end;
|
||||
|
|
@ -645,6 +661,7 @@ begin
|
|||
Result.LineNum := ALineNum;
|
||||
Result.Op1.Kind := opNone;
|
||||
Result.Op2.Kind := opNone;
|
||||
Result.Op3.Kind := opNone;
|
||||
|
||||
S := TrimStr(ALine);
|
||||
if (Length(S) = 0) or (S[0]= Ord('#')) then Exit;
|
||||
|
|
@ -759,6 +776,22 @@ begin
|
|||
Result.Op2 := TmpOp;
|
||||
if Result.Op2.Kind <> opNone then
|
||||
Result.NumOps := 2;
|
||||
P := OpEnd;
|
||||
|
||||
{ Third operand (VEX 3-operand forms, e.g. vpaddb %ymm2,%ymm3,%ymm3, and
|
||||
imm-prefixed forms like pshufd $0x0E,%xmm3,%xmm3). }
|
||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do
|
||||
P := P + 1;
|
||||
if (P < Length(S)) and (S[P]= Ord(',')) then
|
||||
begin
|
||||
P := P + 1;
|
||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do
|
||||
P := P + 1;
|
||||
TmpOp := ParseOperand(S, P, OpEnd);
|
||||
Result.Op3 := TmpOp;
|
||||
if Result.Op3.Kind <> opNone then
|
||||
Result.NumOps := 3;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
|
@ -991,6 +1024,10 @@ function EncodeSSE(var ACB: TCodeBuf; var ACtx: TEncodeContext;
|
|||
const AMnem: string; const ASrc, ADst: TOperand): string; forward;
|
||||
function EncodeMovqXmm(var ACB: TCodeBuf; var ACtx: TEncodeContext;
|
||||
const AMnem: string; const ASrc, ADst: TOperand): string; forward;
|
||||
{ VEX/AVX encoder. AOp1/AOp2/AOp3 are the parsed operands in AT&T order
|
||||
(AOp1 = first / source-or-immediate ... AOp3 = destination). }
|
||||
function EncodeVEX(var ACB: TCodeBuf; var ACtx: TEncodeContext;
|
||||
const AMnem: string; const AOp1, AOp2, AOp3: TOperand): string; forward;
|
||||
|
||||
{ Returns True if the mnemonic is a conditional or unconditional branch }
|
||||
function IsBranch(const AMnem: string): Boolean;
|
||||
|
|
@ -1300,7 +1337,7 @@ function EncodeInstruction(var ACtx: TEncodeContext;
|
|||
var
|
||||
CB: TCodeBuf;
|
||||
Mnem: string;
|
||||
Op1, Op2: TOperand;
|
||||
Op1, Op2, Op3: TOperand;
|
||||
DispOff: Integer;
|
||||
Dummy: Boolean;
|
||||
TargetOff: Integer;
|
||||
|
|
@ -1312,6 +1349,7 @@ begin
|
|||
ACtx.ImmTail := 0;
|
||||
Op1 := AParsed.Op1;
|
||||
Op2 := AParsed.Op2;
|
||||
Op3 := AParsed.Op3;
|
||||
{ Accept unsuffixed AT&T mnemonics (mov/lea/xor/…) by inferring the size. }
|
||||
Mnem := NormalizeMnemonic(AParsed.Mnemonic, Op1, Op2);
|
||||
|
||||
|
|
@ -1351,6 +1389,14 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ ---- cpuid ---- 0F A2 }
|
||||
if Mnem = 'cpuid' then
|
||||
begin
|
||||
CBEmit(CB, $0F); CBEmit(CB, $A2);
|
||||
Result := CB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ ---- leave ---- }
|
||||
if Mnem = 'leave' then
|
||||
begin
|
||||
|
|
@ -1743,12 +1789,42 @@ begin
|
|||
or (Mnem = 'cvtsd2ss') or (Mnem = 'cvtss2sd')
|
||||
or (Mnem = 'cvtsi2sdq') or (Mnem = 'cvtsi2ssq')
|
||||
or (Mnem = 'cvtsd2si') or (Mnem = 'cvtss2si')
|
||||
or (Mnem = 'cvttsd2si') or (Mnem = 'cvttss2si') then
|
||||
or (Mnem = 'cvttsd2si') or (Mnem = 'cvttss2si')
|
||||
or (Mnem = 'pxor') or (Mnem = 'movdqa') or (Mnem = 'movdqu')
|
||||
or (Mnem = 'paddb') or (Mnem = 'pcmpgtb') or (Mnem = 'pcmpeqb')
|
||||
or (Mnem = 'psadbw') or (Mnem = 'pabsb') then
|
||||
begin
|
||||
Result := EncodeSSE(CB, ACtx, Mnem, Op1, Op2);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ ---- pshufd $imm8, xmm/m, xmm (66 0F 70 /r ib) ---- }
|
||||
if Mnem = 'pshufd' then
|
||||
begin
|
||||
if (Op1.Kind = opImm) and (Op2.Kind = opReg) and (Op3.Kind = opReg) then
|
||||
begin
|
||||
EmitSSERegReg(CB, $66, False, $70, -1, Op2.Reg, Op3.Reg);
|
||||
CBEmit(CB, Integer(Op1.Imm) and $FF);
|
||||
end
|
||||
else
|
||||
raise EAssembler.Create('pshufd: unsupported operands');
|
||||
Result := CB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ ---- AVX/AVX2 (VEX-encoded) ---- }
|
||||
if (Mnem = 'vzeroupper')
|
||||
or (Mnem = 'vpxor') or (Mnem = 'vpaddb') or (Mnem = 'vpaddw')
|
||||
or (Mnem = 'vpaddq') or (Mnem = 'vpcmpeqb') or (Mnem = 'vpcmpgtb')
|
||||
or (Mnem = 'vpsadbw') or (Mnem = 'vpabsb')
|
||||
or (Mnem = 'vmovdqa') or (Mnem = 'vmovdqu') or (Mnem = 'vmovd')
|
||||
or (Mnem = 'vpsllw') or (Mnem = 'vpsrlw') or (Mnem = 'vpshufd')
|
||||
or (Mnem = 'vextracti128') then
|
||||
begin
|
||||
Result := EncodeVEX(CB, ACtx, Mnem, Op1, Op2, Op3);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
raise EAssembler.Create('unhandled mnemonic: ' + Mnem);
|
||||
end;
|
||||
|
||||
|
|
@ -2274,6 +2350,92 @@ begin
|
|||
raise EAssembler.Create(AMnem + ': not implemented');
|
||||
end;
|
||||
|
||||
{ ---- VEX (AVX/AVX2) helpers ----------------------------------------- }
|
||||
|
||||
{ pp (mandatory-prefix) field: 0=none, 1=66, 2=F3, 3=F2. }
|
||||
function VexPP(APrefix: Integer): Integer;
|
||||
begin
|
||||
case APrefix of
|
||||
$66: Result := 1;
|
||||
$F3: Result := 2;
|
||||
$F2: Result := 3;
|
||||
else
|
||||
Result := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Emit a VEX-encoded instruction. AMap is the opcode map (1 = 0F, 2 = 0F38,
|
||||
3 = 0F3A). ADstReg goes in ModRM.reg, ARmReg/ARmMem in ModRM.r/m, AVvvv is
|
||||
the non-destructive source register (the VEX.vvvv field; pass 15 / unused for
|
||||
two-operand forms). AL256 selects the 256-bit (ymm) form (VEX.L). AW is
|
||||
VEX.W. The 2-byte form (C5) is used when X=B=0, map=1 and W=0; otherwise the
|
||||
3-byte form (C4) is required. }
|
||||
procedure EmitVexRR(var ACB: TCodeBuf; APrefix, AMap: Integer;
|
||||
AW, AL256: Boolean; AOpcode, ADstReg, AVvvv, ARmReg: Integer);
|
||||
var
|
||||
R, X, B, PP, Byte2, Byte3: Integer;
|
||||
begin
|
||||
R := Ord(ADstReg >= 8); { ModRM.reg extension }
|
||||
X := 0;
|
||||
B := Ord(ARmReg >= 8); { ModRM.r/m extension }
|
||||
PP := VexPP(APrefix);
|
||||
|
||||
if (AMap = 1) and (not AW) and (X = 0) and (B = 0) then
|
||||
begin
|
||||
{ 2-byte VEX (C5). Byte2 = ~R<<7 | ~vvvv<<3 | L<<2 | pp }
|
||||
Byte2 := ((1 - R) shl 7) or (((not AVvvv) and $0F) shl 3)
|
||||
or (Ord(AL256) shl 2) or PP;
|
||||
CBEmit(ACB, $C5);
|
||||
CBEmit(ACB, Byte2 and $FF);
|
||||
end
|
||||
else
|
||||
begin
|
||||
{ 3-byte VEX (C4). Byte2 = ~R<<7|~X<<6|~B<<5|map ; Byte3 = W<<7|~vvvv<<3|L<<2|pp }
|
||||
Byte2 := ((1 - R) shl 7) or ((1 - X) shl 6) or ((1 - B) shl 5) or (AMap and $1F);
|
||||
Byte3 := (Ord(AW) shl 7) or (((not AVvvv) and $0F) shl 3)
|
||||
or (Ord(AL256) shl 2) or PP;
|
||||
CBEmit(ACB, $C4);
|
||||
CBEmit(ACB, Byte2 and $FF);
|
||||
CBEmit(ACB, Byte3 and $FF);
|
||||
end;
|
||||
CBEmit(ACB, AOpcode);
|
||||
CBEmit(ACB, MakeModRM(3, ADstReg, ARmReg));
|
||||
end;
|
||||
|
||||
{ As EmitVexRR but the r/m operand is memory. }
|
||||
procedure EmitVexRM(var ACB: TCodeBuf; var ACtx: TEncodeContext;
|
||||
APrefix, AMap: Integer; AW, AL256: Boolean;
|
||||
AOpcode, ADstReg, AVvvv: Integer; const AMem: TOperand);
|
||||
var
|
||||
R, X, B, PP, Byte2, Byte3, BaseReg: Integer;
|
||||
begin
|
||||
BaseReg := AMem.Base;
|
||||
if BaseReg < 0 then BaseReg := 0;
|
||||
R := Ord(ADstReg >= 8);
|
||||
X := Ord(AMem.Index >= 8);
|
||||
B := Ord(BaseReg >= 8);
|
||||
PP := VexPP(APrefix);
|
||||
|
||||
if (AMap = 1) and (not AW) and (X = 0) and (B = 0) then
|
||||
begin
|
||||
Byte2 := ((1 - R) shl 7) or (((not AVvvv) and $0F) shl 3)
|
||||
or (Ord(AL256) shl 2) or PP;
|
||||
CBEmit(ACB, $C5);
|
||||
CBEmit(ACB, Byte2 and $FF);
|
||||
end
|
||||
else
|
||||
begin
|
||||
Byte2 := ((1 - R) shl 7) or ((1 - X) shl 6) or ((1 - B) shl 5) or (AMap and $1F);
|
||||
Byte3 := (Ord(AW) shl 7) or (((not AVvvv) and $0F) shl 3)
|
||||
or (Ord(AL256) shl 2) or PP;
|
||||
CBEmit(ACB, $C4);
|
||||
CBEmit(ACB, Byte2 and $FF);
|
||||
CBEmit(ACB, Byte3 and $FF);
|
||||
end;
|
||||
CBEmit(ACB, AOpcode);
|
||||
EncodeMemOperand(ACB, ACtx, AMem, ADstReg and 7);
|
||||
end;
|
||||
|
||||
{ ---- SSE/FP helpers -------------------------------------------------- }
|
||||
|
||||
procedure EmitSSERegReg(var ACB: TCodeBuf; APrefix: Integer;
|
||||
|
|
@ -2350,6 +2512,16 @@ begin
|
|||
else if AMnem = 'cvtss2si' then begin Prefix := $F3; Opcode1 := $2D; NeedRexW := True; end
|
||||
else if AMnem = 'cvttsd2si' then begin Prefix := $F2; Opcode1 := $2C; NeedRexW := True; end
|
||||
else if AMnem = 'cvttss2si' then begin Prefix := $F3; Opcode1 := $2C; NeedRexW := True; end
|
||||
{ ---- SSE2 packed-integer (128-bit xmm) ---- }
|
||||
else if AMnem = 'pxor' then begin Prefix := $66; Opcode1 := $EF; end
|
||||
else if AMnem = 'movdqa' then begin Prefix := $66; Opcode1 := $6F; end
|
||||
else if AMnem = 'movdqu' then begin Prefix := $F3; Opcode1 := $6F; end
|
||||
else if AMnem = 'paddb' then begin Prefix := $66; Opcode1 := $FC; end
|
||||
else if AMnem = 'pcmpgtb' then begin Prefix := $66; Opcode1 := $64; end
|
||||
else if AMnem = 'pcmpeqb' then begin Prefix := $66; Opcode1 := $74; end
|
||||
else if AMnem = 'psadbw' then begin Prefix := $66; Opcode1 := $F6; end
|
||||
{ 3-byte 0F 38 map. }
|
||||
else if AMnem = 'pabsb' then begin Prefix := $66; Opcode1 := $38; Opcode2 := $1C; end
|
||||
else
|
||||
raise EAssembler.Create('SSE: unhandled mnemonic: ' + AMnem);
|
||||
|
||||
|
|
@ -2401,6 +2573,129 @@ begin
|
|||
raise EAssembler.Create(AMnem + ': unsupported operand combination');
|
||||
end;
|
||||
|
||||
{ ---- VEX/AVX2 instruction encoder ----------------------------------- }
|
||||
|
||||
function EncodeVEX(var ACB: TCodeBuf; var ACtx: TEncodeContext;
|
||||
const AMnem: string; const AOp1, AOp2, AOp3: TOperand): string;
|
||||
var
|
||||
Prefix, Map, Opc: Integer;
|
||||
W, L: Boolean;
|
||||
ShiftSub: Integer; { /n in ModRM.reg for the imm-shift forms, else -1 }
|
||||
begin
|
||||
{ vzeroupper: C5 F8 77, no operands (vvvv=1111, L=0, pp=0). }
|
||||
if AMnem = 'vzeroupper' then
|
||||
begin
|
||||
CBEmit(ACB, $C5); CBEmit(ACB, $F8); CBEmit(ACB, $77);
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ VEX.L: 256-bit (ymm) vs 128-bit (xmm), taken from a vector register
|
||||
operand's width (ymm = 256). Prefer the destination (last operand); fall
|
||||
back to an earlier vector operand. }
|
||||
if (AOp3.Kind = opReg) and (AOp3.RegW = 256) then L := True
|
||||
else if (AOp3.Kind = opReg) and (AOp3.RegW = 128) then L := False
|
||||
else if (AOp2.Kind = opReg) and (AOp2.RegW = 256) then L := True
|
||||
else if (AOp1.Kind = opReg) and (AOp1.RegW = 256) then L := True
|
||||
else L := False;
|
||||
|
||||
Prefix := 0; Map := 1; W := False; ShiftSub := -1; Opc := 0;
|
||||
|
||||
if AMnem = 'vpxor' then begin Prefix := $66; Opc := $EF; end
|
||||
else if AMnem = 'vpaddb' then begin Prefix := $66; Opc := $FC; end
|
||||
else if AMnem = 'vpaddw' then begin Prefix := $66; Opc := $FD; end
|
||||
else if AMnem = 'vpaddq' then begin Prefix := $66; Opc := $D4; end
|
||||
else if AMnem = 'vpcmpeqb' then begin Prefix := $66; Opc := $74; end
|
||||
else if AMnem = 'vpcmpgtb' then begin Prefix := $66; Opc := $64; end
|
||||
else if AMnem = 'vpsadbw' then begin Prefix := $66; Opc := $F6; end
|
||||
else if AMnem = 'vpabsb' then begin Prefix := $66; Map := 2; Opc := $1C; end
|
||||
else if AMnem = 'vmovdqa' then begin Prefix := $66; Opc := $6F; end
|
||||
else if AMnem = 'vmovdqu' then begin Prefix := $F3; Opc := $6F; end
|
||||
else if AMnem = 'vpsllw' then begin Prefix := $66; Opc := $71; ShiftSub := 6; end
|
||||
else if AMnem = 'vpsrlw' then begin Prefix := $66; Opc := $71; ShiftSub := 2; end
|
||||
else if AMnem = 'vpshufd' then begin Prefix := $66; Opc := $70; end
|
||||
else if AMnem = 'vextracti128' then begin Prefix := $66; Map := 3; Opc := $39; end
|
||||
else if AMnem = 'vmovd' then
|
||||
begin
|
||||
{ vmovd %xmm, %r32 : 66 VEX.128.0F.W0 7E /r — gp reg in r/m, xmm in reg. }
|
||||
EmitVexRR(ACB, $66, 1, False, False, $7E, AOp1.Reg, 0, AOp2.Reg);
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end
|
||||
else
|
||||
raise EAssembler.Create('VEX: unhandled mnemonic: ' + AMnem);
|
||||
|
||||
{ Imm-shift forms: vpsllw $imm, src, dst. ModRM.reg = /ShiftSub, r/m = src,
|
||||
vvvv = dst; imm8 trails. }
|
||||
if ShiftSub >= 0 then
|
||||
begin
|
||||
if (AOp1.Kind = opImm) and (AOp2.Kind = opReg) and (AOp3.Kind = opReg) then
|
||||
begin
|
||||
EmitVexRR(ACB, Prefix, Map, W, L, Opc, ShiftSub, AOp3.Reg, AOp2.Reg);
|
||||
CBEmit(ACB, Integer(AOp1.Imm) and $FF);
|
||||
end
|
||||
else
|
||||
raise EAssembler.Create(AMnem + ': bad shift operands');
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Imm + 2 regs (no vvvv): vpshufd $imm,%src,%dst ; vextracti128 $imm,%src,%dst.
|
||||
ModRM.reg = src(reg field holds the operand being read), r/m = dst, imm8
|
||||
trails, vvvv unused (1111). Note for vextracti128 the ENCODING reg/rm
|
||||
direction is reversed vs vpshufd (it stores ymm-src in reg, xmm-dst in rm),
|
||||
and VEX.L=1 always (it operates on a 256-bit source despite the xmm dst). }
|
||||
if AMnem = 'vpshufd' then
|
||||
begin
|
||||
if (AOp1.Kind = opImm) and (AOp2.Kind = opReg) and (AOp3.Kind = opReg) then
|
||||
begin
|
||||
EmitVexRR(ACB, Prefix, Map, W, L, Opc, AOp3.Reg, 0, AOp2.Reg);
|
||||
CBEmit(ACB, Integer(AOp1.Imm) and $FF);
|
||||
end
|
||||
else
|
||||
raise EAssembler.Create(AMnem + ': bad operands');
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if AMnem = 'vextracti128' then
|
||||
begin
|
||||
if (AOp1.Kind = opImm) and (AOp2.Kind = opReg) and (AOp3.Kind = opReg) then
|
||||
begin
|
||||
{ ymm src is in ModRM.reg, xmm dst in r/m; L=1 (256-bit source). }
|
||||
EmitVexRR(ACB, Prefix, Map, W, True, Opc, AOp2.Reg, 0, AOp3.Reg);
|
||||
CBEmit(ACB, Integer(AOp1.Imm) and $FF);
|
||||
end
|
||||
else
|
||||
raise EAssembler.Create(AMnem + ': bad operands');
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Two-operand (no vvvv): vpabsb %src,%dst ; vmovdqa/vmovdqu %src/mem,%dst. }
|
||||
if AOp3.Kind = opNone then
|
||||
begin
|
||||
if (AOp1.Kind = opReg) and (AOp2.Kind = opReg) then
|
||||
EmitVexRR(ACB, Prefix, Map, W, L, Opc, AOp2.Reg, 0, AOp1.Reg)
|
||||
else if IsMemLike(AOp1) and (AOp2.Kind = opReg) then
|
||||
EmitVexRM(ACB, ACtx, Prefix, Map, W, L, Opc, AOp2.Reg, 0, AOp1)
|
||||
else
|
||||
raise EAssembler.Create(AMnem + ': bad 2-operand form');
|
||||
Result := ACB.Data;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ Three-operand: vpaddb %src2,%src1,%dst. ModRM.reg = dst, vvvv = src1,
|
||||
r/m = src2 (reg or mem). }
|
||||
if (AOp1.Kind = opReg) and (AOp2.Kind = opReg) and (AOp3.Kind = opReg) then
|
||||
EmitVexRR(ACB, Prefix, Map, W, L, Opc, AOp3.Reg, AOp2.Reg, AOp1.Reg)
|
||||
else if IsMemLike(AOp1) and (AOp2.Kind = opReg) and (AOp3.Kind = opReg) then
|
||||
EmitVexRM(ACB, ACtx, Prefix, Map, W, L, Opc, AOp3.Reg, AOp2.Reg, AOp1)
|
||||
else
|
||||
raise EAssembler.Create(AMnem + ': bad 3-operand form');
|
||||
Result := ACB.Data;
|
||||
end;
|
||||
|
||||
{ Encode `movq`/`movd` moving between an XMM register and a GP register or
|
||||
memory. The XMM operand is always the ModRM.reg field:
|
||||
xmm <- gp/mem : 66 [REX.W] 0F 6E /r (load into xmm)
|
||||
|
|
@ -2459,6 +2754,7 @@ var
|
|||
FVal: Single;
|
||||
DataOp: TOperand;
|
||||
OpEnd: Integer;
|
||||
Count, Size, FillVal, J: Integer;
|
||||
begin
|
||||
Dir := AParsed.Mnemonic;
|
||||
Args := TrimStr(AParsed.RawLine);
|
||||
|
|
@ -2483,6 +2779,8 @@ begin
|
|||
ASection := eskTbss
|
||||
else if StartsWithStr(Args, '.note.GNU-stack') then
|
||||
Exit
|
||||
else if StartsWithStr(Args, '.data') then
|
||||
ASection := eskData
|
||||
else if StartsWithStr(Args, '.bss') then
|
||||
ASection := eskBss
|
||||
else if StartsWithStr(Args, '.opdf') then
|
||||
|
|
@ -2580,6 +2878,34 @@ begin
|
|||
Exit;
|
||||
end;
|
||||
|
||||
{ .fill <count>, <size>, <value> — emit <count> copies of a <size>-byte
|
||||
little-endian <value>. Used for SIMD threshold constant blocks
|
||||
(e.g. `.fill 32, 1, 0xBF`). size/value default to 1/0 if omitted (GNU as),
|
||||
but the RTL always supplies all three. }
|
||||
if Dir = '.fill' then
|
||||
begin
|
||||
P := 0;
|
||||
Len := Length(Args);
|
||||
while (P < Len) and ((Args[P] = Ord(' ')) or (Args[P] = Ord(#9))) do P := P + 1;
|
||||
Val := ParseInt(Args, P); { count }
|
||||
Count := Integer(Val);
|
||||
Size := 1; FillVal := 0;
|
||||
while (P < Len) and ((Args[P] = Ord(' ')) or (Args[P] = Ord(#9)) or
|
||||
(Args[P] = Ord(','))) do P := P + 1;
|
||||
if P < Len then
|
||||
begin
|
||||
Size := Integer(ParseInt(Args, P)); { size }
|
||||
while (P < Len) and ((Args[P] = Ord(' ')) or (Args[P] = Ord(#9)) or
|
||||
(Args[P] = Ord(','))) do P := P + 1;
|
||||
if P < Len then
|
||||
FillVal := Integer(ParseInt(Args, P)); { value }
|
||||
end;
|
||||
for I := 0 to Count - 1 do
|
||||
for J := 0 to Size - 1 do
|
||||
AWriter.AppendByte(ASection, (FillVal shr (J * 8)) and $FF);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ .int is an alias for .long: GNU as treats both as a 32-bit value on
|
||||
x86-64. The OPDF debug emitter uses .int, so accept it here. }
|
||||
if (Dir = '.long') or (Dir = '.int') then
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ type
|
|||
procedure TestEndbr64AndHlt;
|
||||
procedure TestSyscall_0F05;
|
||||
procedure TestCallPltStripsSuffix;
|
||||
procedure TestSse2_PxorAndPabsb;
|
||||
procedure TestVex_Vpxor2byte;
|
||||
procedure TestVex_Vpabsb3byte;
|
||||
procedure TestVex_VextractI128;
|
||||
procedure TestSibWhitespaceTolerated;
|
||||
end;
|
||||
|
||||
{ ---- ELF writer unit tests ---- }
|
||||
|
|
@ -245,6 +250,65 @@ begin
|
|||
not ContainsBytes(Obj, 'foo@PLT'));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestSse2_PxorAndPabsb;
|
||||
var
|
||||
Obj: string;
|
||||
begin
|
||||
{ pxor %xmm3,%xmm3 -> 66 0f ef db ; pabsb is a 3-byte 0F38 op:
|
||||
pabsb %xmm2,%xmm2 -> 66 0f 38 1c d2. Verified vs cc. }
|
||||
Obj := AssembleToBytes('pxor %xmm3, %xmm3' + LineEnding);
|
||||
AssertTrue('66 0f ef db missing',
|
||||
ContainsBytes(Obj, Chr($66) + Chr($0F) + Chr($EF) + Chr($DB)));
|
||||
Obj := AssembleToBytes('pabsb %xmm2, %xmm2' + LineEnding);
|
||||
AssertTrue('66 0f 38 1c d2 missing',
|
||||
ContainsBytes(Obj, Chr($66) + Chr($0F) + Chr($38) + Chr($1C) + Chr($D2)));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestVex_Vpxor2byte;
|
||||
var
|
||||
Obj: string;
|
||||
begin
|
||||
{ vpxor %ymm3,%ymm3,%ymm3 -> c5 e5 ef db (2-byte VEX). Verified vs cc. }
|
||||
Obj := AssembleToBytes('vpxor %ymm3, %ymm3, %ymm3' + LineEnding);
|
||||
AssertTrue('c5 e5 ef db missing',
|
||||
ContainsBytes(Obj, Chr($C5) + Chr($E5) + Chr($EF) + Chr($DB)));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestVex_Vpabsb3byte;
|
||||
var
|
||||
Obj: string;
|
||||
begin
|
||||
{ vpabsb %ymm2,%ymm2 -> c4 e2 7d 1c d2 (3-byte VEX, 0F38 map). Verified vs cc. }
|
||||
Obj := AssembleToBytes('vpabsb %ymm2, %ymm2' + LineEnding);
|
||||
AssertTrue('c4 e2 7d 1c d2 missing',
|
||||
ContainsBytes(Obj, Chr($C4) + Chr($E2) + Chr($7D) + Chr($1C) + Chr($D2)));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestVex_VextractI128;
|
||||
var
|
||||
Obj: string;
|
||||
begin
|
||||
{ vextracti128 $1,%ymm3,%xmm4 -> c4 e3 7d 39 dc 01 (L=1 despite xmm dst).
|
||||
Verified vs cc. }
|
||||
Obj := AssembleToBytes('vextracti128 $1, %ymm3, %xmm4' + LineEnding);
|
||||
AssertTrue('c4 e3 7d 39 dc 01 missing',
|
||||
ContainsBytes(Obj, Chr($C4) + Chr($E3) + Chr($7D) + Chr($39) + Chr($DC) + Chr($01)));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestSibWhitespaceTolerated;
|
||||
var
|
||||
A, B: string;
|
||||
begin
|
||||
{ GNU as allows whitespace after the SIB comma: `(%rdi, %rdx)` must encode
|
||||
identically to `(%rdi,%rdx)` (movdqu -> f3 0f 6f 04 17). }
|
||||
A := AssembleToBytes('movdqu (%rdi,%rdx), %xmm0' + LineEnding);
|
||||
B := AssembleToBytes('movdqu (%rdi, %rdx), %xmm0' + LineEnding);
|
||||
AssertTrue('f3 0f 6f 04 17 missing (no space)',
|
||||
ContainsBytes(A, Chr($F3) + Chr($0F) + Chr($6F) + Chr($04) + Chr($17)));
|
||||
AssertTrue('f3 0f 6f 04 17 missing (with space)',
|
||||
ContainsBytes(B, Chr($F3) + Chr($0F) + Chr($6F) + Chr($04) + Chr($17)));
|
||||
end;
|
||||
|
||||
procedure TAsmEncodingTests.TestQuadSymbol_EmitsReloc;
|
||||
var
|
||||
Obj: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue