feat(asm): unsuffixed mnemonics, mem-indirect jmp, endbr64/hlt/syscall
This commit is contained in:
parent
fe7b016119
commit
c673fc21f1
|
|
@ -163,6 +163,7 @@ type
|
||||||
Sym: string; { symbol name (for RIP-rel, labels, TLS) }
|
Sym: string; { symbol name (for RIP-rel, labels, TLS) }
|
||||||
SymDisp: Int64; { addend to symbol (sym + N) }
|
SymDisp: Int64; { addend to symbol (sym + N) }
|
||||||
TpOff: Boolean; { sym@tpoff — emit R_X86_64_TPOFF32 relocation }
|
TpOff: Boolean; { sym@tpoff — emit R_X86_64_TPOFF32 relocation }
|
||||||
|
Indirect: Boolean; { memory operand reached via *mem (call/jmp *disp(%reg)) }
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Parsed line types }
|
{ Parsed line types }
|
||||||
|
|
@ -342,6 +343,7 @@ begin
|
||||||
Result.Scale := 1;
|
Result.Scale := 1;
|
||||||
Result.Sym := '';
|
Result.Sym := '';
|
||||||
Result.SymDisp := 0;
|
Result.SymDisp := 0;
|
||||||
|
Result.Indirect := False;
|
||||||
|
|
||||||
P := APos;
|
P := APos;
|
||||||
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do
|
while (P < Length(S)) and ((S[P]= Ord(' ')) or (S[P]= 9)) do
|
||||||
|
|
@ -353,7 +355,8 @@ begin
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Indirect register: *%reg }
|
{ Indirect operand: *%reg (register-indirect) or *disp(%reg) (memory-
|
||||||
|
indirect), used by `call`/`jmp` through a pointer. }
|
||||||
if S[P]= Ord('*') then
|
if S[P]= Ord('*') then
|
||||||
begin
|
begin
|
||||||
P := P + 1;
|
P := P + 1;
|
||||||
|
|
@ -371,6 +374,11 @@ begin
|
||||||
AEnd := P;
|
AEnd := P;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
{ *disp(%reg): parse the remainder as an ordinary memory operand and flag
|
||||||
|
it indirect so the branch encoder uses FF /4 with that memory ModRM. }
|
||||||
|
Result := ParseOperand(S, P, AEnd);
|
||||||
|
Result.Indirect := True;
|
||||||
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ Immediate: $N }
|
{ Immediate: $N }
|
||||||
|
|
@ -1244,6 +1252,45 @@ begin
|
||||||
Result := (AWidth = 8) and (ARegCode >= 4) and (ARegCode <= 7);
|
Result := (AWidth = 8) and (ARegCode >= 4) and (ARegCode <= 7);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ Map an unsuffixed AT&T mnemonic (mov/lea/xor/…) to its size-suffixed form,
|
||||||
|
inferring the operand size from a register operand's width (GNU as does the
|
||||||
|
same). Mnemonics that already carry a suffix, and ones that are size-fixed in
|
||||||
|
long mode (push/pop/call/jmp are 64-bit), pass through. 64-bit is the default
|
||||||
|
when no register operand pins the width. }
|
||||||
|
function NormalizeMnemonic(const AMnem: string;
|
||||||
|
const AOp1, AOp2: TOperand): string;
|
||||||
|
var
|
||||||
|
W: Integer;
|
||||||
|
Suffix: string;
|
||||||
|
begin
|
||||||
|
Result := AMnem;
|
||||||
|
{ Width from whichever operand is a register (Op2 first — it is the
|
||||||
|
destination in AT&T two-operand forms). }
|
||||||
|
W := 0;
|
||||||
|
if AOp2.Kind = opReg then W := AOp2.RegW
|
||||||
|
else if AOp1.Kind = opReg then W := AOp1.RegW;
|
||||||
|
if W = 64 then Suffix := 'q'
|
||||||
|
else if W = 32 then Suffix := 'l'
|
||||||
|
else if W = 16 then Suffix := 'w'
|
||||||
|
else if W = 8 then Suffix := 'b'
|
||||||
|
else Suffix := 'q'; { default 64-bit when no register pins the size }
|
||||||
|
|
||||||
|
{ Size-inferred integer ALU/data mnemonics. }
|
||||||
|
if (AMnem = 'mov') or (AMnem = 'xor') or (AMnem = 'and') or (AMnem = 'or') or
|
||||||
|
(AMnem = 'add') or (AMnem = 'sub') or (AMnem = 'cmp') or (AMnem = 'test') or
|
||||||
|
(AMnem = 'inc') or (AMnem = 'dec') or (AMnem = 'neg') or (AMnem = 'not') or
|
||||||
|
(AMnem = 'imul') or (AMnem = 'shl') or (AMnem = 'shr') then
|
||||||
|
Result := AMnem + Suffix
|
||||||
|
{ lea's size is the destination pointer width — always 64-bit here. }
|
||||||
|
else if AMnem = 'lea' then
|
||||||
|
Result := 'leaq'
|
||||||
|
{ Stack/branch ops are 64-bit-only in long mode. }
|
||||||
|
else if AMnem = 'push' then Result := 'pushq'
|
||||||
|
else if AMnem = 'pop' then Result := 'popq'
|
||||||
|
else if AMnem = 'call' then Result := 'callq';
|
||||||
|
{ 'jmp' keeps its name (the encoder already handles jmp incl. indirect). }
|
||||||
|
end;
|
||||||
|
|
||||||
{ ---- Main instruction encoder ----------------------------------------- }
|
{ ---- Main instruction encoder ----------------------------------------- }
|
||||||
|
|
||||||
function EncodeInstruction(var ACtx: TEncodeContext;
|
function EncodeInstruction(var ACtx: TEncodeContext;
|
||||||
|
|
@ -1261,9 +1308,10 @@ var
|
||||||
begin
|
begin
|
||||||
CBInit(CB);
|
CBInit(CB);
|
||||||
ACtx.ImmTail := 0;
|
ACtx.ImmTail := 0;
|
||||||
Mnem := AParsed.Mnemonic;
|
|
||||||
Op1 := AParsed.Op1;
|
Op1 := AParsed.Op1;
|
||||||
Op2 := AParsed.Op2;
|
Op2 := AParsed.Op2;
|
||||||
|
{ Accept unsuffixed AT&T mnemonics (mov/lea/xor/…) by inferring the size. }
|
||||||
|
Mnem := NormalizeMnemonic(AParsed.Mnemonic, Op1, Op2);
|
||||||
|
|
||||||
{ ---- lock prefix (F0) — emitted ahead of the instruction it guards ---- }
|
{ ---- lock prefix (F0) — emitted ahead of the instruction it guards ---- }
|
||||||
if AParsed.HasLock then
|
if AParsed.HasLock then
|
||||||
|
|
@ -1277,6 +1325,30 @@ begin
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{ ---- endbr64 (CET) ---- F3 0F 1E FA }
|
||||||
|
if Mnem = 'endbr64' then
|
||||||
|
begin
|
||||||
|
CBEmit(CB, $F3); CBEmit(CB, $0F); CBEmit(CB, $1E); CBEmit(CB, $FA);
|
||||||
|
Result := CB.Data;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ ---- hlt ---- F4 }
|
||||||
|
if Mnem = 'hlt' then
|
||||||
|
begin
|
||||||
|
CBEmit(CB, $F4);
|
||||||
|
Result := CB.Data;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{ ---- syscall ---- 0F 05 }
|
||||||
|
if Mnem = 'syscall' then
|
||||||
|
begin
|
||||||
|
CBEmit(CB, $0F); CBEmit(CB, $05);
|
||||||
|
Result := CB.Data;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
{ ---- leave ---- }
|
{ ---- leave ---- }
|
||||||
if Mnem = 'leave' then
|
if Mnem = 'leave' then
|
||||||
begin
|
begin
|
||||||
|
|
@ -1433,10 +1505,25 @@ begin
|
||||||
end
|
end
|
||||||
else if (Op1.Kind = opIndirect) then
|
else if (Op1.Kind = opIndirect) then
|
||||||
begin
|
begin
|
||||||
|
{ Register-indirect: jmp *%reg / call *%reg. FF /4 (jmp) or /2 (call). }
|
||||||
if Op1.Reg >= 8 then
|
if Op1.Reg >= 8 then
|
||||||
CBEmit(CB, MakeRex(False, False, False, True));
|
CBEmit(CB, MakeRex(False, False, False, True));
|
||||||
CBEmit(CB, $FF);
|
CBEmit(CB, $FF);
|
||||||
CBEmit(CB, MakeModRM(3, 4, Op1.Reg));
|
if Mnem = 'jmp' then
|
||||||
|
CBEmit(CB, MakeModRM(3, 4, Op1.Reg))
|
||||||
|
else
|
||||||
|
CBEmit(CB, MakeModRM(3, 2, Op1.Reg));
|
||||||
|
end
|
||||||
|
else if IsMemLike(Op1) and Op1.Indirect then
|
||||||
|
begin
|
||||||
|
{ Memory-indirect: jmp *disp(%reg) / call *disp(%reg). FF /4 or /2 with
|
||||||
|
the memory ModRM. }
|
||||||
|
EmitRexForMem(CB, False, 0, Op1, False);
|
||||||
|
CBEmit(CB, $FF);
|
||||||
|
if Mnem = 'jmp' then
|
||||||
|
EncodeMemOperand(CB, ACtx, Op1, 4)
|
||||||
|
else
|
||||||
|
EncodeMemOperand(CB, ACtx, Op1, 2);
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise EAssembler.Create(Mnem + ': unsupported operand');
|
raise EAssembler.Create(Mnem + ': unsupported operand');
|
||||||
|
|
@ -1546,22 +1633,24 @@ begin
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ ---- incq / decq ---- }
|
{ ---- incq / decq ---- }
|
||||||
if (Mnem = 'incq') or (Mnem = 'decq') then
|
if (Mnem = 'incq') or (Mnem = 'decq') or
|
||||||
|
(Mnem = 'incl') or (Mnem = 'decl') then
|
||||||
begin
|
begin
|
||||||
|
{ FF /0 = inc, /1 = dec; REX.W only for the 'q' (64-bit) forms. }
|
||||||
if Op1.Kind = opReg then
|
if Op1.Kind = opReg then
|
||||||
begin
|
begin
|
||||||
EmitRexIfNeeded(CB, True, 0, Op1.Reg, False);
|
EmitRexIfNeeded(CB, (Mnem = 'incq') or (Mnem = 'decq'), 0, Op1.Reg, False);
|
||||||
CBEmit(CB, $FF);
|
CBEmit(CB, $FF);
|
||||||
if Mnem = 'incq' then
|
if (Mnem = 'incq') or (Mnem = 'incl') then
|
||||||
CBEmit(CB, MakeModRM(3, 0, Op1.Reg))
|
CBEmit(CB, MakeModRM(3, 0, Op1.Reg))
|
||||||
else
|
else
|
||||||
CBEmit(CB, MakeModRM(3, 1, Op1.Reg));
|
CBEmit(CB, MakeModRM(3, 1, Op1.Reg));
|
||||||
end
|
end
|
||||||
else if IsMemLike(Op1) then
|
else if IsMemLike(Op1) then
|
||||||
begin
|
begin
|
||||||
EmitRexForMem(CB, True, 0, Op1, False);
|
EmitRexForMem(CB, (Mnem = 'incq') or (Mnem = 'decq'), 0, Op1, False);
|
||||||
CBEmit(CB, $FF);
|
CBEmit(CB, $FF);
|
||||||
if Mnem = 'incq' then
|
if (Mnem = 'incq') or (Mnem = 'incl') then
|
||||||
EncodeMemOperand(CB, ACtx, Op1, 0)
|
EncodeMemOperand(CB, ACtx, Op1, 0)
|
||||||
else
|
else
|
||||||
EncodeMemOperand(CB, ACtx, Op1, 1);
|
EncodeMemOperand(CB, ACtx, Op1, 1);
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ type
|
||||||
procedure TestMovqRegToXmm_SseEncoding;
|
procedure TestMovqRegToXmm_SseEncoding;
|
||||||
procedure TestNegl_F7Slash3;
|
procedure TestNegl_F7Slash3;
|
||||||
procedure TestLockXaddl_AtomicEncoding;
|
procedure TestLockXaddl_AtomicEncoding;
|
||||||
|
procedure TestUnsuffixedMov_InfersSize;
|
||||||
|
procedure TestJmpIndirectMem_FFSlash4;
|
||||||
|
procedure TestEndbr64AndHlt;
|
||||||
|
procedure TestSyscall_0F05;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ ---- ELF writer unit tests ---- }
|
{ ---- ELF writer unit tests ---- }
|
||||||
|
|
@ -172,6 +176,52 @@ begin
|
||||||
ContainsBytes(Obj, Chr($F0) + Chr($0F) + Chr($C1) + Chr($07)));
|
ContainsBytes(Obj, Chr($F0) + Chr($0F) + Chr($C1) + Chr($07)));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TAsmEncodingTests.TestUnsuffixedMov_InfersSize;
|
||||||
|
var
|
||||||
|
Obj: string;
|
||||||
|
begin
|
||||||
|
{ Unsuffixed AT&T mnemonics infer size from the register operand.
|
||||||
|
mov %rbx, (%rdi) is 64-bit -> 48 89 1f (same as movq). Verified vs cc. }
|
||||||
|
Obj := AssembleToBytes('mov %rbx, (%rdi)' + LineEnding);
|
||||||
|
AssertTrue('48 89 1f missing (mov inferred q)',
|
||||||
|
ContainsBytes(Obj, Chr($48) + Chr($89) + Chr($1F)));
|
||||||
|
{ xor %eax, %eax is 32-bit -> 31 c0 (no REX.W). }
|
||||||
|
Obj := AssembleToBytes('xor %eax, %eax' + LineEnding);
|
||||||
|
AssertTrue('31 c0 missing (xor inferred l)',
|
||||||
|
ContainsBytes(Obj, Chr($31) + Chr($C0)));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAsmEncodingTests.TestJmpIndirectMem_FFSlash4;
|
||||||
|
var
|
||||||
|
Obj: string;
|
||||||
|
begin
|
||||||
|
{ jmp *56(%rdi) -> ff 67 38 (FF /4, memory-indirect). The longjmp tail. }
|
||||||
|
Obj := AssembleToBytes('jmp *56(%rdi)' + LineEnding);
|
||||||
|
AssertTrue('ff 67 38 missing',
|
||||||
|
ContainsBytes(Obj, Chr($FF) + Chr($67) + Chr($38)));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAsmEncodingTests.TestEndbr64AndHlt;
|
||||||
|
var
|
||||||
|
Obj: string;
|
||||||
|
begin
|
||||||
|
{ endbr64 -> f3 0f 1e fa ; hlt -> f4 }
|
||||||
|
Obj := AssembleToBytes('endbr64' + LineEnding);
|
||||||
|
AssertTrue('f3 0f 1e fa missing',
|
||||||
|
ContainsBytes(Obj, Chr($F3) + Chr($0F) + Chr($1E) + Chr($FA)));
|
||||||
|
Obj := AssembleToBytes('hlt' + LineEnding);
|
||||||
|
AssertTrue('f4 missing', ContainsBytes(Obj, Chr($F4)));
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TAsmEncodingTests.TestSyscall_0F05;
|
||||||
|
var
|
||||||
|
Obj: string;
|
||||||
|
begin
|
||||||
|
{ syscall -> 0f 05 (needed for the FreeBSD direct-syscall stubs). }
|
||||||
|
Obj := AssembleToBytes('syscall' + LineEnding);
|
||||||
|
AssertTrue('0f 05 missing', ContainsBytes(Obj, Chr($0F) + Chr($05)));
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TAsmEncodingTests.TestQuadSymbol_EmitsReloc;
|
procedure TAsmEncodingTests.TestQuadSymbol_EmitsReloc;
|
||||||
var
|
var
|
||||||
Obj: string;
|
Obj: string;
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ var
|
||||||
I: Integer;
|
I: Integer;
|
||||||
M: TArchiveMember;
|
M: TArchiveMember;
|
||||||
Obj: TElfObjectFile;
|
Obj: TElfObjectFile;
|
||||||
SawSetjmp: Boolean;
|
SawLongName: Boolean;
|
||||||
begin
|
begin
|
||||||
RTLPath := ProjectRoot() + 'compiler/target/blaise_rtl.a';
|
RTLPath := ProjectRoot() + 'compiler/target/blaise_rtl.a';
|
||||||
if not FileExists(RTLPath) then
|
if not FileExists(RTLPath) then
|
||||||
|
|
@ -348,12 +348,14 @@ begin
|
||||||
ReadArchiveFile(RTLPath, Members);
|
ReadArchiveFile(RTLPath, Members);
|
||||||
AssertTrue('expected several RTL members, got '
|
AssertTrue('expected several RTL members, got '
|
||||||
+ IntToStr(Members.Count), Members.Count >= 5);
|
+ IntToStr(Members.Count), Members.Count >= 5);
|
||||||
SawSetjmp := False;
|
{ A member whose name exceeds 15 chars exercises the GNU long-name (//)
|
||||||
|
table. rtl_platform_layout_linux.o is the longest current RTL member. }
|
||||||
|
SawLongName := False;
|
||||||
for I := 0 to Members.Count - 1 do
|
for I := 0 to Members.Count - 1 do
|
||||||
begin
|
begin
|
||||||
M := Members.Get(I);
|
M := Members.Get(I);
|
||||||
if M.Name = 'blaise_setjmp_x86_64.o' then
|
if M.Name = 'rtl_platform_layout_linux.o' then
|
||||||
SawSetjmp := True;
|
SawLongName := True;
|
||||||
{ Every member must parse as a valid x86-64 relocatable object
|
{ Every member must parse as a valid x86-64 relocatable object
|
||||||
with at least its NULL section + one real section. }
|
with at least its NULL section + one real section. }
|
||||||
Obj := ParseElfObject(M.Data, M.Name);
|
Obj := ParseElfObject(M.Data, M.Name);
|
||||||
|
|
@ -364,8 +366,8 @@ begin
|
||||||
Obj.Free();
|
Obj.Free();
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
AssertTrue('long-named member blaise_setjmp_x86_64.o not found '
|
AssertTrue('long-named member rtl_platform_layout_linux.o not found '
|
||||||
+ '(GNU long-name table mishandled?)', SawSetjmp);
|
+ '(GNU long-name table mishandled?)', SawLongName);
|
||||||
finally
|
finally
|
||||||
for I := 0 to Members.Count - 1 do
|
for I := 0 to Members.Count - 1 do
|
||||||
Members.Get(I).Free();
|
Members.Get(I).Free();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue