From 52fd1d5b3b7334d67622c866fa39dbf6c8e1821d Mon Sep 17 00:00:00 2001 From: Graeme Geldenhuys Date: Thu, 25 Jun 2026 12:54:18 +0100 Subject: [PATCH] feat(asm): SSE2 + AVX2/VEX encoders --- .../main/pascal/blaise.assembler.x86_64.pas | 352 +++++++++++++++++- .../src/test/pascal/cp.test.assembler.pas | 64 ++++ 2 files changed, 403 insertions(+), 13 deletions(-) diff --git a/compiler/src/main/pascal/blaise.assembler.x86_64.pas b/compiler/src/main/pascal/blaise.assembler.x86_64.pas index 5f08bee..dd54e01 100644 --- a/compiler/src/main/pascal/blaise.assembler.x86_64.pas +++ b/compiler/src/main/pascal/blaise.assembler.x86_64.pas @@ -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 , , — emit copies of a -byte + little-endian . 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 diff --git a/compiler/src/test/pascal/cp.test.assembler.pas b/compiler/src/test/pascal/cp.test.assembler.pas index cfcc79d..b932f23 100644 --- a/compiler/src/test/pascal/cp.test.assembler.pas +++ b/compiler/src/test/pascal/cp.test.assembler.pas @@ -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;