feat(parser): evaluate the TEXT and GLOBL flags operand as one expression

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 21:50:20 +02:00
1 parent 4eb9100def
commit cea5db6964
4 files changed
+239 -16

No files matched your search

+39 -13
View File
@@ -43,6 +43,12 @@ type state struct {
file *ast.File
errs []error
// expand records the assembly path (ParseWithOptions with Expand): the
// one view of a file that must reject what the toolchain's assembler
// rejects. The tooling view (linter, formatter, language server) stays
// tolerant and reports through its own diagnostics instead.
expand bool
curText *ast.Text // the TEXT body labels/instructions attach to
pending []string // comment lines awaiting a TEXT to become its Doc
}
@@ -245,13 +251,14 @@ func (p *state) parseText(line []token.Token) {
text.Name = sym
rest = rest[n:]
// Consume flags (identifiers, possibly '|' joined) up to the frame '$'.
// Consume the flags operand: everything between the symbol and the
// frame '$' is one operand, which the toolchain evaluates to a single
// integer (identifiers joined by '|', each a known textflag.h name,
// literals and constant arithmetic beside them). A single trailing
// comma is the separator that stood before the '$'.
rest = skipComma(rest)
text.Flags, text.FlagVal = p.evalFlags(flagsRun(rest), false)
for len(rest) > 0 && rest[0].Kind != token.Dollar {
if rest[0].Kind == token.Ident {
text.Flags = append(text.Flags, rest[0].Text)
}
// Commas, '|' and anything else between flags is skipped.
rest = rest[1:]
}
@@ -291,6 +298,23 @@ func (p *state) parseText(line []token.Token) {
}
}
// The toolchain's own TEXT diagnostics over the evaluated flags
// (cmd/asm/internal/asm/asm.go asmText, and the frame check its arm64
// backend raises): an ABIInternal symbol must not grow the stack, and
// NOFRAME reserves no frame at all, so a declared frame contradicts it.
// Both belong to the assembly path; the tooling view reports the frame
// case through the linter's own advisory instead.
if p.expand {
if text.Name.ABI == "ABIInternal" && text.FlagVal&flagNOSPLIT == 0 {
p.errorf(line[0].Pos, "TEXT %q: ABIInternal requires NOSPLIT", text.Name.Name)
}
if text.FlagVal&flagNOFRAME != 0 && text.Frame != nil &&
text.Frame.Imm.HasVal && text.Frame.Imm.Val > 0 {
p.errorf(text.Frame.Pos, "NOFRAME functions must have a frame size of 0, not %d",
text.Frame.Imm.Val)
}
}
p.file.Decls = append(p.file.Decls, text)
p.curText = text
}
@@ -309,15 +333,17 @@ func (p *state) parseGlobl(line []token.Token) *ast.Globl {
g.Name = sym
rest = skipComma(rest[n:])
// Flags are identifiers (RODATA, DUPOK) or legacy numeric constants
// (2, 8, 9, 10) from runtime/textflag.h.
for len(rest) > 0 && rest[0].Kind != token.Dollar {
if rest[0].Kind == token.Ident || rest[0].Kind == token.Number {
g.Flags = append(g.Flags, rest[0].Text)
// (2, 8, 9, 10) from runtime/textflag.h. The toolchain evaluates the
// operand as one constant expression, so every name is validated here
// and the whole operand folds to g.FlagVal; Flags keeps the atoms as
// written, the numeric spellings being data-side combinations the
// link layer reads back.
g.Flags, g.FlagVal = p.evalFlags(flagsRun(rest), true)
for i, t := range rest {
if t.Kind == token.Dollar {
g.Size = parseOperand(rest[i:], false)
break
}
rest = rest[1:]
}
if len(rest) > 0 && rest[0].Kind == token.Dollar {
g.Size = parseOperand(rest, false)
}
return g
}