feat(asm): the operand forms and defines GOROOT writes
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [development]
|
## [development]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **The operand forms GOROOT writes.** Numeric PC-relative jumps
|
||||||
|
(`JEQ 2(PC)`, the park loop `JMP 0(PC)`) resolve with the toolchain's
|
||||||
|
own instruction counting and fold jump-to-jump chains exactly as its
|
||||||
|
branch optimiser does; symbol immediates (`MOVQ $sym(SB), AX`)
|
||||||
|
assemble to the toolchain's RIP-relative LEA with an R_PCREL
|
||||||
|
relocation; negated constant expressions in operands (`ADJSP
|
||||||
|
$-(REGS - 8)`, the shape the cgo ABI macros write) fold; and `gasm
|
||||||
|
asm` predefines the `GOARCH_<arch>` and `GOOS_<goos>` macros the go
|
||||||
|
command passes to `go tool asm`, so GOROOT headers' `#ifdef
|
||||||
|
GOARCH_amd64` platform blocks (`go_tls.h`'s `get_tls` and friends)
|
||||||
|
select as intended. The GOROOT corpus measure moves to 261 of 353
|
||||||
|
files assembling for every target architecture (73.9 %), 87.5 % of
|
||||||
|
the real-code corpus, from 70.8 % and 82.2 %.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Per-architecture reference pages.** [docs/asm/](docs/asm/README.md)
|
- **Per-architecture reference pages.** [docs/asm/](docs/asm/README.md)
|
||||||
|
|||||||
+158
-14
@@ -86,6 +86,10 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, [
|
|||||||
// outgrows the short form.
|
// outgrows the short form.
|
||||||
long := make([]bool, len(t.Body))
|
long := make([]bool, len(t.Body))
|
||||||
sizes := make([]int, len(t.Body))
|
sizes := make([]int, len(t.Body))
|
||||||
|
numTargets := make([]int, len(t.Body))
|
||||||
|
for i := range numTargets {
|
||||||
|
numTargets[i] = -1
|
||||||
|
}
|
||||||
offsets := map[string]int{}
|
offsets := map[string]int{}
|
||||||
pcs := make([]int, len(t.Body))
|
pcs := make([]int, len(t.Body))
|
||||||
var guardJBlong, guardJBElong, moreJMPlong bool
|
var guardJBlong, guardJBElong, moreJMPlong bool
|
||||||
@@ -94,6 +98,10 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, [
|
|||||||
for {
|
for {
|
||||||
guard := fi.guardLen(guardJBlong, guardJBElong)
|
guard := fi.guardLen(guardJBlong, guardJBElong)
|
||||||
pos := guard + len(fi.prologue)
|
pos := guard + len(fi.prologue)
|
||||||
|
for i := range numTargets {
|
||||||
|
numTargets[i] = -1
|
||||||
|
}
|
||||||
|
idxAtPc := map[int]int{}
|
||||||
for i, stmt := range t.Body {
|
for i, stmt := range t.Body {
|
||||||
switch s := stmt.(type) {
|
switch s := stmt.(type) {
|
||||||
case *ast.Label:
|
case *ast.Label:
|
||||||
@@ -105,12 +113,78 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, [
|
|||||||
}
|
}
|
||||||
sizes[i] = sz
|
sizes[i] = sz
|
||||||
pcs[i] = pos
|
pcs[i] = pos
|
||||||
|
idxAtPc[pos] = i
|
||||||
pos += sz
|
pos += sz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bodyLen := pos - (guard + len(fi.prologue))
|
bodyLen := pos - (guard + len(fi.prologue))
|
||||||
// Expand any short jump whose displacement no longer fits rel8.
|
// Expand any short jump whose displacement no longer fits rel8.
|
||||||
changed := false
|
changed := false
|
||||||
|
// Numeric ±N(PC) jumps resolve against this iteration's layout; the
|
||||||
|
// emission pass reads the same table after the loop converges. A
|
||||||
|
// target that is itself an unconditional local JMP is chased to the
|
||||||
|
// ultimate target: the toolchain's brloop pass collapses branch-to-
|
||||||
|
// branch chains before it encodes, so matching its bytes requires
|
||||||
|
// the same redirection.
|
||||||
|
for i := range numTargets {
|
||||||
|
numTargets[i] = -1
|
||||||
|
}
|
||||||
|
for i, stmt := range t.Body {
|
||||||
|
s, ok := stmt.(*ast.Instr)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(s.Operands) == 1 {
|
||||||
|
if n, isNum := pcJumpOffset(s.Operands[0]); isNum {
|
||||||
|
if target, okT := pcJumpTarget(t, i, n, pcs); okT {
|
||||||
|
numTargets[i] = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range numTargets {
|
||||||
|
if numTargets[i] < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tgt := numTargets[i]
|
||||||
|
for hop := 0; hop < len(t.Body); hop++ {
|
||||||
|
idx, ok := idxAtPc[tgt]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
in, ok := t.Body[idx].(*ast.Instr)
|
||||||
|
if !ok || strings.ToUpper(in.Mnemonic.Text) != "JMP" || len(in.Operands) != 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if name, isLabel := labelName(in.Operands[0]); isLabel {
|
||||||
|
tgt = offsets[resolve(name)]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n, isNum := pcJumpOffset(in.Operands[0]); isNum {
|
||||||
|
next, okT := pcJumpTarget(t, idx, n, pcs)
|
||||||
|
if !okT {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
tgt = next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break // JMP through a register or memory: the chain ends
|
||||||
|
}
|
||||||
|
numTargets[i] = tgt
|
||||||
|
}
|
||||||
|
for i, stmt := range t.Body {
|
||||||
|
s, ok := stmt.(*ast.Instr)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if numTargets[i] >= 0 && !long[i] {
|
||||||
|
rel := int64(numTargets[i] - (pcs[i] + jumpSize(strings.ToUpper(s.Mnemonic.Text), false)))
|
||||||
|
if !fits8(rel) {
|
||||||
|
long[i] = true
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, stmt := range t.Body {
|
for i, stmt := range t.Body {
|
||||||
s, ok := stmt.(*ast.Instr)
|
s, ok := stmt.(*ast.Instr)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -232,7 +306,7 @@ func assemble(t *ast.Text, link *linkInfo) ([]byte, []sbPatch, map[string]int, [
|
|||||||
spadjStep{pos + epi, 0},
|
spadjStep{pos + epi, 0},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
code, ps, pool, err := encodeInstr(s, pos, offsets, fi, long[i], resolve, link)
|
code, ps, pool, err := encodeInstr(s, pos, offsets, fi, long[i], resolve, link, numTargets[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", s.Mnemonic.Text, err)
|
||||||
}
|
}
|
||||||
@@ -428,6 +502,37 @@ func computeFrame(t *ast.Text) frameInfo {
|
|||||||
return fi
|
return fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pcJumpOffset recognises the numeric relative jump operand ±N(PC) and
|
||||||
|
// returns N: the toolchain counts instructions, not bytes, so +2(PC) targets
|
||||||
|
// the second instruction boundary after the branch.
|
||||||
|
func pcJumpOffset(op *ast.Operand) (int, bool) {
|
||||||
|
if op.Kind != ast.OpAddr || op.Addr.Base != "PC" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return int(op.Addr.Offset), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// pcJumpTarget resolves a numeric jump at statement index j: N counts the
|
||||||
|
// instruction statements after the jump itself (N = 0 is the jump's own
|
||||||
|
// address, the classic park loop), and the target is the start of the Nth
|
||||||
|
// one. It reports false when the count runs past the end of the function.
|
||||||
|
func pcJumpTarget(t *ast.Text, j, n int, pcs []int) (int, bool) {
|
||||||
|
if n == 0 {
|
||||||
|
return pcs[j], true
|
||||||
|
}
|
||||||
|
seen := 0
|
||||||
|
for k := j + 1; k < len(t.Body); k++ {
|
||||||
|
if _, ok := t.Body[k].(*ast.Instr); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen++
|
||||||
|
if seen == n {
|
||||||
|
return pcs[k], true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
// hasCall reports whether the function body contains a CALL instruction.
|
// hasCall reports whether the function body contains a CALL instruction.
|
||||||
func hasCall(t *ast.Text) bool {
|
func hasCall(t *ast.Text) bool {
|
||||||
for _, stmt := range t.Body {
|
for _, stmt := range t.Body {
|
||||||
@@ -602,7 +707,7 @@ func instrSize(s *ast.Instr, fi frameInfo, long bool, link *linkInfo) (int, erro
|
|||||||
}
|
}
|
||||||
return jumpSize(mnem, long), nil
|
return jumpSize(mnem, long), nil
|
||||||
}
|
}
|
||||||
code, _, _, err := encodeInstr(s, 0, nil, fi, false, nil, link)
|
code, _, _, err := encodeInstr(s, 0, nil, fi, false, nil, link, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -637,7 +742,7 @@ func jumpSize(mnem string, long bool) int {
|
|||||||
// (relative to pc, the instruction's own offset). A RET in a frame-pointer
|
// (relative to pc, the instruction's own offset). A RET in a frame-pointer
|
||||||
// function is prefixed with the epilogue. resolve, when non-nil, redirects a
|
// function is prefixed with the epilogue. resolve, when non-nil, redirects a
|
||||||
// jump label through the jump-to-jump chain before the offset lookup.
|
// jump label through the jump-to-jump chain before the offset lookup.
|
||||||
func encodeInstr(s *ast.Instr, pc int, offsets map[string]int, fi frameInfo, long bool, resolve func(string) string, link *linkInfo) ([]byte, []sbPatch, []floatPoolEntry, error) {
|
func encodeInstr(s *ast.Instr, pc int, offsets map[string]int, fi frameInfo, long bool, resolve func(string) string, link *linkInfo, numTarget int) ([]byte, []sbPatch, []floatPoolEntry, error) {
|
||||||
mnem := strings.ToUpper(s.Mnemonic.Text)
|
mnem := strings.ToUpper(s.Mnemonic.Text)
|
||||||
|
|
||||||
var prefix []byte
|
var prefix []byte
|
||||||
@@ -677,7 +782,7 @@ func encodeInstr(s *ast.Instr, pc int, offsets map[string]int, fi frameInfo, lon
|
|||||||
}
|
}
|
||||||
return append(prefix, code...), nil, nil, nil
|
return append(prefix, code...), nil, nil, nil
|
||||||
}
|
}
|
||||||
code, err = encodeJump(s, mnem, pc+len(prefix), offsets, long, resolve)
|
code, err = encodeJump(s, mnem, pc+len(prefix), offsets, long, resolve, numTarget)
|
||||||
} else {
|
} else {
|
||||||
code, ps, pool, err = encodeNormal(s, fi, link)
|
code, ps, pool, err = encodeNormal(s, fi, link)
|
||||||
}
|
}
|
||||||
@@ -703,6 +808,32 @@ func encodeNormal(s *ast.Instr, fi frameInfo, link *linkInfo) ([]byte, []sbPatch
|
|||||||
}
|
}
|
||||||
return code, nil, nil, nil
|
return code, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
// MOVQ $sym±off(SB), r64: the toolchain assembles a symbol immediate as
|
||||||
|
// LEAQ disp32(RIP), r64 with an R_PCREL relocation at the disp32 field,
|
||||||
|
// never as a 64-bit absolute immediate (verified against go tool asm).
|
||||||
|
// MOVD is the MOVQ alias; the narrower widths reject the form outright.
|
||||||
|
if (mnemUpper == "MOVQ" || mnemUpper == "MOVD") && len(s.Operands) == 2 &&
|
||||||
|
s.Operands[0].Kind == ast.OpImmediate && s.Operands[0].Imm.Sym != nil &&
|
||||||
|
s.Operands[0].Imm.Sym.Pseudo == "SB" {
|
||||||
|
mem := &ast.Operand{Kind: ast.OpAddr, Addr: ast.Address{Sym: s.Operands[0].Imm.Sym}}
|
||||||
|
src, err := operandFromAST(mnemUpper, mem, 8, fi, link)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
dst, err := operandFromAST(mnemUpper, s.Operands[1], 8, fi, link)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
e := &enc{}
|
||||||
|
if err := e.encodeLea([]Operand{src, dst}, 8); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
ps := make([]sbPatch, len(e.patches))
|
||||||
|
for i, p := range e.patches {
|
||||||
|
ps[i] = sbPatch{off: p.off, name: p.name, addend: p.addend}
|
||||||
|
}
|
||||||
|
return e.out, ps, nil, nil
|
||||||
|
}
|
||||||
_, size := splitSize(mnemUpper)
|
_, size := splitSize(mnemUpper)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 8
|
size = 8
|
||||||
@@ -753,21 +884,29 @@ func encodeBookkeeping(upper string, s *ast.Instr) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encodeJump encodes a JMP/CALL/Jcc with a relative offset resolved from the
|
// encodeJump encodes a JMP/CALL/Jcc with a relative offset resolved from the
|
||||||
// target label, in the short (rel8) or long (rel32) form.
|
// target label or from a numeric ±N(PC) instruction count, in the short
|
||||||
func encodeJump(s *ast.Instr, mnem string, pc int, offsets map[string]int, long bool, resolve func(string) string) ([]byte, error) {
|
// (rel8) or long (rel32) form. numTarget is the resolved byte offset of a
|
||||||
|
// numeric operand, negative when the operand is not one.
|
||||||
|
func encodeJump(s *ast.Instr, mnem string, pc int, offsets map[string]int, long bool, resolve func(string) string, numTarget int) ([]byte, error) {
|
||||||
if len(s.Operands) != 1 {
|
if len(s.Operands) != 1 {
|
||||||
return nil, fmt.Errorf("jump expects 1 operand, got %d", len(s.Operands))
|
return nil, fmt.Errorf("jump expects 1 operand, got %d", len(s.Operands))
|
||||||
}
|
}
|
||||||
name, ok := labelName(s.Operands[0])
|
name, isLabel := labelName(s.Operands[0])
|
||||||
if !ok {
|
if !isLabel && numTarget < 0 {
|
||||||
return nil, fmt.Errorf("jump target must be a local label")
|
return nil, fmt.Errorf("jump target must be a local label")
|
||||||
}
|
}
|
||||||
if resolve != nil && mnem != "CALL" {
|
var target int
|
||||||
name = resolve(name)
|
if isLabel {
|
||||||
}
|
if resolve != nil && mnem != "CALL" {
|
||||||
target, ok := offsets[name]
|
name = resolve(name)
|
||||||
if !ok {
|
}
|
||||||
return nil, fmt.Errorf("undefined label %q", name)
|
t, ok := offsets[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("undefined label %q", name)
|
||||||
|
}
|
||||||
|
target = t
|
||||||
|
} else {
|
||||||
|
target = numTarget
|
||||||
}
|
}
|
||||||
rel := int64(target - (pc + jumpSize(mnem, long)))
|
rel := int64(target - (pc + jumpSize(mnem, long)))
|
||||||
|
|
||||||
@@ -841,6 +980,11 @@ func indirectJumpTarget(s *ast.Instr) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
a := s.Operands[0].Addr
|
a := s.Operands[0].Addr
|
||||||
|
// ±N(PC) is the numeric relative form, the PC counts instructions from
|
||||||
|
// the branch: relative, not indirect.
|
||||||
|
if a.Base == "PC" || a.Index == "PC" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if a.Base != "" || a.Index != "" {
|
if a.Base != "" || a.Index != "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ func TestDifferentialKernels(t *testing.T) {
|
|||||||
{filepath.Join("..", "testdata", "verify", "quadreg_amd64.s"), "", false},
|
{filepath.Join("..", "testdata", "verify", "quadreg_amd64.s"), "", false},
|
||||||
{filepath.Join("..", "testdata", "verify", "floatimm_amd64.s"), "", false},
|
{filepath.Join("..", "testdata", "verify", "floatimm_amd64.s"), "", false},
|
||||||
{filepath.Join("..", "testdata", "verify", "bookkeep_amd64.s"), "", false},
|
{filepath.Join("..", "testdata", "verify", "bookkeep_amd64.s"), "", false},
|
||||||
|
{filepath.Join("..", "testdata", "verify", "forms_amd64.s"), "", false},
|
||||||
{filepath.Join("..", "testdata", "verify", "datarel_arm64.s"), "arm64", true},
|
{filepath.Join("..", "testdata", "verify", "datarel_arm64.s"), "arm64", true},
|
||||||
{filepath.Join("..", "testdata", "verify", "divslash_arm64.s"), "arm64", true},
|
{filepath.Join("..", "testdata", "verify", "divslash_arm64.s"), "arm64", true},
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -166,6 +166,13 @@ func AssembleFile(f *ast.File) (*Image, error) {
|
|||||||
for _, d := range dataSyms {
|
for _, d := range dataSyms {
|
||||||
known[d.name] = true
|
known[d.name] = true
|
||||||
}
|
}
|
||||||
|
// TEXT symbols are file-level definitions too: a symbol immediate
|
||||||
|
// ($fn(SB)) may name one, exactly as a data reference names a GLOBL.
|
||||||
|
for _, d := range f.Decls {
|
||||||
|
if t, ok := d.(*ast.Text); ok {
|
||||||
|
known[t.Name.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
link := &linkInfo{symbols: known, allowExternal: true}
|
link := &linkInfo{symbols: known, allowExternal: true}
|
||||||
poolSeen := map[string]bool{}
|
poolSeen := map[string]bool{}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -664,6 +664,7 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
f, errs := parser.ParseWithOptions(path, src, parser.Options{
|
f, errs := parser.ParseWithOptions(path, src, parser.Options{
|
||||||
Expand: true,
|
Expand: true,
|
||||||
IncludeDirs: append(slices.Clone(dirs), hdrDir),
|
IncludeDirs: append(slices.Clone(dirs), hdrDir),
|
||||||
|
Predefines: platformPredefinesFor(goarchName(tg.a), goos),
|
||||||
})
|
})
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
ok = false
|
ok = false
|
||||||
@@ -683,12 +684,19 @@ func runCorpusAudit(root string, dirs includeDirs) (*corpusStats, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
|
|
||||||
|
|
||||||
ok := true
|
ok := true
|
||||||
for _, i := range wanted {
|
for _, i := range wanted {
|
||||||
tg, t := targets[i], tallies[i]
|
tg, t := targets[i], tallies[i]
|
||||||
t.attempted++
|
t.attempted++
|
||||||
|
// The parse carries the target's platform predefines, so it
|
||||||
|
// cannot be shared across targets the way a header-free file's
|
||||||
|
// could: a #ifdef GOARCH_arm block must be live on arm64 and
|
||||||
|
// dead everywhere else.
|
||||||
|
f, errs := parser.ParseWithOptions(path, src, parser.Options{
|
||||||
|
Expand: true,
|
||||||
|
IncludeDirs: dirs,
|
||||||
|
Predefines: platformPredefinesFor(goarchName(tg.a), goos),
|
||||||
|
})
|
||||||
var err error
|
var err error
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
err = errs[0] // a parse failure is a failure for every target
|
err = errs[0] // a parse failure is a failure for every target
|
||||||
|
|||||||
+29
-6
@@ -574,7 +574,7 @@ naming the package.
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
dirs = append(dirs, hdrDir)
|
dirs = append(dirs, hdrDir)
|
||||||
}
|
}
|
||||||
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
|
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs, Predefines: platformPredefinesFor(string(targetArch), goos)})
|
||||||
for _, e := range errs {
|
for _, e := range errs {
|
||||||
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
|
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
|
||||||
}
|
}
|
||||||
@@ -789,6 +789,29 @@ e.g. --map wideCopyAVX2=wideCopyAVX512 pairs the two regardless of suffix.
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// platformPredefines mirrors the go command's assembler invocation, which
|
||||||
|
// defines GOOS_<goos> and GOARCH_<arch> as -D macros: GOROOT headers
|
||||||
|
// (go_tls.h, asm_riscv64.h) select their platform blocks with #ifdef on
|
||||||
|
// exactly those names, so an assembler without them cannot see the platform
|
||||||
|
// definitions at all.
|
||||||
|
func platformPredefines(goarch, goos string) map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"GOARCH_" + goarch: "1",
|
||||||
|
"GOOS_" + goos: "1",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// platformPredefinesFor resolves the ambient GOOS the way a build would: a
|
||||||
|
// file whose name carries one (sys_darwin_arm64.s) is compiled for that GOOS
|
||||||
|
// and nothing else.
|
||||||
|
func platformPredefinesFor(goarch string, fileGoos string) map[string]string {
|
||||||
|
goos := fileGoos
|
||||||
|
if goos == "" {
|
||||||
|
goos = runtime.GOOS
|
||||||
|
}
|
||||||
|
return platformPredefines(goarch, goos)
|
||||||
|
}
|
||||||
|
|
||||||
// assembleFile assembles a parsed file for the given architecture and returns the image.
|
// assembleFile assembles a parsed file for the given architecture and returns the image.
|
||||||
func assembleFile(targetArch arch.Arch, f *ast.File) (*asm.Image, error) {
|
func assembleFile(targetArch arch.Arch, f *ast.File) (*asm.Image, error) {
|
||||||
switch targetArch {
|
switch targetArch {
|
||||||
@@ -813,17 +836,17 @@ func assemblePath(path string, forced arch.Arch, dirs includeDirs) (*asm.Image,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs})
|
target := forced
|
||||||
|
if target == arch.Unknown {
|
||||||
|
target = arch.FromFilename(path)
|
||||||
|
}
|
||||||
|
f, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true, IncludeDirs: dirs, Predefines: platformPredefinesFor(string(target), "")})
|
||||||
for _, e := range errs {
|
for _, e := range errs {
|
||||||
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
|
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
|
||||||
}
|
}
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, fmt.Errorf("parse errors")
|
return nil, fmt.Errorf("parse errors")
|
||||||
}
|
}
|
||||||
target := forced
|
|
||||||
if target == arch.Unknown {
|
|
||||||
target = arch.FromFilename(path)
|
|
||||||
}
|
|
||||||
return assembleFile(target, f)
|
return assembleFile(target, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -489,6 +489,17 @@ func parseImmediate(g []token.Token) ast.Immediate {
|
|||||||
} else if g[i].Kind == token.Plus {
|
} else if g[i].Kind == token.Plus {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
// A constant expression after the sign: $-(R - 8), $+(32-shift). The
|
||||||
|
// toolchain folds the negated value in place (the cgo ABI macros write
|
||||||
|
// ADJSP $-(REGS_HOST_TO_ABI0_STACK - 8)), so the sign applies to the
|
||||||
|
// folded value exactly as it does to a bare literal.
|
||||||
|
if i < len(g) && (g[i].Kind == token.LParen || g[i].Kind == token.Tilde) {
|
||||||
|
if v, rest, ok := foldExpr(g[i:]); ok && len(rest) == 0 {
|
||||||
|
imm.Val = v
|
||||||
|
imm.HasVal = true
|
||||||
|
return imm
|
||||||
|
}
|
||||||
|
}
|
||||||
if i < len(g) && g[i].Kind == token.Number {
|
if i < len(g) && g[i].Kind == token.Number {
|
||||||
text := g[i].Text
|
text := g[i].Text
|
||||||
if v, ok := tryInt(text); ok {
|
if v, ok := tryInt(text); ok {
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ type Options struct {
|
|||||||
// Expand enables macro expansion, include splicing and the
|
// Expand enables macro expansion, include splicing and the
|
||||||
// statement-separator reading of ';' that the expanded bodies rely on.
|
// statement-separator reading of ';' that the expanded bodies rely on.
|
||||||
Expand bool
|
Expand bool
|
||||||
|
// Predefines names the macros defined before the file is read. The
|
||||||
|
// go command drives go tool asm with -D GOOS_<goos> -D GOARCH_<arch>,
|
||||||
|
// and GOROOT's own headers (go_tls.h, asm_riscv64.h) select their
|
||||||
|
// platform blocks with #ifdef on exactly those names, so an assembler
|
||||||
|
// without them cannot see the platform definitions at all.
|
||||||
|
Predefines map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseWithOptions parses src like Parse, optionally preprocessing it first.
|
// ParseWithOptions parses src like Parse, optionally preprocessing it first.
|
||||||
@@ -44,6 +50,9 @@ func ParseWithOptions(path, src string, opts Options) (*ast.File, []error) {
|
|||||||
var errs []error
|
var errs []error
|
||||||
if opts.Expand {
|
if opts.Expand {
|
||||||
pp := &preproc{opts: opts, macros: map[string]*macroDef{}}
|
pp := &preproc{opts: opts, macros: map[string]*macroDef{}}
|
||||||
|
for name, value := range opts.Predefines {
|
||||||
|
pp.macros[name] = ¯oDef{name: name, body: lexer.Tokenize(value)}
|
||||||
|
}
|
||||||
lines = pp.fileLines(path, tokens, token.Position{})
|
lines = pp.fileLines(path, tokens, token.Position{})
|
||||||
errs = pp.errs
|
errs = pp.errs
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Vendored
+36
@@ -0,0 +1,36 @@
|
|||||||
|
// Kernel: the operand forms the GOROOT campaign surfaced — numeric
|
||||||
|
// PC-relative jumps, symbol-immediate materialisation (the toolchain rewrites
|
||||||
|
// MOVQ $sym(SB) into a RIP-relative LEA) and the negated constant-expression
|
||||||
|
// ADJSP the cgo ABI macros write. Bytes are pinned against go tool asm by
|
||||||
|
// TestDifferentialKernels.
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
DATA sd<>(SB)/4, $7
|
||||||
|
GLOBL sd<>(SB), RODATA, $4
|
||||||
|
|
||||||
|
// func Jumps(flag int64) int64
|
||||||
|
TEXT ·Jumps(SB), NOSPLIT, $0-16
|
||||||
|
MOVQ flag+0(FP), AX
|
||||||
|
TESTQ AX, AX
|
||||||
|
JEQ 2(PC)
|
||||||
|
MOVQ $1, AX
|
||||||
|
JMP 3(PC)
|
||||||
|
MOVQ $2, AX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func SymImm() int64
|
||||||
|
TEXT ·SymImm(SB), NOSPLIT, $0-16
|
||||||
|
MOVQ $sd<>(SB), AX
|
||||||
|
MOVQ $·SymImm(SB), CX
|
||||||
|
MOVQ AX, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// func Frame()
|
||||||
|
TEXT ·Frame(SB), NOSPLIT, $0
|
||||||
|
PUSHFQ
|
||||||
|
CLD
|
||||||
|
ADJSP $(64 - 8)
|
||||||
|
ADJSP $-(64 - 8)
|
||||||
|
POPFQ
|
||||||
|
RET
|
||||||
Reference in New Issue
Block a user