186 lines
5.3 KiB
Go
186 lines
5.3 KiB
Go
// 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}
|
|
}
|