feat(parser): bracket register ranges, index-only VSIB and bare trailing immediates
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -497,3 +497,140 @@ func TestParseEqualsZeroOptions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBracketRegisterRange pins the amd64 multi-source operand of the
|
||||
// 4FMAPS/4VNNIW families: the bracket group [Z0-Z3] names four consecutive
|
||||
// source registers and must reach the AST as a register range instead of an
|
||||
// empty address.
|
||||
func TestBracketRegisterRange(t *testing.T) {
|
||||
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tV4FMADDPS 17(SP), [Z0-Z3], K2, Z0\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs)
|
||||
}
|
||||
fn := file.Decls[0].(*ast.Text)
|
||||
in := fn.Body[0].(*ast.Instr)
|
||||
if len(in.Operands) != 4 {
|
||||
t.Fatalf("operands = %d, want 4", len(in.Operands))
|
||||
}
|
||||
rng := in.Operands[1]
|
||||
if rng.Kind != ast.OpAddr {
|
||||
t.Errorf("range operand kind = %v, want OpAddr", rng.Kind)
|
||||
}
|
||||
if rng.Addr.Range == nil {
|
||||
t.Fatalf("range operand = %+v, want a register range", rng.Addr)
|
||||
}
|
||||
if rng.Addr.Range.Lo != "Z0" || rng.Addr.Range.Hi != "Z3" {
|
||||
t.Errorf("range = %s-%s, want Z0-Z3", rng.Addr.Range.Lo, rng.Addr.Range.Hi)
|
||||
}
|
||||
if rng.Addr.Sym != nil || rng.Addr.Base != "" || rng.Addr.Index != "" || rng.Addr.Shift != "" {
|
||||
t.Errorf("range operand carries stray address fields: %+v", rng.Addr)
|
||||
}
|
||||
if rng.Raw != "[ Z0 - Z3 ]" {
|
||||
t.Errorf("range raw = %q, want the verbatim spelling", rng.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBracketRegisterRangeNotList pins that arm64-style register lists, whose
|
||||
// members carry arrangements, stay out of the simple range shape: they remain
|
||||
// plain bracketed groups the arm64 encoder reads from Raw. A comma inside
|
||||
// brackets is a top-level comma, so a multi-member list spans several
|
||||
// operands, exactly the shape the arm64 encoder's list scan stitches back.
|
||||
func TestBracketRegisterRangeNotList(t *testing.T) {
|
||||
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tVLD1 (R2), [V21.B16]\n\tVLD1 (R1), [V2.B16, V3.B16]\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs)
|
||||
}
|
||||
fn := file.Decls[0].(*ast.Text)
|
||||
for i, want := range []string{"[ V21.B16 ]", "V3.B16 ]"} {
|
||||
in := fn.Body[i].(*ast.Instr)
|
||||
op := in.Operands[len(in.Operands)-1]
|
||||
if op.Addr.Range != nil {
|
||||
t.Errorf("%s: range = %v, want nil", in.Mnemonic.Text, op.Addr.Range)
|
||||
}
|
||||
if op.Raw != want {
|
||||
t.Errorf("operand %d raw = %q, want %q", i, op.Raw, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVSIBIndexOnly pins the gather/scatter memory operand with a scaled
|
||||
// vector index and no base register: 8(X4*1) must carry index and scale and
|
||||
// leave the base empty, not strand the scale in the shift suffix.
|
||||
func TestVSIBIndexOnly(t *testing.T) {
|
||||
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tVPGATHERDQ Y0, 8(X4*1), Y6\n\tVPGATHERDQ Y0, (X4*2), Y6\n\tVPGATHERDQ Y0, -8(X4*1), Y6\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs)
|
||||
}
|
||||
fn := file.Decls[0].(*ast.Text)
|
||||
want := []ast.Address{
|
||||
{Index: "X4", Scale: 1, Offset: 8, HasOff: true},
|
||||
{Index: "X4", Scale: 2},
|
||||
{Index: "X4", Scale: 1, Offset: -8, HasOff: true},
|
||||
}
|
||||
for i, w := range want {
|
||||
in := fn.Body[i].(*ast.Instr)
|
||||
a := in.Operands[1].Addr
|
||||
if a.Base != "" || a.Index != w.Index || a.Scale != w.Scale || a.Offset != w.Offset || a.HasOff != w.HasOff || a.Shift != "" {
|
||||
t.Errorf("operand %d = %+v, want %+v", i, a, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVSIBTwoGroupKeepsBase pins that the ordinary (base)(index*scale)
|
||||
// grammar is untouched by the index-only recognition.
|
||||
func TestVSIBTwoGroupKeepsBase(t *testing.T) {
|
||||
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tVP4DPWSSD 7(SI)(DI*1), [Z2-Z5], K4, Z17\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs)
|
||||
}
|
||||
fn := file.Decls[0].(*ast.Text)
|
||||
in := fn.Body[0].(*ast.Instr)
|
||||
a := in.Operands[0].Addr
|
||||
if a.Base != "SI" || a.Index != "DI" || a.Scale != 1 || a.Offset != 7 || !a.HasOff {
|
||||
t.Errorf("address = %+v, want base SI index DI scale 1 offset 7", a)
|
||||
}
|
||||
if in.Operands[1].Addr.Range == nil || in.Operands[1].Addr.Range.Lo != "Z2" || in.Operands[1].Addr.Range.Hi != "Z5" {
|
||||
t.Errorf("second operand = %+v, want range Z2-Z5", in.Operands[1].Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBareTrailingImmediate pins the toolchain's bare constant spelling in
|
||||
// the final operand slot: CMPSD X1, X0, 1 reads as $1 (math/floor_amd64.s).
|
||||
// Earlier slots keep the strict grammar, so a bare number there stays an
|
||||
// address rather than becoming an immediate.
|
||||
func TestBareTrailingImmediate(t *testing.T) {
|
||||
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tCMPSD X1, X0, 1\n\tCMPSD X1, X0, -1\n\tADDQ AX, 1+2\n\tRET\n")
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs)
|
||||
}
|
||||
fn := file.Decls[0].(*ast.Text)
|
||||
for i, want := range []int64{1, -1, 3} {
|
||||
in := fn.Body[i].(*ast.Instr)
|
||||
last := in.Operands[len(in.Operands)-1]
|
||||
if last.Kind != ast.OpImmediate || !last.Imm.HasVal || last.Imm.Val != want {
|
||||
t.Errorf("operand %d = %+v, want immediate %d", i, last, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A bare number outside the final slot is not an immediate.
|
||||
file2, errs2 := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tADDQ 1, AX\n\tRET\n")
|
||||
if len(errs2) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs2)
|
||||
}
|
||||
fn2 := file2.Decls[0].(*ast.Text)
|
||||
first := fn2.Body[0].(*ast.Instr).Operands[0]
|
||||
if first.Kind != ast.OpAddr {
|
||||
t.Errorf("non-final bare number kind = %v, want OpAddr", first.Kind)
|
||||
}
|
||||
// A bare name in the final slot stays a symbol: labels are names, not
|
||||
// constants, and jump targets depend on the distinction.
|
||||
file3, errs3 := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tJMP loop\nloop: NOP\n\tRET\n")
|
||||
if len(errs3) > 0 {
|
||||
t.Fatalf("parse errors: %v", errs3)
|
||||
}
|
||||
fn3 := file3.Decls[0].(*ast.Text)
|
||||
jmp := fn3.Body[0].(*ast.Instr)
|
||||
if jmp.Operands[0].Kind != ast.OpAddr || jmp.Operands[0].Addr.Sym == nil || jmp.Operands[0].Addr.Sym.Name != "loop" {
|
||||
t.Errorf("jump target = %+v, want label loop", jmp.Operands[0])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user