fix(parser): fold a signed parenthesised displacement expression
Test / test (push) Failing after 2m21s
Test / test (push) Failing after 2m21s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+64
-6
@@ -5,6 +5,7 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -299,6 +300,10 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// The pooled $i64 constants the wide MOV immediate loads refer to join
|
||||||
|
// the declared data as read-only symbols, deduplicated across the file
|
||||||
|
// (the toolchain synthesises the same symbols into its rodata).
|
||||||
|
litSeen := map[string]bool{}
|
||||||
|
|
||||||
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
@@ -306,10 +311,23 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
code, labels, relocs, lines, spadj, err := assembleRISCV(t)
|
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
||||||
}
|
}
|
||||||
|
for _, lit := range lits {
|
||||||
|
if litSeen[lit.Name] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
litSeen[lit.Name] = true
|
||||||
|
dataSyms = append(dataSyms, dataSym{
|
||||||
|
name: lit.Name,
|
||||||
|
buf: lit.Data,
|
||||||
|
size: len(lit.Data),
|
||||||
|
rodata: true,
|
||||||
|
dupok: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
fl := FuncLayout{
|
fl := FuncLayout{
|
||||||
Name: t.Name.Name,
|
Name: t.Name.Name,
|
||||||
Pkg: t.Name.Pkg,
|
Pkg: t.Name.Pkg,
|
||||||
@@ -561,11 +579,6 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
|
return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
|
||||||
}
|
}
|
||||||
w := dd.Width
|
w := dd.Width
|
||||||
switch w {
|
|
||||||
case 1, 2, 4, 8:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
|
||||||
}
|
|
||||||
off := dd.Name.Offset
|
off := dd.Name.Offset
|
||||||
buf := syms[i].buf
|
buf := syms[i].buf
|
||||||
if off < 0 || off+int64(w) > int64(len(buf)) {
|
if off < 0 || off+int64(w) > int64(len(buf)) {
|
||||||
@@ -587,9 +600,54 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// A string or rune value ("DATA s+0(SB)/20, $"text"") writes its
|
||||||
|
// bytes into the field and leaves the rest zero, the toolchain's
|
||||||
|
// WriteString: the declared width must hold every byte, and any
|
||||||
|
// width is legal.
|
||||||
|
if s := dd.Value.Imm.Str; s != "" && !dd.Value.Imm.HasVal {
|
||||||
|
text, err := strconv.Unquote(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid string value %s", dd.Name.Name, s)
|
||||||
|
}
|
||||||
|
if len(text) > w {
|
||||||
|
return nil, fmt.Errorf("DATA %q: string of %d bytes does not fit width %d", dd.Name.Name, len(text), w)
|
||||||
|
}
|
||||||
|
copy(buf[off:], text)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// A floating-point value stores its IEEE-754 bits: /4 the float32
|
||||||
|
// rounding of the parsed double, /8 the full 64 bits, the
|
||||||
|
// toolchain's WriteFloat32 and WriteFloat64.
|
||||||
|
if f := dd.Value.Imm.Float; f != "" && !dd.Value.Imm.HasVal {
|
||||||
|
num, err := strconv.ParseFloat(f, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid floating-point value %q", dd.Name.Name, f)
|
||||||
|
}
|
||||||
|
if dd.Value.Imm.Neg {
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
var v uint64
|
||||||
|
switch w {
|
||||||
|
case 4:
|
||||||
|
v = uint64(math.Float32bits(float32(num)))
|
||||||
|
case 8:
|
||||||
|
v = math.Float64bits(num)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid width %d for a float (want 4 or 8)", dd.Name.Name, w)
|
||||||
|
}
|
||||||
|
for j := range w {
|
||||||
|
buf[off+int64(j)] = byte(v >> (8 * j))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !dd.Value.Imm.HasVal {
|
if !dd.Value.Imm.HasVal {
|
||||||
return nil, fmt.Errorf("DATA %q: value must be an integer immediate or a symbol address", dd.Name.Name)
|
return nil, fmt.Errorf("DATA %q: value must be an integer immediate or a symbol address", dd.Name.Name)
|
||||||
}
|
}
|
||||||
|
switch w {
|
||||||
|
case 1, 2, 4, 8:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
||||||
|
}
|
||||||
v := dd.Value.Imm.Val
|
v := dd.Value.Imm.Val
|
||||||
if dd.Value.Imm.Neg {
|
if dd.Value.Imm.Neg {
|
||||||
v = -v
|
v = -v
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -418,3 +419,75 @@ func main() {
|
|||||||
t.Fatalf("linked program failed: %v\n%s", err, out)
|
t.Fatalf("linked program failed: %v\n%s", err, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCollectDataFloatAndStringValues covers the non-integer DATA values the
|
||||||
|
// runtime's math and asm files use: floating-point initialisers store their
|
||||||
|
// IEEE-754 bits (/4 the float32 rounding, /8 the full double) and string
|
||||||
|
// initialisers write their bytes zero-padded within the declared width.
|
||||||
|
func TestCollectDataFloatAndStringValues(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
TEXT ·Keep(SB), NOSPLIT, $0-8
|
||||||
|
RET
|
||||||
|
GLOBL vals<>(SB), RODATA, $44
|
||||||
|
DATA vals<>+0(SB)/8, $0.5
|
||||||
|
DATA vals<>+8(SB)/8, $-1.0
|
||||||
|
DATA vals<>+16(SB)/4, $1.5
|
||||||
|
DATA vals<>+20(SB)/16, $"call frame too "
|
||||||
|
DATA vals<>+36(SB)/4, $"hi"
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("fvals_amd64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFile(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFile: %v", err)
|
||||||
|
}
|
||||||
|
byName := map[string]DataSymbol{}
|
||||||
|
for _, d := range img.DataSyms {
|
||||||
|
byName[d.Name] = d
|
||||||
|
}
|
||||||
|
d := byName["vals"]
|
||||||
|
if d.Size != 44 {
|
||||||
|
t.Fatalf("vals size = %d, want 44", d.Size)
|
||||||
|
}
|
||||||
|
buf := img.Data[d.Offset : d.Offset+44]
|
||||||
|
// 0.5 = 0x3FE0000000000000, -1.0 = 0xBFF0000000000000 (float64);
|
||||||
|
// 1.5 = 0x3FC00000 (float32).
|
||||||
|
for _, c := range []struct {
|
||||||
|
off int
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{0, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x3F}},
|
||||||
|
{8, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF}},
|
||||||
|
{16, []byte{0x00, 0x00, 0xC0, 0x3F}},
|
||||||
|
{20, []byte("call frame too ")},
|
||||||
|
{36, []byte{'h', 'i', 0x00, 0x00}},
|
||||||
|
} {
|
||||||
|
if string(buf[c.off:c.off+len(c.want)]) != string(c.want) {
|
||||||
|
t.Errorf("vals+%d: got % x, want % x", c.off, buf[c.off:c.off+len(c.want)], c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCollectDataValueErrors pins the value-kind width rules: a float needs
|
||||||
|
// width 4 or 8, a string must fit its declared width, and a bad float
|
||||||
|
// literal is diagnosed rather than stored.
|
||||||
|
func TestCollectDataValueErrors(t *testing.T) {
|
||||||
|
cases := []string{
|
||||||
|
`GLOBL v<>(SB), RODATA, $4
|
||||||
|
DATA v<>+0(SB)/1, $0.5`,
|
||||||
|
`GLOBL v<>(SB), RODATA, $2
|
||||||
|
DATA v<>+0(SB)/2, $"toolarge"`,
|
||||||
|
}
|
||||||
|
for i, src := range cases {
|
||||||
|
full := "#include \"textflag.h\"\nTEXT ·Keep(SB), NOSPLIT, $0-8\n\tRET\n" + src
|
||||||
|
f, errs := parser.Parse(fmt.Sprintf("verr%d_amd64.s", i), full)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("case %d parse: %v", i, errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFile(f); err == nil {
|
||||||
|
t.Errorf("case %d: expected an error, got none", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -552,6 +552,20 @@ func parseAddress(g []token.Token) ast.Address {
|
|||||||
i = len(g) - len(rest)
|
i = len(g) - len(rest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The same expression under a leading sign: -(24+8)(X6) puts the sign
|
||||||
|
// outside the fold. The base group must follow for the value to
|
||||||
|
// commit, exactly as in the unsigned branch above.
|
||||||
|
if i < len(g) && (g[i].Kind == token.Minus || g[i].Kind == token.Plus) &&
|
||||||
|
i+1 < len(g) && g[i+1].Kind == token.LParen {
|
||||||
|
if v, rest, ok := foldExpr(g[i+1:]); ok && len(rest) > 0 && rest[0].Kind == token.LParen {
|
||||||
|
if g[i].Kind == token.Minus {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
addr.Offset = v
|
||||||
|
addr.HasOff = true
|
||||||
|
i = len(g) - len(rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Optional leading displacement before a '(' base group. A sign pushes
|
// Optional leading displacement before a '(' base group. A sign pushes
|
||||||
// the parenthesis one token further out: -4(DX) has it at i+2.
|
// the parenthesis one token further out: -4(DX) has it at i+2.
|
||||||
if isSignedNumber(g, i) {
|
if isSignedNumber(g, i) {
|
||||||
|
|||||||
@@ -634,3 +634,27 @@ func TestBareTrailingImmediate(t *testing.T) {
|
|||||||
t.Errorf("jump target = %+v, want label loop", jmp.Operands[0])
|
t.Errorf("jump target = %+v, want label loop", jmp.Operands[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSignedParenDisplacement pins a sign before a parenthesised
|
||||||
|
// displacement expression: -(24+8)(X6) negates the folded value and keeps
|
||||||
|
// the base group, the shape GOROOT's riscv64 and loong64 files use.
|
||||||
|
func TestSignedParenDisplacement(t *testing.T) {
|
||||||
|
file, errs := Parse("t.s", "TEXT \u00b7f(SB), NOSPLIT, $0-0\n\tMOV X7, -(24+8)(X6)\n\tMOV X7, +(16)(X6)\n\tRET\n")
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
text := file.Decls[0].(*ast.Text)
|
||||||
|
ins := text.Body[0].(*ast.Instr)
|
||||||
|
op := ins.Operands[1] // Plan 9 order: the destination address is last
|
||||||
|
if !op.Addr.HasOff || op.Addr.Offset != -32 {
|
||||||
|
t.Errorf("-(24+8): offset = %v hasOff=%v, want -32 true", op.Addr.Offset, op.Addr.HasOff)
|
||||||
|
}
|
||||||
|
if op.Addr.Base != "X6" {
|
||||||
|
t.Errorf("-(24+8): base = %q, want X6", op.Addr.Base)
|
||||||
|
}
|
||||||
|
ins = text.Body[1].(*ast.Instr)
|
||||||
|
op = ins.Operands[1]
|
||||||
|
if !op.Addr.HasOff || op.Addr.Offset != 16 || op.Addr.Base != "X6" {
|
||||||
|
t.Errorf("+(16): offset = %v hasOff=%v base=%q, want 16 true X6", op.Addr.Offset, op.Addr.HasOff, op.Addr.Base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user