feat(asm): materialise frame-relative addresses the way the toolchain does
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
1120a52a54
commit
c1cef7b6e8
4 files changed
+394
-13
No files matched your search
+103
-6
@@ -791,7 +791,7 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
|||||||
// ADR/ADRP: (label, Rd) with the byte distance split into immlo and
|
// ADR/ADRP: (label, Rd) with the byte distance split into immlo and
|
||||||
// immhi.
|
// immhi.
|
||||||
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FADR {
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FADR {
|
||||||
return encodeARM64ADR(mnem, enc.op, ops, pc, offsets, resolve)
|
return encodeARM64ADR(mnem, enc.op, ops, pc, offsets)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bitfield extract with wrapping immr: UBFX, SBFX.
|
// Bitfield extract with wrapping immr: UBFX, SBFX.
|
||||||
@@ -1897,6 +1897,13 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
|
|||||||
}
|
}
|
||||||
return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil
|
return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil
|
||||||
}
|
}
|
||||||
|
// Frame-relative immediate address: MOVD $sym+off(FP|SP), Rd
|
||||||
|
// materialises the address with ADD/SUB from the hardware SP
|
||||||
|
// (asm7.go case 4), the shape every runtime address-of-argument
|
||||||
|
// load is written in.
|
||||||
|
if src.Imm.Sym != nil && (src.Imm.Sym.Pseudo == "FP" || src.Imm.Sym.Pseudo == "SP") {
|
||||||
|
return encodeARM64FrameAddr(src.Imm.Sym, dst, mnem, fi)
|
||||||
|
}
|
||||||
rd := arm64RegNum(operandRegName(dst))
|
rd := arm64RegNum(operandRegName(dst))
|
||||||
// The FP immediates: FMOVS/FMOVD $f, Fd ride the FMOV (immediate)
|
// The FP immediates: FMOVS/FMOVD $f, Fd ride the FMOV (immediate)
|
||||||
// instruction when the 8-bit field carries the value and the
|
// instruction when the 8-bit field carries the value and the
|
||||||
@@ -2066,6 +2073,21 @@ func arm64MovSize(mnem string, ops []*ast.Operand, fi arm64FrameInfo) int {
|
|||||||
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
||||||
return 8 // ADRP + ADD
|
return 8 // ADRP + ADD
|
||||||
}
|
}
|
||||||
|
// The frame-relative immediate address: one ADD/SUB imm12 word in
|
||||||
|
// the addcon band, the hi<<12 plus lo pair in the 24-bit band, and
|
||||||
|
// an error (so an irrelevant size) for the pool form beyond either.
|
||||||
|
if src.Imm.Sym != nil && (src.Imm.Sym.Pseudo == "FP" || src.Imm.Sym.Pseudo == "SP") {
|
||||||
|
v := arm64FrameAddrValue(src.Imm.Sym, fi)
|
||||||
|
switch {
|
||||||
|
case mnem != "MOVD" && mnem != "MOV",
|
||||||
|
!arm64IsAddcon(v) && !arm64IsAddcon(-v) && (v < 0 || v > 0xFFFFFF):
|
||||||
|
return 4
|
||||||
|
case arm64IsAddcon(v) || arm64IsAddcon(-v):
|
||||||
|
return 4
|
||||||
|
default:
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
}
|
||||||
// The con(register) form lowers to the toolchain's ADD/SUB chain:
|
// The con(register) form lowers to the toolchain's ADD/SUB chain:
|
||||||
// one word in the addcon band, two in the 24-bit band, and the two
|
// one word in the addcon band, two in the 24-bit band, and the two
|
||||||
// pool words (LDR X plus the UXTX add) beyond it.
|
// pool words (LDR X plus the UXTX add) beyond it.
|
||||||
@@ -2332,6 +2354,17 @@ func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
|||||||
// (SXTB, SXTH, SXTW), the unsigned byte and halfword forms to UBFM
|
// (SXTB, SXTH, SXTW), the unsigned byte and halfword forms to UBFM
|
||||||
// (UXTB, UXTH), and only MOVWU to an ORR against WZR. MOVD stays
|
// (UXTB, UXTH), and only MOVWU to an ORR against WZR. MOVD stays
|
||||||
// ORR Xd, XZR, Xm.
|
// ORR Xd, XZR, Xm.
|
||||||
|
//
|
||||||
|
// The SP register moves ride the ADD (immediate) form instead: ORR
|
||||||
|
// cannot address SP and register 31 encodes ZR there (asm7.go case
|
||||||
|
// 24), whose C_RSP row exists for MOVD alone, so every other width is
|
||||||
|
// an illegal combination.
|
||||||
|
if sn, dn := operandRegName(src), operandRegName(dst); sn == "RSP" || sn == "SP" || dn == "RSP" || dn == "SP" {
|
||||||
|
if mnem != "MOVD" && mnem != "MOV" {
|
||||||
|
return nil, fmt.Errorf("%s: illegal combination: the SP register move exists for MOVD only", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(a64AddSub(1, 0, 0, 0, 0, uint32(rs), uint32(rd))), nil
|
||||||
|
}
|
||||||
if rs != 31 {
|
if rs != 31 {
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "MOVB":
|
case "MOVB":
|
||||||
@@ -2770,6 +2803,28 @@ func encodeARM64SBAddr(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encodeARM64FrameAddr lowers MOVD $sym+off(FP) and its SP spelling: the
|
||||||
|
// address rides ADD/SUB from the hardware SP, one imm12 word inside the
|
||||||
|
// addcon band and the hi<<12 plus lo pair inside the 24-bit band (asm7.go
|
||||||
|
// optab case 4). Beyond either the toolchain pools the constant (case 34),
|
||||||
|
// and no pool the plain form reaches exists here, so that shape is refused.
|
||||||
|
// Every other width is an illegal combination: the toolchain's AACON rows
|
||||||
|
// exist for MOVD alone.
|
||||||
|
func encodeARM64FrameAddr(sym *ast.Symbol, dst *ast.Operand, mnem string, fi arm64FrameInfo) ([]byte, error) {
|
||||||
|
if mnem != "MOVD" && mnem != "MOV" {
|
||||||
|
return nil, fmt.Errorf("%s: illegal combination: the $frame-address form exists for MOVD only", mnem)
|
||||||
|
}
|
||||||
|
rd := arm64RegNum(operandRegName(dst))
|
||||||
|
if rd < 0 {
|
||||||
|
return nil, fmt.Errorf("%s %s: invalid destination register", mnem, sym.Raw)
|
||||||
|
}
|
||||||
|
v := arm64FrameAddrValue(sym, fi)
|
||||||
|
if !arm64IsAddcon(v) && !arm64IsAddcon(-v) && (v < 0 || v > 0xFFFFFF) {
|
||||||
|
return nil, fmt.Errorf("%s: %s needs the literal pool, which no arm64 pool reaches here", mnem, sym.Raw)
|
||||||
|
}
|
||||||
|
return a64WordsLE(arm64FrameAddrWords(v, rd)...), nil
|
||||||
|
}
|
||||||
|
|
||||||
// encodeARM64SBLoad emits ADRP R27, 0; LDR Rd, [R27, 0] with relocations,
|
// encodeARM64SBLoad emits ADRP R27, 0; LDR Rd, [R27, 0] with relocations,
|
||||||
// matching the toolchain: the scratch register is REGTMP (R27) and the pair
|
// matching the toolchain: the scratch register is REGTMP (R27) and the pair
|
||||||
// carries R_ARM64_PCREL_LDST64. FMOVQ has no LDST relocation width, so it
|
// carries R_ARM64_PCREL_LDST64. FMOVQ has no LDST relocation width, so it
|
||||||
@@ -3820,7 +3875,7 @@ func encodeARM64DP1(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, err
|
|||||||
// target split into immlo (bits 30:29) and immhi (bits 23:5), with bit 31
|
// target split into immlo (bits 30:29) and immhi (bits 23:5), with bit 31
|
||||||
// selecting the page form. An n(PC) operand resolves to the instruction's
|
// selecting the page form. An n(PC) operand resolves to the instruction's
|
||||||
// own address: the toolchain rewrites it away and encodes displacement 0.
|
// own address: the toolchain rewrites it away and encodes displacement 0.
|
||||||
func encodeARM64ADR(mnem string, page uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string) ([]byte, error) {
|
func encodeARM64ADR(mnem string, page uint32, ops []*ast.Operand, pc int, offsets map[string]int) ([]byte, error) {
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
@@ -3830,7 +3885,10 @@ func encodeARM64ADR(mnem string, page uint32, ops []*ast.Operand, pc int, offset
|
|||||||
}
|
}
|
||||||
var rel int64
|
var rel int64
|
||||||
if _, pcRel := arm64PCRelOffset(ops[0]); !pcRel {
|
if _, pcRel := arm64PCRelOffset(ops[0]); !pcRel {
|
||||||
target := resolve(arm64Label(ops[0]))
|
// The label resolves to its own statement: the toolchain's
|
||||||
|
// jump-to-jump collapse rewrites p.To alone (obj/pass.go), so an
|
||||||
|
// ADR/ADRP's From-side label is never chased through a chain.
|
||||||
|
target := arm64Label(ops[0])
|
||||||
targetOff, ok := offsets[target]
|
targetOff, ok := offsets[target]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("undefined label %q", target)
|
return nil, fmt.Errorf("undefined label %q", target)
|
||||||
@@ -5816,24 +5874,63 @@ func arm64ResolveAliases(f *ast.File) {
|
|||||||
}
|
}
|
||||||
aliases := map[string]alias{}
|
aliases := map[string]alias{}
|
||||||
raws := map[string]string{}
|
raws := map[string]string{}
|
||||||
|
// The directive lines the parser records include the dead branches of
|
||||||
|
// every conditional, and a define inside a disabled region must not
|
||||||
|
// become an alias (go_tls.h's `#ifdef GOARCH_arm \n #define LR R14`
|
||||||
|
// must stay dead on arm64, where LR is R30). The liveness walk mirrors
|
||||||
|
// the preprocessor's: a conditional is live when its name was defined
|
||||||
|
// by a live define earlier in the file; every other name is undefined,
|
||||||
|
// the predefines (GOARCH_arm64 and friends) included, which the
|
||||||
|
// assembler never sees.
|
||||||
|
seen := map[string]bool{} // every live-defined name, any macro shape
|
||||||
|
cond := []bool{} // one entry per open #ifdef/#ifndef: its truth
|
||||||
|
live := func() bool {
|
||||||
|
for _, c := range cond {
|
||||||
|
if !c {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
for _, d := range f.Decls {
|
for _, d := range f.Decls {
|
||||||
pre, ok := d.(*ast.Preproc)
|
pre, ok := d.(*ast.Preproc)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fields := strings.Fields(pre.Raw)
|
fields := strings.Fields(pre.Raw)
|
||||||
if len(fields) < 3 || fields[0] != "define" {
|
if len(fields) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch fields[0] {
|
||||||
|
case "ifdef", "ifndef":
|
||||||
|
truth := false
|
||||||
|
if len(fields) >= 2 {
|
||||||
|
truth = seen[fields[1]] != (fields[0] == "ifndef")
|
||||||
|
}
|
||||||
|
cond = append(cond, truth)
|
||||||
|
case "else":
|
||||||
|
if len(cond) > 0 {
|
||||||
|
cond[len(cond)-1] = !cond[len(cond)-1]
|
||||||
|
}
|
||||||
|
case "endif":
|
||||||
|
if len(cond) > 0 {
|
||||||
|
cond = cond[:len(cond)-1]
|
||||||
|
}
|
||||||
|
case "define":
|
||||||
|
if !live() || len(fields) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name, body := fields[1], strings.Join(fields[2:], " ")
|
name, body := fields[1], strings.Join(fields[2:], " ")
|
||||||
// A parameterised macro spells its parameter list right after the
|
seen[name] = true
|
||||||
// name; a multi-instruction body needs statement expansion.
|
// A parameterised macro spells its parameter list right after
|
||||||
|
// the name; a multi-instruction body needs statement expansion.
|
||||||
if strings.ContainsAny(name, "(") || body == "" ||
|
if strings.ContainsAny(name, "(") || body == "" ||
|
||||||
strings.HasPrefix(body, "(") || strings.ContainsAny(body, "();") {
|
strings.HasPrefix(body, "(") || strings.ContainsAny(body, "();") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
raws[name] = body
|
raws[name] = body
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Alias bodies may name other aliases (hlp1 → res_ptr → R0): substitute
|
// Alias bodies may name other aliases (hlp1 → res_ptr → R0): substitute
|
||||||
// transitively until nothing changes, bounded against cycles.
|
// transitively until nothing changes, bounded against cycles.
|
||||||
for range 8 {
|
for range 8 {
|
||||||
|
|||||||
@@ -403,6 +403,59 @@ func arm64ResolvePseudo(sym *ast.Symbol, fi arm64FrameInfo) (base int, off int32
|
|||||||
return -1, 0
|
return -1, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrValue returns the SP-relative displacement a $sym+off(FP)
|
||||||
|
// or $sym+off(SP) immediate-address operand stands for, the toolchain's
|
||||||
|
// aclass arithmetic (asm7.go): a parameter reference sits autosize+8 above
|
||||||
|
// the hardware SP, and a pseudo-SP reference sits frame+8 above it, the
|
||||||
|
// alignment padding cancelling out of the autosize.
|
||||||
|
func arm64FrameAddrValue(sym *ast.Symbol, fi arm64FrameInfo) int64 {
|
||||||
|
switch sym.Pseudo {
|
||||||
|
case "FP":
|
||||||
|
return sym.Offset + int64(fi.autosize) + 8
|
||||||
|
default: // SP
|
||||||
|
return sym.Offset + int64(fi.frame) + 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64IsAddcon reports whether v is an addcon value (asm7.go isaddcon): an
|
||||||
|
// unsigned imm12, or a multiple of 4096 whose shifted form fits imm12.
|
||||||
|
func arm64IsAddcon(v int64) bool {
|
||||||
|
if v < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v&0xFFF == 0 {
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return v <= 0xFFF
|
||||||
|
}
|
||||||
|
|
||||||
|
// arm64FrameAddrWords returns the word sequence of the toolchain's optab
|
||||||
|
// case 4 for a frame-relative address (the C_AACON and C_AACON2 rows): one
|
||||||
|
// ADD/SUB imm12 word inside the addcon band, SUB carrying a negative
|
||||||
|
// displacement, and otherwise the hi<<12 word from SP followed by the low
|
||||||
|
// word added in place. The 24-bit band never reaches the REGTMP
|
||||||
|
// materialisation the ADD/SUB immediate ladder uses: aclass classifies the
|
||||||
|
// address straight into C_AACON2.
|
||||||
|
func arm64FrameAddrWords(v int64, rd int) []uint32 {
|
||||||
|
word := func(v int64, rn int) uint32 {
|
||||||
|
op := uint32(0) // ADD
|
||||||
|
if v < 0 {
|
||||||
|
op = 1 // SUB
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
sh := uint32(0)
|
||||||
|
if v&0xFFF000 != 0 { // asm7.go oaddi: the shift form when low 12 bits are clear
|
||||||
|
sh = 1
|
||||||
|
v >>= 12
|
||||||
|
}
|
||||||
|
return a64AddSub(1, op, 0, sh, uint32(v), uint32(rn), uint32(rd))
|
||||||
|
}
|
||||||
|
if arm64IsAddcon(v) || arm64IsAddcon(-v) {
|
||||||
|
return []uint32{word(v, 31)}
|
||||||
|
}
|
||||||
|
return []uint32{word(v&^int64(0xFFF), 31), word(v&0xFFF, rd)}
|
||||||
|
}
|
||||||
|
|
||||||
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
// arm64PreStoreImm encodes a pre-index store (STR with writeback):
|
||||||
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
// size<<30 | 7<<27 | V<<26 | opc<<22 | 1<<11 | 1<<10 | imm9<<12 | Rn<<5 | Rt.
|
||||||
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
func arm64PreStoreImm(size, V int, imm9 int32, rn, rt int) uint32 {
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assembleArm64Source parses src and assembles it for arm64, returning the
|
||||||
|
// first function's words little-endian.
|
||||||
|
func assembleArm64Source(t *testing.T, src string) []uint32 {
|
||||||
|
t.Helper()
|
||||||
|
f, errs := parser.Parse("test_arm64.s", src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
if len(img.Funcs) != 1 {
|
||||||
|
t.Fatalf("got %d funcs, want 1", len(img.Funcs))
|
||||||
|
}
|
||||||
|
return wordsOf(img.Code[img.Funcs[0].Offset : img.Funcs[0].Offset+img.Funcs[0].Size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrEncoding(t *testing.T) {
|
||||||
|
src := `#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·fr(SB), NOSPLIT, $432-24
|
||||||
|
MOVD $argframe+0(FP), R3
|
||||||
|
MOVD $big+4096(FP), R4
|
||||||
|
MOVD $ret-8(FP), R2
|
||||||
|
MOVD $x-64(FP), R5
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// Prologue (4: large frame) then the body at words 4..9, the toolchain's
|
||||||
|
// own encodings for the same statements:
|
||||||
|
// ADD $456, RSP, R3 (456 = 448 + 8 + 0)
|
||||||
|
// ADD $(1<<12), RSP, R4 (4552, the hi<<12 half)
|
||||||
|
// ADD $456, R4, R4 (then the lo half)
|
||||||
|
// ADD $448, RSP, R2 (448 - 8 + 8)
|
||||||
|
// ADD $392, RSP, R5 (448 - 64 + 8)
|
||||||
|
// ADD $0, RSP, R19 (the SP register move)
|
||||||
|
// ADD $0, R20, RSP
|
||||||
|
want := []uint32{
|
||||||
|
0xd10703f4, 0xa93ffa9d, 0x9100029f, 0xd10023fd,
|
||||||
|
0x910723e3, 0x914007e4, 0x91072084, 0x910703e2,
|
||||||
|
0x910623e5, 0x910003f3, 0x9100029f,
|
||||||
|
}
|
||||||
|
if len(ws) < len(want) {
|
||||||
|
t.Fatalf("got %d words, want at least %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrSizes(t *testing.T) {
|
||||||
|
// One imm12 word inside the addcon band, two inside the 24-bit band.
|
||||||
|
tests := []struct {
|
||||||
|
v int64
|
||||||
|
verb int
|
||||||
|
}{
|
||||||
|
{456, 1},
|
||||||
|
{0xFFF, 1},
|
||||||
|
{0x1000, 1}, // the shifted imm12 form
|
||||||
|
{0x1005, 2}, // the hi<<12 plus lo pair
|
||||||
|
{0xFFFFFF, 2},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := len(arm64FrameAddrWords(tt.v, 3))
|
||||||
|
if got != tt.verb {
|
||||||
|
t.Errorf("arm64FrameAddrWords(%d) took %d words, want %d", tt.v, got, tt.verb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ws := arm64FrameAddrWords(0x1000, 3); len(ws) != 1 || ws[0] != 0x914007e3 {
|
||||||
|
t.Errorf("arm64FrameAddrWords(0x1000) = %08x, want the shifted ADD 914007e3", ws[0])
|
||||||
|
}
|
||||||
|
if !arm64IsAddcon(0xFFF) || arm64IsAddcon(0x1001) || arm64IsAddcon(-1) {
|
||||||
|
t.Error("arm64IsAddcon misclassifies the band edges")
|
||||||
|
}
|
||||||
|
fp := &ast.Symbol{Name: "x", Pseudo: "FP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(fp, arm64FrameInfo{autosize: 448}); v != 464 {
|
||||||
|
t.Errorf("FP address: got %d, want 464", v)
|
||||||
|
}
|
||||||
|
sp := &ast.Symbol{Name: "x", Pseudo: "SP", Offset: 8}
|
||||||
|
if v := arm64FrameAddrValue(sp, arm64FrameInfo{frame: 432}); v != 448 {
|
||||||
|
t.Errorf("SP address: got %d, want 448", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64FrameAddrRejects(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
src string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"width", `TEXT ·f(SB), NOSPLIT, $16-8
|
||||||
|
MOVW $x+0(FP), R7
|
||||||
|
RET
|
||||||
|
`, "illegal combination",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pool band", `TEXT ·f(SB), NOSPLIT, $20000000-8
|
||||||
|
MOVD $x+20000000(FP), R7
|
||||||
|
RET
|
||||||
|
`, "literal pool",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("test_arm64.s", tt.src)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
_, err := AssembleFileARM64(f)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("%s: no error, want one naming %q", tt.name, tt.want)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("%s: error %q, want it to name %q", tt.name, err, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64SPMoveEncoding(t *testing.T) {
|
||||||
|
src := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD RSP, R19
|
||||||
|
MOVD R20, RSP
|
||||||
|
MOVD ZR, R4
|
||||||
|
MOVD RSP, RSP
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
// The SP register moves ride ADD $0; the zero move stays ORR.
|
||||||
|
want := []uint32{
|
||||||
|
0x910003f3, // ADD $0, RSP, R19
|
||||||
|
0x9100029f, // ADD $0, R20, RSP
|
||||||
|
0xaa1f03e4, // ORR R4, ZR, ZR
|
||||||
|
0x910003ff, // ADD $0, RSP, RSP
|
||||||
|
0xd65f03c0, // RET
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rej := `TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVW RSP, R7
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
f, errs := parser.Parse("test_arm64.s", rej)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
if _, err := AssembleFileARM64(f); err == nil || !strings.Contains(err.Error(), "illegal combination") {
|
||||||
|
t.Errorf("MOVW RSP: error %v, want an illegal-combination refusal", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64AliasLiveness(t *testing.T) {
|
||||||
|
// A define inside a dead conditional branch must not become an alias:
|
||||||
|
// go_tls.h's `#ifdef GOARCH_arm` block defines LR as R14, which would
|
||||||
|
// silently renumber the link register on arm64, where LR is R30.
|
||||||
|
src := `#define RARG R5
|
||||||
|
#ifdef GOARCH_arm
|
||||||
|
#define LR R14
|
||||||
|
#endif
|
||||||
|
TEXT ·f(SB), NOSPLIT, $0-8
|
||||||
|
MOVD LR, R0
|
||||||
|
MOVD RARG, R1
|
||||||
|
MOVD R14, R2
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0xaa1e03e0, // ORR R0, ZR, R30: LR stayed the link register
|
||||||
|
0xaa0503e1, // ORR R1, ZR, R5: the live alias applied
|
||||||
|
0xaa0e03e2, // ORR R2, ZR, R14: R14 is R14
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArm64ADRNoChainChase(t *testing.T) {
|
||||||
|
// The toolchain's jump-to-jump collapse rewrites branch targets only:
|
||||||
|
// a B to a chain-leading label is redirected, an ADR to the same label
|
||||||
|
// resolves to the label itself.
|
||||||
|
src := `TEXT ·adrchain(SB), NOSPLIT, $0-0
|
||||||
|
B a
|
||||||
|
a:
|
||||||
|
B b
|
||||||
|
b:
|
||||||
|
ADR a, R0
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
ws := assembleArm64Source(t, src)
|
||||||
|
want := []uint32{
|
||||||
|
0x14000002, // B +2: chased through a to b
|
||||||
|
0x14000001, // B +1: a's own jump to b
|
||||||
|
0x10ffffe0, // ADR a, R0: -4, the label itself, unchased
|
||||||
|
0xd65f03c0,
|
||||||
|
}
|
||||||
|
if len(ws) != len(want) {
|
||||||
|
t.Fatalf("got %d words, want %d", len(ws), len(want))
|
||||||
|
}
|
||||||
|
for i, w := range want {
|
||||||
|
if ws[i] != w {
|
||||||
|
t.Errorf("word %d: got %08x, want %08x", i, ws[i], w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,12 +55,9 @@ var gorootARM64Packages = []string{
|
|||||||
// not silently dropped: the gaps are findings, and closing one is a matter
|
// not silently dropped: the gaps are findings, and closing one is a matter
|
||||||
// of removing its entry and watching the file pin itself.
|
// of removing its entry and watching the file pin itself.
|
||||||
var gorootARM64GapFiles = map[string]string{
|
var gorootARM64GapFiles = map[string]string{
|
||||||
"runtime/asm_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge, and MOVD $sym+off(FP) materialises from ZR",
|
"runtime/asm_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge",
|
||||||
"runtime/asan_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
"runtime/sys_linux_arm64.s": "the unparenthesised NOSPLIT|NOFRAME flag list is not recognised, so the prologue and guard shapes diverge (cgoSigtramp, clone)",
|
||||||
"runtime/libfuzzer_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
|
||||||
"runtime/msan_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
|
||||||
"runtime/tls_arm64.s": "a branch distance diverges: an earlier statement's expansion is sized differently",
|
"runtime/tls_arm64.s": "a branch distance diverges: an earlier statement's expansion is sized differently",
|
||||||
"runtime/valgrind_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
|
||||||
"crypto/internal/fips140/aes/aes_arm64.s": "a tested immediate bit in a TBZ diverges",
|
"crypto/internal/fips140/aes/aes_arm64.s": "a tested immediate bit in a TBZ diverges",
|
||||||
"crypto/internal/fips140/aes/gcm/gcm_arm64.s": "an ADD immediate's value diverges mid-function",
|
"crypto/internal/fips140/aes/gcm/gcm_arm64.s": "an ADD immediate's value diverges mid-function",
|
||||||
"crypto/internal/fips140/nistec/p256_asm_arm64.s": "a branch distance and a logical-immediate lowering diverge",
|
"crypto/internal/fips140/nistec/p256_asm_arm64.s": "a branch distance and a logical-immediate lowering diverge",
|
||||||
@@ -68,9 +65,7 @@ var gorootARM64GapFiles = map[string]string{
|
|||||||
"runtime/preempt_arm64.s": "the prologue shape for the function's frame diverges at the first word",
|
"runtime/preempt_arm64.s": "the prologue shape for the function's frame diverges at the first word",
|
||||||
"runtime/race_arm64.s": "the flag list and prologue shape diverge at the first words",
|
"runtime/race_arm64.s": "the flag list and prologue shape diverge at the first words",
|
||||||
"runtime/rt0_linux_arm64.s": "the #ifdef GOOS selection diverges: gasm keeps a word the toolchain drops",
|
"runtime/rt0_linux_arm64.s": "the #ifdef GOOS selection diverges: gasm keeps a word the toolchain drops",
|
||||||
"runtime/sys_linux_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
|
||||||
"internal/runtime/syscall/linux/asm_linux_arm64.s": "a branch distance diverges: an earlier statement's expansion is sized differently",
|
"internal/runtime/syscall/linux/asm_linux_arm64.s": "a branch distance diverges: an earlier statement's expansion is sized differently",
|
||||||
"reflect/asm_arm64.s": "MOVD $sym+off(FP) materialises MOV from ZR where the toolchain emits ADD $off, SP, Rd",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// gorootOtherGOOS matches the file names of the ports the linux build never
|
// gorootOtherGOOS matches the file names of the ports the linux build never
|
||||||
|
|||||||
Reference in new issue
Block a user