// Copyright (c) 2026 Petr BalvĂ­n (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 strips the comments from one flags operand. A comment on a // directive line is always its trailing text (a comment mid-line is the // statement boundary the statement reader makes of it), so what the // operand split hands over carries it at the tail at most. func flagsRun(g []token.Token) []token.Token { out := make([]token.Token, 0, len(g)) for _, t := range g { if t.Kind != token.Comment { out = append(out, t) } } 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. pseudo names the directive // in the toolchain's own diagnostics ("TEXT", "GLOBL"). // // 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, pseudo string) ([]string, int64) { if len(g) == 0 { return nil, 0 } // The flags operand is a bare constant expression; the immediate // spelling is the toolchain's "TEXT: expected integer constant". if g[0].Kind == token.Dollar { if p.expand { found := "$" if len(g) > 1 { found += g[1].Text } p.errorf(g[0].Pos, "%s: expected integer constant; found %s", pseudo, found) } 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} }