feat(parser): evaluate the TEXT and GLOBL flags operand as one expression
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
4eb9100def
commit
cea5db6964
4 files changed
+239
-16
No files matched your search
+187
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// The TEXT and GLOBL flags operand.
|
||||
//
|
||||
// The toolchain reads the middle operand of a TEXT (and of a GLOBL) as one
|
||||
// constant expression: identifiers joined by '|', each a name
|
||||
// runtime/textflag.h defines as a number, with literals and constant
|
||||
// arithmetic beside them (cmd/asm/internal/asm/asm.go, evalInteger). gasm
|
||||
// consumes textflag.h natively rather than expanding it, so the names are
|
||||
// evaluated here against the same table, and an identifier outside it is
|
||||
// the toolchain's evaluation failure.
|
||||
package parser
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/token"
|
||||
)
|
||||
|
||||
// textFlags is the flag table of runtime/textflag.h: the names the
|
||||
// assembler knows and the values they stand for.
|
||||
var textFlags = map[string]int64{
|
||||
"NOPROF": 1,
|
||||
"DUPOK": 2,
|
||||
"NOSPLIT": 4,
|
||||
"RODATA": 8,
|
||||
"NOPTR": 16,
|
||||
"WRAPPER": 32,
|
||||
"NEEDCTXT": 64,
|
||||
"TLSBSS": 256,
|
||||
"NOFRAME": 512,
|
||||
"REFLECTMETHOD": 1024,
|
||||
"TOPFRAME": 2048,
|
||||
"ABIWRAPPER": 4096,
|
||||
}
|
||||
|
||||
// flagNOSPLIT and flagNOFRAME single out the two flags whose value the
|
||||
// parser itself reasons about, in the toolchain's own TEXT diagnostics.
|
||||
const (
|
||||
flagNOSPLIT = 4
|
||||
flagNOFRAME = 512
|
||||
)
|
||||
|
||||
// flagOrder lists the table ascending by value, the order the canonical
|
||||
// name expansion of a folded value reads in.
|
||||
var flagOrder = []struct {
|
||||
name string
|
||||
val int64
|
||||
}{
|
||||
{"NOPROF", 1},
|
||||
{"DUPOK", 2},
|
||||
{"NOSPLIT", 4},
|
||||
{"RODATA", 8},
|
||||
{"NOPTR", 16},
|
||||
{"WRAPPER", 32},
|
||||
{"NEEDCTXT", 64},
|
||||
{"TLSBSS", 256},
|
||||
{"NOFRAME", 512},
|
||||
{"REFLECTMETHOD", 1024},
|
||||
{"TOPFRAME", 2048},
|
||||
{"ABIWRAPPER", 4096},
|
||||
}
|
||||
|
||||
// flagsRun returns the flags operand of a TEXT or GLOBL directive: the
|
||||
// tokens after the symbol up to the frame operand's '$', with comments and
|
||||
// the one trailing comma that separated the operand from the '$' removed.
|
||||
func flagsRun(g []token.Token) []token.Token {
|
||||
n := 0
|
||||
for n < len(g) && g[n].Kind != token.Dollar {
|
||||
n++
|
||||
}
|
||||
out := make([]token.Token, 0, n)
|
||||
for _, t := range g[:n] {
|
||||
if t.Kind == token.Comment {
|
||||
continue
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
if len(out) > 0 && out[len(out)-1].Kind == token.Comma {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// evalFlags evaluates one flags operand. Every identifier must name a
|
||||
// flag in textFlags; the toolchain rejects any other with "unexpected NAME
|
||||
// evaluating expression", and so does this. The whole operand folds to
|
||||
// one integer, which is returned as the value.
|
||||
//
|
||||
// The names returned follow the operand's own spelling: an operand written
|
||||
// purely as names keeps them in written order, duplicates included, the
|
||||
// toolchain accepting both silently; an operand that mixes in literals or
|
||||
// arithmetic has no written names to keep, so the folded value expands to
|
||||
// the canonical ascending name list. Bits outside the table (a literal
|
||||
// such as 8192) live in the value alone; the names cannot spell them.
|
||||
//
|
||||
// After a failure the names collected so far are returned with value 0:
|
||||
// the tree stays usable beside the diagnostic, and value 0 is exactly the
|
||||
// flags the assembler can trust the operand for.
|
||||
func (p *state) evalFlags(g []token.Token, keepWritten bool) ([]string, int64) {
|
||||
if len(g) == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
// An explicit '$' makes the operand an immediate ('$NOSPLIT' over the
|
||||
// preprocessor's '$4'); the toolchain reads it as the same constant.
|
||||
if g[0].Kind == token.Dollar {
|
||||
g = g[1:]
|
||||
if len(g) == 0 {
|
||||
return nil, 0
|
||||
}
|
||||
}
|
||||
|
||||
sub := make([]token.Token, 0, len(g))
|
||||
var names []string
|
||||
namesOnly := true
|
||||
failed := false
|
||||
for _, t := range g {
|
||||
if t.Kind == token.Ident {
|
||||
v, ok := textFlags[t.Text]
|
||||
if !ok {
|
||||
if p.expand {
|
||||
p.errorf(t.Pos, "unexpected %s evaluating expression", t.Text)
|
||||
}
|
||||
failed = true
|
||||
}
|
||||
names = append(names, t.Text)
|
||||
if ok {
|
||||
sub = append(sub, numberToken(v, t))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if t.Kind != token.Pipe {
|
||||
// Anything beyond names and their '|' separators gives the
|
||||
// operand arithmetic the written names cannot express.
|
||||
namesOnly = false
|
||||
}
|
||||
if keepWritten && t.Kind == token.Number {
|
||||
names = append(names, t.Text)
|
||||
}
|
||||
sub = append(sub, t)
|
||||
}
|
||||
if failed {
|
||||
return names, 0
|
||||
}
|
||||
v, rest, ok := foldExpr(sub)
|
||||
if !ok || len(rest) > 0 {
|
||||
if p.expand {
|
||||
bad := g[0]
|
||||
if ok && len(rest) > 0 {
|
||||
bad = rest[0]
|
||||
}
|
||||
p.errorf(bad.Pos, "unexpected %s evaluating expression", bad.Text)
|
||||
}
|
||||
return names, 0
|
||||
}
|
||||
if keepWritten {
|
||||
return names, v
|
||||
}
|
||||
if namesOnly {
|
||||
// Written names stay exactly as written; the value folds them.
|
||||
return names, v
|
||||
}
|
||||
return flagNames(v), v
|
||||
}
|
||||
|
||||
// flagNames expands a folded flags value into the canonical ascending name
|
||||
// list of the table's bits it holds. A negative value expands to nothing:
|
||||
// its bit pattern is not a combination the names can spell.
|
||||
func flagNames(v int64) []string {
|
||||
if v <= 0 {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, f := range flagOrder {
|
||||
if v&f.val != 0 {
|
||||
out = append(out, f.name)
|
||||
v &^= f.val
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// numberToken rebuilds t as the decimal literal of v at t's position.
|
||||
func numberToken(v int64, t token.Token) token.Token {
|
||||
return token.Token{Kind: token.Number, Text: strconv.FormatInt(v, 10), Pos: t.Pos, End: t.End}
|
||||
}
|
||||
Reference in new issue
Block a user