| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package asm |
| |
| import ( |
| "fmt" |
| "strings" |
| "text/scanner" |
| |
| "cmd/asm/internal/addr" |
| "cmd/asm/internal/arch" |
| "cmd/asm/internal/lex" |
| "cmd/internal/obj" |
| ) |
| |
| // TODO: This package has many numeric conversions that should be unnecessary. |
| |
| // symbolType returns the extern/static etc. type appropriate for the symbol. |
| func (p *Parser) symbolType(a *addr.Addr) int { |
| switch a.Register { |
| case arch.RFP: |
| return p.arch.D_PARAM |
| case arch.RSP: |
| return p.arch.D_AUTO |
| case arch.RSB: |
| // See comment in addrToAddr. |
| if a.IsImmediateAddress { |
| return p.arch.D_ADDR |
| } |
| if a.IsStatic { |
| return p.arch.D_STATIC |
| } |
| return p.arch.D_EXTERN |
| } |
| p.errorf("invalid register for symbol %s", a.Symbol) |
| return 0 |
| } |
| |
| // TODO: configure the architecture |
| |
| // TODO: This is hacky and irregular. When obj settles down, rewrite for simplicity. |
| func (p *Parser) addrToAddr(a *addr.Addr) obj.Addr { |
| out := p.arch.NoAddr |
| if a.Has(addr.Symbol) { |
| // How to encode the symbols: |
| // syntax = Typ,Index |
| // $a(SB) = ADDR,EXTERN |
| // $a<>(SB) = ADDR,STATIC |
| // a(SB) = EXTERN,NONE |
| // a<>(SB) = STATIC,NONE |
| // The call to symbolType does the first column; we need to fix up Index here. |
| out.Type = int16(p.symbolType(a)) |
| out.Sym = obj.Linklookup(p.linkCtxt, a.Symbol, 0) |
| if a.IsImmediateAddress { |
| // Index field says whether it's a static. |
| switch a.Register { |
| case arch.RSB: |
| if a.IsStatic { |
| out.Index = uint8(p.arch.D_STATIC) |
| } else { |
| out.Index = uint8(p.arch.D_EXTERN) |
| } |
| default: |
| p.errorf("can't handle immediate address of %s not (SB)\n", a.Symbol) |
| } |
| } |
| } else if a.Has(addr.Register) { |
| // TODO: SP is tricky, and this isn't good enough. |
| // SP = D_SP |
| // 4(SP) = 4(D_SP) |
| // x+4(SP) = D_AUTO with sym=x TODO |
| out.Type = a.Register |
| if a.Register == arch.RSP { |
| out.Type = int16(p.arch.SP) |
| } |
| if a.IsIndirect { |
| out.Type += int16(p.arch.D_INDIR) |
| } |
| // a.Register2 handled in the instruction method; it's bizarre. |
| } |
| if a.Has(addr.Index) { |
| out.Index = uint8(a.Index) // TODO: out.Index == p.NoArch.Index should be same type as Register. |
| } |
| if a.Has(addr.Scale) { |
| out.Scale = a.Scale |
| } |
| if a.Has(addr.Offset) { |
| out.Offset = a.Offset |
| if a.Is(addr.Offset) { |
| // RHS of MOVL $0xf1, 0xf1 // crash |
| out.Type = int16(p.arch.D_INDIR + p.arch.D_NONE) |
| } else if a.IsImmediateConstant && out.Type == int16(p.arch.D_NONE) { |
| out.Type = int16(p.arch.D_CONST) |
| } |
| } |
| if a.Has(addr.Float) { |
| out.U.Dval = a.Float |
| out.Type = int16(p.arch.D_FCONST) |
| } |
| if a.Has(addr.String) { |
| out.U.Sval = a.String |
| out.Type = int16(p.arch.D_SCONST) |
| } |
| // TODO from https://go-review.googlesource.com/#/c/3196/ { |
| // There's a general rule underlying this special case and the one at line 91 (RHS OF MOVL $0xf1). |
| // Unless there's a $, it's an indirect. |
| // 4(R1)(R2*8) |
| // 4(R1) |
| // 4(R2*8) |
| // 4 |
| // (R1)(R2*8) |
| // (R1) |
| // (R2*8) |
| // There should be a more general approach that doesn't just pick off cases. |
| // } |
| if a.IsIndirect && !a.Has(addr.Register) && a.Has(addr.Index) { |
| // LHS of LEAQ 0(BX*8), CX |
| out.Type = int16(p.arch.D_INDIR + p.arch.D_NONE) |
| } |
| return out |
| } |
| |
| func (p *Parser) append(prog *obj.Prog, doLabel bool) { |
| if p.firstProg == nil { |
| p.firstProg = prog |
| } else { |
| p.lastProg.Link = prog |
| } |
| p.lastProg = prog |
| if doLabel { |
| p.pc++ |
| for _, label := range p.pendingLabels { |
| if p.labels[label] != nil { |
| p.errorf("label %q multiply defined", label) |
| } |
| p.labels[label] = prog |
| } |
| p.pendingLabels = p.pendingLabels[0:0] |
| } |
| prog.Pc = int64(p.pc) |
| fmt.Println(p.histLineNum, prog) |
| } |
| |
| // asmText assembles a TEXT pseudo-op. |
| // TEXT runtime·sigtramp(SB),4,$0-0 |
| func (p *Parser) asmText(word string, operands [][]lex.Token) { |
| if len(operands) != 2 && len(operands) != 3 { |
| p.errorf("expect two or three operands for TEXT") |
| } |
| |
| // Operand 0 is the symbol name in the form foo(SB). |
| // That means symbol plus indirect on SB and no offset. |
| nameAddr := p.address(operands[0]) |
| if !nameAddr.Is(addr.Symbol|addr.Register|addr.Indirect) || nameAddr.Register != arch.RSB { |
| p.errorf("TEXT symbol %q must be an offset from SB", nameAddr.Symbol) |
| } |
| name := nameAddr.Symbol |
| next := 1 |
| |
| // Next operand is the optional text flag, a literal integer. |
| flag := int8(0) |
| if len(operands) == 3 { |
| flagAddr := p.address(operands[next]) |
| if !flagAddr.Is(addr.Offset) { |
| p.errorf("TEXT flag for %s must be an integer", name) |
| } |
| flag = int8(flagAddr.Offset) |
| next++ |
| } |
| |
| // Next operand is the frame and arg size. |
| // Bizarre syntax: $frameSize-argSize is two words, not subtraction. |
| // Both frameSize and argSize must be simple integers; only frameSize |
| // can be negative. |
| // The "-argSize" may be missing; if so, set it to obj.ArgsSizeUnknown. |
| // Parse left to right. |
| op := operands[next] |
| if len(op) < 2 || op[0].ScanToken != '$' { |
| p.errorf("TEXT %s: frame size must be an immediate constant", name) |
| } |
| op = op[1:] |
| negative := false |
| if op[0].ScanToken == '-' { |
| negative = true |
| op = op[1:] |
| } |
| if len(op) == 0 || op[0].ScanToken != scanner.Int { |
| p.errorf("TEXT %s: frame size must be an immediate constant", name) |
| } |
| frameSize := p.positiveAtoi(op[0].String()) |
| if negative { |
| frameSize = -frameSize |
| } |
| op = op[1:] |
| argSize := int64(obj.ArgsSizeUnknown) |
| if len(op) > 0 { |
| // There is an argument size. It must be a minus sign followed by a non-negative integer literal. |
| if len(op) != 2 || op[0].ScanToken != '-' || op[1].ScanToken != scanner.Int { |
| p.errorf("TEXT %s: argument size must be of form -integer", name) |
| } |
| argSize = p.positiveAtoi(op[1].String()) |
| } |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| As: int16(p.arch.ATEXT), |
| Lineno: int32(p.histLineNum), |
| From: obj.Addr{ |
| Type: int16(p.symbolType(&nameAddr)), |
| Index: uint8(p.arch.D_NONE), |
| Sym: obj.Linklookup(p.linkCtxt, name, 0), |
| Scale: flag, |
| }, |
| To: obj.Addr{ |
| Index: uint8(p.arch.D_NONE), |
| }, |
| } |
| // Encoding of frameSize and argSize depends on architecture. |
| switch p.arch.Thechar { |
| case '6': |
| prog.To.Type = int16(p.arch.D_CONST) |
| prog.To.Offset = (argSize << 32) | frameSize |
| case '8': |
| prog.To.Type = int16(p.arch.D_CONST2) |
| prog.To.Offset = frameSize |
| prog.To.Offset2 = int32(argSize) |
| default: |
| p.errorf("internal error: can't encode TEXT $arg-frame") |
| } |
| p.append(prog, true) |
| } |
| |
| // asmData assembles a DATA pseudo-op. |
| // DATA masks<>+0x00(SB)/4, $0x00000000 |
| func (p *Parser) asmData(word string, operands [][]lex.Token) { |
| if len(operands) != 2 { |
| p.errorf("expect two operands for DATA") |
| } |
| |
| // Operand 0 has the general form foo<>+0x04(SB)/4. |
| op := operands[0] |
| n := len(op) |
| if n < 3 || op[n-2].ScanToken != '/' || op[n-1].ScanToken != scanner.Int { |
| p.errorf("expect /size for DATA argument") |
| } |
| scale := p.scale(op[n-1].String()) |
| op = op[:n-2] |
| nameAddr := p.address(op) |
| ok := nameAddr.Is(addr.Symbol|addr.Register|addr.Indirect) || nameAddr.Is(addr.Symbol|addr.Register|addr.Indirect|addr.Offset) |
| if !ok || nameAddr.Register != arch.RSB { |
| p.errorf("DATA symbol %q must be an offset from SB", nameAddr.Symbol) |
| } |
| name := strings.Replace(nameAddr.Symbol, "·", ".", 1) |
| |
| // Operand 1 is an immediate constant or address. |
| valueAddr := p.address(operands[1]) |
| if !valueAddr.IsImmediateConstant && !valueAddr.IsImmediateAddress { |
| p.errorf("DATA value must be an immediate constant or address") |
| } |
| |
| // The addresses must not overlap. Easiest test: require monotonicity. |
| if lastAddr, ok := p.dataAddr[name]; ok && nameAddr.Offset < lastAddr { |
| p.errorf("overlapping DATA entry for %s", nameAddr.Symbol) |
| } |
| p.dataAddr[name] = nameAddr.Offset + int64(scale) |
| |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| As: int16(p.arch.ADATA), |
| Lineno: int32(p.histLineNum), |
| From: obj.Addr{ |
| Type: int16(p.symbolType(&nameAddr)), |
| Index: uint8(p.arch.D_NONE), |
| Sym: obj.Linklookup(p.linkCtxt, name, 0), |
| Offset: nameAddr.Offset, |
| Scale: scale, |
| }, |
| To: p.addrToAddr(&valueAddr), |
| } |
| |
| p.append(prog, false) |
| } |
| |
| // asmGlobl assembles a GLOBL pseudo-op. |
| // GLOBL shifts<>(SB),8,$256 |
| // GLOBL shifts<>(SB),$256 |
| func (p *Parser) asmGlobl(word string, operands [][]lex.Token) { |
| if len(operands) != 2 && len(operands) != 3 { |
| p.errorf("expect two or three operands for GLOBL") |
| } |
| |
| // Operand 0 has the general form foo<>+0x04(SB). |
| nameAddr := p.address(operands[0]) |
| if !nameAddr.Is(addr.Symbol|addr.Register|addr.Indirect) || nameAddr.Register != arch.RSB { |
| p.errorf("GLOBL symbol %q must be an offset from SB", nameAddr.Symbol) |
| } |
| name := strings.Replace(nameAddr.Symbol, "·", ".", 1) |
| |
| // If three operands, middle operand is a scale. |
| scale := int8(0) |
| op := operands[1] |
| if len(operands) == 3 { |
| scaleAddr := p.address(op) |
| if !scaleAddr.Is(addr.Offset) { |
| p.errorf("GLOBL scale must be a constant") |
| } |
| scale = int8(scaleAddr.Offset) |
| op = operands[2] |
| } |
| |
| // Final operand is an immediate constant. |
| sizeAddr := p.address(op) |
| if !sizeAddr.Is(addr.ImmediateConstant | addr.Offset) { |
| p.errorf("GLOBL size must be an immediate constant") |
| } |
| size := sizeAddr.Offset |
| |
| // log.Printf("GLOBL %s %d, $%d", name, scale, size) |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| As: int16(p.arch.AGLOBL), |
| Lineno: int32(p.histLineNum), |
| From: obj.Addr{ |
| Type: int16(p.symbolType(&nameAddr)), |
| Index: uint8(p.arch.D_NONE), |
| Sym: obj.Linklookup(p.linkCtxt, name, 0), |
| Offset: nameAddr.Offset, |
| Scale: scale, |
| }, |
| To: obj.Addr{ |
| Type: int16(p.arch.D_CONST), |
| Index: uint8(p.arch.D_NONE), |
| Offset: size, |
| }, |
| } |
| p.append(prog, false) |
| } |
| |
| // asmPCData assembles a PCDATA pseudo-op. |
| // PCDATA $2, $705 |
| func (p *Parser) asmPCData(word string, operands [][]lex.Token) { |
| if len(operands) != 2 { |
| p.errorf("expect two operands for PCDATA") |
| } |
| |
| // Operand 0 must be an immediate constant. |
| addr0 := p.address(operands[0]) |
| if !addr0.Is(addr.ImmediateConstant | addr.Offset) { |
| p.errorf("PCDATA value must be an immediate constant") |
| } |
| value0 := addr0.Offset |
| |
| // Operand 1 must be an immediate constant. |
| addr1 := p.address(operands[1]) |
| if !addr1.Is(addr.ImmediateConstant | addr.Offset) { |
| p.errorf("PCDATA value must be an immediate constant") |
| } |
| value1 := addr1.Offset |
| |
| // log.Printf("PCDATA $%d, $%d", value0, value1) |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| As: int16(p.arch.APCDATA), |
| Lineno: int32(p.histLineNum), |
| From: obj.Addr{ |
| Type: int16(p.arch.D_CONST), |
| Index: uint8(p.arch.D_NONE), |
| Offset: value0, |
| }, |
| To: obj.Addr{ |
| Type: int16(p.arch.D_CONST), |
| Index: uint8(p.arch.D_NONE), |
| Offset: value1, |
| }, |
| } |
| p.append(prog, true) |
| } |
| |
| // asmFuncData assembles a FUNCDATA pseudo-op. |
| // FUNCDATA $1, funcdata<>+4(SB) |
| func (p *Parser) asmFuncData(word string, operands [][]lex.Token) { |
| if len(operands) != 2 { |
| p.errorf("expect two operands for FUNCDATA") |
| } |
| |
| // Operand 0 must be an immediate constant. |
| valueAddr := p.address(operands[0]) |
| if !valueAddr.Is(addr.ImmediateConstant | addr.Offset) { |
| p.errorf("FUNCDATA value must be an immediate constant") |
| } |
| value := valueAddr.Offset |
| |
| // Operand 1 is a symbol name in the form foo(SB). |
| // That means symbol plus indirect on SB and no offset. |
| nameAddr := p.address(operands[1]) |
| if !nameAddr.Is(addr.Symbol|addr.Register|addr.Indirect) || nameAddr.Register != arch.RSB { |
| p.errorf("FUNCDATA symbol %q must be an offset from SB", nameAddr.Symbol) |
| } |
| name := strings.Replace(nameAddr.Symbol, "·", ".", 1) |
| |
| // log.Printf("FUNCDATA %s, $%d", name, value) |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| As: int16(p.arch.AFUNCDATA), |
| Lineno: int32(p.histLineNum), |
| From: obj.Addr{ |
| Type: int16(p.arch.D_CONST), |
| Index: uint8(p.arch.D_NONE), |
| Offset: value, |
| }, |
| To: obj.Addr{ |
| Type: int16(p.symbolType(&nameAddr)), |
| Index: uint8(p.arch.D_NONE), |
| Sym: obj.Linklookup(p.linkCtxt, name, 0), |
| }, |
| } |
| p.append(prog, true) |
| } |
| |
| // asmJump assembles a jump instruction. |
| // JMP R1 |
| // JMP exit |
| // JMP 3(PC) |
| func (p *Parser) asmJump(op int, a []addr.Addr) { |
| var target *addr.Addr |
| switch len(a) { |
| default: |
| p.errorf("jump must have one or two addresses") |
| case 1: |
| target = &a[0] |
| case 2: |
| if !a[0].Is(0) { |
| p.errorf("two-address jump must have empty first address") |
| } |
| target = &a[1] |
| } |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| Lineno: int32(p.histLineNum), |
| As: int16(op), |
| From: p.arch.NoAddr, |
| } |
| switch { |
| case target.Is(addr.Register): |
| // JMP R1 |
| prog.To = p.addrToAddr(target) |
| case target.Is(addr.Symbol): |
| // JMP exit |
| targetProg := p.labels[target.Symbol] |
| if targetProg == nil { |
| p.toPatch = append(p.toPatch, Patch{prog, target.Symbol}) |
| } else { |
| p.branch(prog, targetProg) |
| } |
| case target.Is(addr.Register | addr.Indirect), target.Is(addr.Register | addr.Indirect | addr.Offset): |
| // JMP 4(AX) |
| if target.Register == arch.RPC { |
| prog.To = obj.Addr{ |
| Type: int16(p.arch.D_BRANCH), |
| Index: uint8(p.arch.D_NONE), |
| Offset: p.pc + 1 + target.Offset, // +1 because p.pc is incremented in link, below. |
| } |
| } else { |
| prog.To = p.addrToAddr(target) |
| } |
| case target.Is(addr.Symbol | addr.Indirect | addr.Register): |
| // JMP main·morestack(SB) |
| if target.Register != arch.RSB { |
| p.errorf("jmp to symbol must be SB-relative") |
| } |
| prog.To = obj.Addr{ |
| Type: int16(p.arch.D_BRANCH), |
| Sym: obj.Linklookup(p.linkCtxt, target.Symbol, 0), |
| Index: uint8(p.arch.D_NONE), |
| Offset: target.Offset, |
| } |
| default: |
| p.errorf("cannot assemble jump %+v", target) |
| } |
| p.append(prog, true) |
| } |
| |
| func (p *Parser) patch() { |
| for _, patch := range p.toPatch { |
| targetProg := p.labels[patch.label] |
| if targetProg == nil { |
| p.errorf("undefined label %s", patch.label) |
| } else { |
| p.branch(patch.prog, targetProg) |
| } |
| } |
| } |
| |
| func (p *Parser) branch(jmp, target *obj.Prog) { |
| jmp.To = obj.Addr{ |
| Type: int16(p.arch.D_BRANCH), |
| Index: uint8(p.arch.D_NONE), |
| } |
| jmp.To.U.Branch = target |
| } |
| |
| // asmInstruction assembles an instruction. |
| // MOVW R9, (R10) |
| func (p *Parser) asmInstruction(op int, a []addr.Addr) { |
| prog := &obj.Prog{ |
| Ctxt: p.linkCtxt, |
| Lineno: int32(p.histLineNum), |
| As: int16(op), |
| } |
| switch len(a) { |
| case 0: |
| prog.From = p.arch.NoAddr |
| prog.To = p.arch.NoAddr |
| case 1: |
| if p.arch.UnaryDestination[op] { |
| prog.From = p.arch.NoAddr |
| prog.To = p.addrToAddr(&a[0]) |
| } else { |
| prog.From = p.addrToAddr(&a[0]) |
| prog.To = p.arch.NoAddr |
| } |
| case 2: |
| prog.From = p.addrToAddr(&a[0]) |
| prog.To = p.addrToAddr(&a[1]) |
| // DX:AX as a register pair can only appear on the RHS. |
| // Bizarrely, to obj it's specified by setting index on the LHS. |
| // TODO: can we fix this? |
| if a[1].Has(addr.Register2) { |
| if int(prog.From.Index) != p.arch.D_NONE { |
| p.errorf("register pair operand on RHS must have register on LHS") |
| } |
| prog.From.Index = uint8(a[1].Register2) |
| } |
| case 3: |
| // CMPSD etc.; third operand is imm8, stored in offset, or a register. |
| prog.From = p.addrToAddr(&a[0]) |
| prog.To = p.addrToAddr(&a[1]) |
| switch { |
| case a[2].Is(addr.Offset): |
| prog.To.Offset = a[2].Offset |
| case a[2].Is(addr.Register): |
| // Strange reodering. |
| prog.To = p.addrToAddr(&a[2]) |
| prog.From = p.addrToAddr(&a[1]) |
| if !a[0].IsImmediateConstant { |
| p.errorf("expected $value for 1st operand") |
| } |
| prog.To.Offset = a[0].Offset |
| default: |
| p.errorf("expected offset or register for 3rd operand") |
| } |
| default: |
| p.errorf("can't handle instruction with %d operands", len(a)) |
| } |
| p.append(prog, true) |
| } |