architecture, ctxt := setArch(goarch)
architecture.Init(ctxt)
lexer := lex.NewLexer(input)
- parser := NewParser(ctxt, architecture, lexer)
+ parser := NewParser(ctxt, architecture, lexer, false)
pList := new(obj.Plist)
var ok bool
testOut = new(bytes.Buffer) // The assembler writes test output to this buffer.
input := filepath.Join("testdata", file+".s")
architecture, ctxt := setArch(goarch)
lexer := lex.NewLexer(input)
- parser := NewParser(ctxt, architecture, lexer)
+ parser := NewParser(ctxt, architecture, lexer, false)
pList := new(obj.Plist)
var ok bool
testOut = new(bytes.Buffer) // The assembler writes test output to this buffer.
}
func TestExpr(t *testing.T) {
- p := NewParser(nil, nil, nil) // Expression evaluation uses none of these fields of the parser.
+ p := NewParser(nil, nil, nil, false) // Expression evaluation uses none of these fields of the parser.
for i, test := range exprTests {
p.start(lex.Tokenize(test.input))
result := int64(p.expr())
}
func runBadTest(i int, test badExprTest, t *testing.T) (err error) {
- p := NewParser(nil, nil, nil) // Expression evaluation uses none of these fields of the parser.
+ p := NewParser(nil, nil, nil, false) // Expression evaluation uses none of these fields of the parser.
p.start(lex.Tokenize(test.input))
return tryParse(t, func() {
p.expr()
for i, test := range tests {
arch, ctxt := setArch(goarch)
tokenizer := lex.NewTokenizer("", strings.NewReader(test.input+"\n"), nil)
- parser := NewParser(ctxt, arch, tokenizer)
+ parser := NewParser(ctxt, arch, tokenizer, false)
err := tryParse(t, func() {
parser.Parse()
func newParser(goarch string) *Parser {
architecture, ctxt := setArch(goarch)
- return NewParser(ctxt, architecture, nil)
+ return NewParser(ctxt, architecture, nil, false)
}
// tryParse executes parse func in panicOnError=true context.
parser.start(lex.Tokenize(test.input))
addr := obj.Addr{}
parser.operand(&addr)
- result := obj.Dconv(&emptyProg, &addr)
+ var result string
+ if parser.compilingRuntime {
+ result = obj.DconvWithABIDetail(&emptyProg, &addr)
+ } else {
+ result = obj.Dconv(&emptyProg, &addr)
+ }
if result != test.output {
t.Errorf("fail at %s: got %s; expected %s\n", test.input, result, test.output)
}
parser := newParser("amd64")
testOperandParser(t, parser, amd64OperandTests)
testBadOperandParser(t, parser, amd64BadOperandTests)
+ parser.compilingRuntime = true
+ testOperandParser(t, parser, amd64RuntimeOperandTests)
+ testBadOperandParser(t, parser, amd64BadOperandRuntimeTests)
}
func Test386OperandParser(t *testing.T) {
parser := newParser(sub.arch)
for _, test := range sub.tests {
parser.start(lex.Tokenize(test.input))
- name, ok := parser.funcAddress()
+ name, _, ok := parser.funcAddress()
isFuncSym := strings.HasSuffix(test.input, "(SB)") &&
// Ignore static symbols.
{"[):[o-FP", ""}, // Issue 12469 - asm hung parsing the o-FP range on non ARM platforms.
}
+var amd64RuntimeOperandTests = []operandTest{
+ {"$bar<ABI0>(SB)", "$bar<ABI0>(SB)"},
+ {"$foo<ABIInternal>(SB)", "$foo<ABIInternal>(SB)"},
+}
+
var amd64BadOperandTests = []badOperandTest{
{"[", "register list: expected ']', found EOF"},
{"[4", "register list: bad low register in `[4`"},
{"[X0-X1-X2]", "register list: expected ']' after `[X0-X1`, found '-'"},
{"[X0,X3]", "register list: expected '-' after `[X0`, found ','"},
{"[X0,X1,X2,X3]", "register list: expected '-' after `[X0`, found ','"},
+ {"$foo<ABI0>", "ABI selector only permitted when compiling runtime, reference was to \"foo\""},
+}
+
+var amd64BadOperandRuntimeTests = []badOperandTest{
+ {"$foo<bletch>", "malformed ABI selector \"bletch\" in reference to \"foo\""},
}
var x86OperandTests = []operandTest{
)
type Parser struct {
- lex lex.TokenReader
- lineNum int // Line number in source file.
- errorLine int // Line number of last error.
- errorCount int // Number of errors.
- sawCode bool // saw code in this file (as opposed to comments and blank lines)
- pc int64 // virtual PC; count of Progs; doesn't advance for GLOBL or DATA.
- input []lex.Token
- inputPos int
- pendingLabels []string // Labels to attach to next instruction.
- labels map[string]*obj.Prog
- toPatch []Patch
- addr []obj.Addr
- arch *arch.Arch
- ctxt *obj.Link
- firstProg *obj.Prog
- lastProg *obj.Prog
- dataAddr map[string]int64 // Most recent address for DATA for this symbol.
- isJump bool // Instruction being assembled is a jump.
- errorWriter io.Writer
+ lex lex.TokenReader
+ lineNum int // Line number in source file.
+ errorLine int // Line number of last error.
+ errorCount int // Number of errors.
+ sawCode bool // saw code in this file (as opposed to comments and blank lines)
+ pc int64 // virtual PC; count of Progs; doesn't advance for GLOBL or DATA.
+ input []lex.Token
+ inputPos int
+ pendingLabels []string // Labels to attach to next instruction.
+ labels map[string]*obj.Prog
+ toPatch []Patch
+ addr []obj.Addr
+ arch *arch.Arch
+ ctxt *obj.Link
+ firstProg *obj.Prog
+ lastProg *obj.Prog
+ dataAddr map[string]int64 // Most recent address for DATA for this symbol.
+ isJump bool // Instruction being assembled is a jump.
+ compilingRuntime bool
+ errorWriter io.Writer
}
type Patch struct {
label string
}
-func NewParser(ctxt *obj.Link, ar *arch.Arch, lexer lex.TokenReader) *Parser {
+func NewParser(ctxt *obj.Link, ar *arch.Arch, lexer lex.TokenReader, compilingRuntime bool) *Parser {
return &Parser{
- ctxt: ctxt,
- arch: ar,
- lex: lexer,
- labels: make(map[string]*obj.Prog),
- dataAddr: make(map[string]int64),
- errorWriter: os.Stderr,
+ ctxt: ctxt,
+ arch: ar,
+ lex: lexer,
+ labels: make(map[string]*obj.Prog),
+ dataAddr: make(map[string]int64),
+ errorWriter: os.Stderr,
+ compilingRuntime: compilingRuntime,
}
}
// Defines text symbol in operands[0].
if len(operands) > 0 {
p.start(operands[0])
- if name, ok := p.funcAddress(); ok {
- fmt.Fprintf(w, "def %s ABI0\n", name)
+ if name, abi, ok := p.funcAddress(); ok {
+ fmt.Fprintf(w, "def %s %s\n", name, abi)
}
}
return
// Search for symbol references.
for _, op := range operands {
p.start(op)
- if name, ok := p.funcAddress(); ok {
- fmt.Fprintf(w, "ref %s ABI0\n", name)
+ if name, abi, ok := p.funcAddress(); ok {
+ fmt.Fprintf(w, "ref %s %s\n", name, abi)
}
}
}
case '*':
a.Type = obj.TYPE_INDIR
}
- // Weirdness with statics: Might now have "<>".
- isStatic := false
- if p.peek() == '<' {
- isStatic = true
- p.next()
- p.get('>')
- }
+
+ // Parse optional <> (indicates a static symbol) or
+ // <ABIxxx> (selecting text symbol with specific ABI).
+ doIssueError := true
+ isStatic, abi := p.symRefAttrs(name, doIssueError)
+
if p.peek() == '+' || p.peek() == '-' {
a.Offset = int64(p.expr())
}
if isStatic {
a.Sym = p.ctxt.LookupStatic(name)
} else {
- a.Sym = p.ctxt.Lookup(name)
+ a.Sym = p.ctxt.LookupABI(name, abi)
}
if p.peek() == scanner.EOF {
if prefix == 0 && p.isJump {
}
}
+// symRefAttrs parses an optional function symbol attribute clause for
+// the function symbol 'name', logging an error for a malformed
+// attribute clause if 'issueError' is true. The return value is a
+// (boolean, ABI) pair indicating that the named symbol is either
+// static or a particular ABI specification.
+//
+// The expected form of the attribute clause is:
+//
+// empty, yielding (false, obj.ABI0)
+// "<>", yielding (true, obj.ABI0)
+// "<ABI0>" yielding (false, obj.ABI0)
+// "<ABIInternal>" yielding (false, obj.ABIInternal)
+//
+// Anything else beginning with "<" logs an error if issueError is
+// true, otherwise returns (false, obj.ABI0).
+//
+func (p *Parser) symRefAttrs(name string, issueError bool) (bool, obj.ABI) {
+ abi := obj.ABI0
+ isStatic := false
+ if p.peek() != '<' {
+ return isStatic, abi
+ }
+ p.next()
+ tok := p.peek()
+ if tok == '>' {
+ isStatic = true
+ } else if tok == scanner.Ident {
+ abistr := p.get(scanner.Ident).String()
+ if !p.compilingRuntime {
+ if issueError {
+ p.errorf("ABI selector only permitted when compiling runtime, reference was to %q", name)
+ }
+ } else {
+ theabi, valid := obj.ParseABI(abistr)
+ if !valid {
+ if issueError {
+ p.errorf("malformed ABI selector %q in reference to %q",
+ abistr, name)
+ }
+ } else {
+ abi = theabi
+ }
+ }
+ }
+ p.get('>')
+ return isStatic, abi
+}
+
// funcAddress parses an external function address. This is a
// constrained form of the operand syntax that's always SB-based,
// non-static, and has at most a simple integer offset:
//
-// [$|*]sym[+Int](SB)
-func (p *Parser) funcAddress() (string, bool) {
+// [$|*]sym[<abi>][+Int](SB)
+func (p *Parser) funcAddress() (string, obj.ABI, bool) {
switch p.peek() {
case '$', '*':
// Skip prefix.
tok := p.next()
name := tok.String()
if tok.ScanToken != scanner.Ident || p.atStartOfRegister(name) {
- return "", false
+ return "", obj.ABI0, false
+ }
+ // Parse optional <> (indicates a static symbol) or
+ // <ABIxxx> (selecting text symbol with specific ABI).
+ noErrMsg := false
+ isStatic, abi := p.symRefAttrs(name, noErrMsg)
+ if isStatic {
+ return "", obj.ABI0, false // This function rejects static symbols.
}
tok = p.next()
if tok.ScanToken == '+' {
if p.next().ScanToken != scanner.Int {
- return "", false
+ return "", obj.ABI0, false
}
tok = p.next()
}
if tok.ScanToken != '(' {
- return "", false
+ return "", obj.ABI0, false
}
if reg := p.next(); reg.ScanToken != scanner.Ident || reg.String() != "SB" {
- return "", false
+ return "", obj.ABI0, false
}
if p.next().ScanToken != ')' || p.peek() != scanner.EOF {
- return "", false
+ return "", obj.ABI0, false
}
- return name, true
+ return name, abi, true
}
// registerIndirect parses the general form of a register indirection.
{"TEXT", "$0É:0, 0, $1", "expected end of operand, found É"}, // Issue #12467.
{"TEXT", "$:0:(SB, 0, $1", "expected '(', found 0"}, // Issue 12468.
{"TEXT", "@B(SB),0,$0", "expected '(', found B"}, // Issue 23580.
+ {"TEXT", "foo<ABIInternal>(SB),0", "ABI selector only permitted when compiling runtime, reference was to \"foo\""},
{"FUNCDATA", "", "expect two operands for FUNCDATA"},
{"FUNCDATA", "(SB ", "expect two operands for FUNCDATA"},
{"DATA", "", "expect two operands for DATA"},
case "all", "ret":
ctxt.Retpoline = true
}
+ compilingRuntime := objabi.IsRuntimePackagePath(*flags.Importpath)
ctxt.Bso = bufio.NewWriter(os.Stdout)
defer ctxt.Bso.Flush()
var failedFile string
for _, f := range flag.Args() {
lexer := lex.NewLexer(f)
- parser := asm.NewParser(ctxt, architecture, lexer)
+ parser := asm.NewParser(ctxt, architecture, lexer, compilingRuntime)
ctxt.DiagFunc = func(format string, args ...interface{}) {
diag = true
log.Printf(format, args...)
if len(parts) != 3 {
log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0])
}
- sym, abi := parts[1], parts[2]
- if abi != "ABI0" { // Only supported external ABI right now
- log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abi)
+ sym, abistr := parts[1], parts[2]
+ abi, valid := obj.ParseABI(abistr)
+ if !valid {
+ log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr)
}
// If the symbol is already prefixed with
// Record for later.
if parts[0] == "def" {
- symabiDefs[sym] = obj.ABI0
+ symabiDefs[sym] = abi
} else {
- symabiRefs[sym] = obj.ABI0
+ symabiRefs[sym] = abi
}
default:
log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0])
ABICount
)
+// ParseABI converts from a string representation in 'abistr' to the
+// corresponding ABI value. Second return value is TRUE if the
+// abi string is recognized, FALSE otherwise.
+func ParseABI(abistr string) (ABI, bool) {
+ switch abistr {
+ default:
+ return ABI0, false
+ case "ABI0":
+ return ABI0, true
+ case "ABIInternal":
+ return ABIInternal, true
+ }
+}
+
// Attribute is a set of symbol attributes.
type Attribute uint32
return ctxt.Debugasm == 0
}
+// Dconv accepts an argument 'a' within a prog 'p' and returns a string
+// with a formatted version of the argument.
func Dconv(p *Prog, a *Addr) string {
buf := new(bytes.Buffer)
- WriteDconv(buf, p, a)
+ writeDconv(buf, p, a, false)
return buf.String()
}
+// DconvDconvWithABIDetail accepts an argument 'a' within a prog 'p'
+// and returns a string with a formatted version of the argument, in
+// which text symbols are rendered with explicit ABI selectors.
+func DconvWithABIDetail(p *Prog, a *Addr) string {
+ buf := new(bytes.Buffer)
+ writeDconv(buf, p, a, true)
+ return buf.String()
+}
+
+// WriteDconv accepts an argument 'a' within a prog 'p'
+// and writes a formatted version of the arg to the writer.
func WriteDconv(w io.Writer, p *Prog, a *Addr) {
+ writeDconv(w, p, a, false)
+}
+
+func writeDconv(w io.Writer, p *Prog, a *Addr, abiDetail bool) {
switch a.Type {
default:
fmt.Fprintf(w, "type=%d", a.Type)
case TYPE_BRANCH:
if a.Sym != nil {
- fmt.Fprintf(w, "%s(SB)", a.Sym.Name)
+ fmt.Fprintf(w, "%s%s(SB)", a.Sym.Name, abiDecorate(a, abiDetail))
} else if a.Target() != nil {
fmt.Fprint(w, a.Target().Pc)
} else {
case TYPE_INDIR:
io.WriteString(w, "*")
- a.WriteNameTo(w)
+ a.writeNameTo(w, abiDetail)
case TYPE_MEM:
a.WriteNameTo(w)
case TYPE_ADDR:
io.WriteString(w, "$")
- a.WriteNameTo(w)
+ a.writeNameTo(w, abiDetail)
case TYPE_SHIFT:
v := int(a.Offset)
}
func (a *Addr) WriteNameTo(w io.Writer) {
+ a.writeNameTo(w, false)
+}
+
+func (a *Addr) writeNameTo(w io.Writer, abiDetail bool) {
+
switch a.Name {
default:
fmt.Fprintf(w, "name=%d", a.Name)
reg = Rconv(int(a.Reg))
}
if a.Sym != nil {
- fmt.Fprintf(w, "%s%s(%s)", a.Sym.Name, offConv(a.Offset), reg)
+ fmt.Fprintf(w, "%s%s%s(%s)", a.Sym.Name, abiDecorate(a, abiDetail), offConv(a.Offset), reg)
} else {
fmt.Fprintf(w, "%s(%s)", offConv(a.Offset), reg)
}
}
return i
}
+
+func abiDecorate(a *Addr, abiDetail bool) string {
+ if !abiDetail || a.Sym == nil {
+ return ""
+ }
+ return fmt.Sprintf("<%s>", a.Sym.ABI())
+}
return string(p)
}
+
+// IsRuntimePackagePath examines 'pkgpath' and returns TRUE if it
+// belongs to the collection of "runtime-related" packages, including
+// "runtime" itself, "reflect", "syscall", and the
+// "runtime/internal/*" packages. The compiler and/or assembler in
+// some cases need to be aware of when they are building such a
+// package, for example to enable features such as ABI selectors in
+// assembly sources.
+func IsRuntimePackagePath(pkgpath string) bool {
+ rval := false
+ switch pkgpath {
+ case "runtime":
+ rval = true
+ case "reflect":
+ rval = true
+ case "syscall":
+ rval = true
+ default:
+ rval = strings.HasPrefix(pkgpath, "runtime/internal")
+ }
+ return rval
+}