Files
gasm-sdk/parser/flags.go
T

185 lines
5.3 KiB
Go
Raw Normal View History

// 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 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}
}