blob: e6ea8985e4f78c326a2df0c6b015db0d963ed083 [file] [log] [blame]
// Inferno utils/6l/pass.c
// https://bitbucket.org/inferno-os/inferno-os/src/master/utils/6l/pass.c
//
// Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
// Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net)
// Portions Copyright © 1997-1999 Vita Nuova Limited
// Portions Copyright © 2000-2007 Vita Nuova Holdings Limited (www.vitanuova.com)
// Portions Copyright © 2004,2006 Bruce Ellis
// Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net)
// Revisions Copyright © 2000-2007 Lucent Technologies Inc. and others
// Portions Copyright © 2009 The Go Authors. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package x86
import (
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
"cmd/internal/sys"
"internal/abi"
"log"
"math"
"path"
"strings"
)
func CanUse1InsnTLS(ctxt *obj.Link) bool {
if isAndroid {
// Android uses a global variable for the tls offset.
return false
}
if ctxt.Arch.Family == sys.I386 {
switch ctxt.Headtype {
case objabi.Hlinux,
objabi.Hplan9,
objabi.Hwindows:
return false
}
return true
}
switch ctxt.Headtype {
case objabi.Hplan9, objabi.Hwindows:
return false
case objabi.Hlinux, objabi.Hfreebsd:
return !ctxt.Flag_shared
}
return true
}
func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
// Thread-local storage references use the TLS pseudo-register.
// As a register, TLS refers to the thread-local storage base, and it
// can only be loaded into another register:
//
// MOVQ TLS, AX
//
// An offset from the thread-local storage base is written off(reg)(TLS*1).
// Semantically it is off(reg), but the (TLS*1) annotation marks this as
// indexing from the loaded TLS base. This emits a relocation so that
// if the linker needs to adjust the offset, it can. For example:
//
// MOVQ TLS, AX
// MOVQ 0(AX)(TLS*1), CX // load g into CX
//
// On systems that support direct access to the TLS memory, this
// pair of instructions can be reduced to a direct TLS memory reference:
//
// MOVQ 0(TLS), CX // load g into CX
//
// The 2-instruction and 1-instruction forms correspond to the two code
// sequences for loading a TLS variable in the local exec model given in "ELF
// Handling For Thread-Local Storage".
//
// We apply this rewrite on systems that support the 1-instruction form.
// The decision is made using only the operating system and the -shared flag,
// not the link mode. If some link modes on a particular operating system
// require the 2-instruction form, then all builds for that operating system
// will use the 2-instruction form, so that the link mode decision can be
// delayed to link time.
//
// In this way, all supported systems use identical instructions to
// access TLS, and they are rewritten appropriately first here in
// liblink and then finally using relocations in the linker.
//
// When -shared is passed, we leave the code in the 2-instruction form but
// assemble (and relocate) them in different ways to generate the initial
// exec code sequence. It's a bit of a fluke that this is possible without
// rewriting the instructions more comprehensively, and it only does because
// we only support a single TLS variable (g).
if CanUse1InsnTLS(ctxt) {
// Reduce 2-instruction sequence to 1-instruction sequence.
// Sequences like
// MOVQ TLS, BX
// ... off(BX)(TLS*1) ...
// become
// NOP
// ... off(TLS) ...
//
// TODO(rsc): Remove the Hsolaris special case. It exists only to
// guarantee we are producing byte-identical binaries as before this code.
// But it should be unnecessary.
if (p.As == AMOVQ || p.As == AMOVL) && p.From.Type == obj.TYPE_REG && p.From.Reg == REG_TLS && p.To.Type == obj.TYPE_REG && REG_AX <= p.To.Reg && p.To.Reg <= REG_R15 && ctxt.Headtype != objabi.Hsolaris {
obj.Nopout(p)
}
if p.From.Type == obj.TYPE_MEM && p.From.Index == REG_TLS && REG_AX <= p.From.Reg && p.From.Reg <= REG_R15 {
p.From.Reg = REG_TLS
p.From.Scale = 0
p.From.Index = REG_NONE
}
if p.To.Type == obj.TYPE_MEM && p.To.Index == REG_TLS && REG_AX <= p.To.Reg && p.To.Reg <= REG_R15 {
p.To.Reg = REG_TLS
p.To.Scale = 0
p.To.Index = REG_NONE
}
} else {
// load_g, below, always inserts the 1-instruction sequence. Rewrite it
// as the 2-instruction sequence if necessary.
// MOVQ 0(TLS), BX
// becomes
// MOVQ TLS, BX
// MOVQ 0(BX)(TLS*1), BX
if (p.As == AMOVQ || p.As == AMOVL) && p.From.Type == obj.TYPE_MEM && p.From.Reg == REG_TLS && p.To.Type == obj.TYPE_REG && REG_AX <= p.To.Reg && p.To.Reg <= REG_R15 {
q := obj.Appendp(p, newprog)
q.As = p.As
q.From = p.From
q.From.Type = obj.TYPE_MEM
q.From.Reg = p.To.Reg
q.From.Index = REG_TLS
q.From.Scale = 2 // TODO: use 1
q.To = p.To
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_TLS
p.From.Index = REG_NONE
p.From.Offset = 0
}
}
// Android and Windows use a tls offset determined at runtime. Rewrite
// MOVQ TLS, BX
// to
// MOVQ runtime.tls_g(SB), BX
if (isAndroid || ctxt.Headtype == objabi.Hwindows) &&
(p.As == AMOVQ || p.As == AMOVL) && p.From.Type == obj.TYPE_REG && p.From.Reg == REG_TLS && p.To.Type == obj.TYPE_REG && REG_AX <= p.To.Reg && p.To.Reg <= REG_R15 {
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_EXTERN
p.From.Reg = REG_NONE
p.From.Sym = ctxt.Lookup("runtime.tls_g")
p.From.Index = REG_NONE
if ctxt.Headtype == objabi.Hwindows {
// Windows requires an additional indirection
// to retrieve the TLS pointer,
// as runtime.tls_g contains the TLS offset from GS or FS.
// on AMD64 add
// MOVQ 0(BX)(GS*1), BX
// on 386 add
// MOVQ 0(BX)(FS*1), BX4
q := obj.Appendp(p, newprog)
q.As = p.As
q.From = obj.Addr{}
q.From.Type = obj.TYPE_MEM
q.From.Reg = p.To.Reg
if ctxt.Arch.Family == sys.AMD64 {
q.From.Index = REG_GS
} else {
q.From.Index = REG_FS
}
q.From.Scale = 1
q.From.Offset = 0
q.To = p.To
}
}
// TODO: Remove.
if ctxt.Headtype == objabi.Hwindows && ctxt.Arch.Family == sys.AMD64 || ctxt.Headtype == objabi.Hplan9 {
if p.From.Scale == 1 && p.From.Index == REG_TLS {
p.From.Scale = 2
}
if p.To.Scale == 1 && p.To.Index == REG_TLS {
p.To.Scale = 2
}
}
// Rewrite 0 to $0 in 3rd argument to CMPPS etc.
// That's what the tables expect.
switch p.As {
case ACMPPD, ACMPPS, ACMPSD, ACMPSS:
if p.To.Type == obj.TYPE_MEM && p.To.Name == obj.NAME_NONE && p.To.Reg == REG_NONE && p.To.Index == REG_NONE && p.To.Sym == nil {
p.To.Type = obj.TYPE_CONST
}
}
// Rewrite CALL/JMP/RET to symbol as TYPE_BRANCH.
switch p.As {
case obj.ACALL, obj.AJMP, obj.ARET:
if p.To.Type == obj.TYPE_MEM && (p.To.Name == obj.NAME_EXTERN || p.To.Name == obj.NAME_STATIC) && p.To.Sym != nil {
p.To.Type = obj.TYPE_BRANCH
}
}
// Rewrite MOVL/MOVQ $XXX(FP/SP) as LEAL/LEAQ.
if p.From.Type == obj.TYPE_ADDR && (ctxt.Arch.Family == sys.AMD64 || p.From.Name != obj.NAME_EXTERN && p.From.Name != obj.NAME_STATIC) {
switch p.As {
case AMOVL:
p.As = ALEAL
p.From.Type = obj.TYPE_MEM
case AMOVQ:
p.As = ALEAQ
p.From.Type = obj.TYPE_MEM
}
}
// Rewrite float constants to values stored in memory.
switch p.As {
// Convert AMOVSS $(0), Xx to AXORPS Xx, Xx
case AMOVSS:
if p.From.Type == obj.TYPE_FCONST {
// f == 0 can't be used here due to -0, so use Float64bits
if f := p.From.Val.(float64); math.Float64bits(f) == 0 {
if p.To.Type == obj.TYPE_REG && REG_X0 <= p.To.Reg && p.To.Reg <= REG_X15 {
p.As = AXORPS
p.From = p.To
break
}
}
}
fallthrough
case AFMOVF,
AFADDF,
AFSUBF,
AFSUBRF,
AFMULF,
AFDIVF,
AFDIVRF,
AFCOMF,
AFCOMFP,
AADDSS,
ASUBSS,
AMULSS,
ADIVSS,
ACOMISS,
AUCOMISS:
if p.From.Type == obj.TYPE_FCONST {
f32 := float32(p.From.Val.(float64))
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_EXTERN
p.From.Sym = ctxt.Float32Sym(f32)
p.From.Offset = 0
}
case AMOVSD:
// Convert AMOVSD $(0), Xx to AXORPS Xx, Xx
if p.From.Type == obj.TYPE_FCONST {
// f == 0 can't be used here due to -0, so use Float64bits
if f := p.From.Val.(float64); math.Float64bits(f) == 0 {
if p.To.Type == obj.TYPE_REG && REG_X0 <= p.To.Reg && p.To.Reg <= REG_X15 {
p.As = AXORPS
p.From = p.To
break
}
}
}
fallthrough
case AFMOVD,
AFADDD,
AFSUBD,
AFSUBRD,
AFMULD,
AFDIVD,
AFDIVRD,
AFCOMD,
AFCOMDP,
AADDSD,
ASUBSD,
AMULSD,
ADIVSD,
ACOMISD,
AUCOMISD:
if p.From.Type == obj.TYPE_FCONST {
f64 := p.From.Val.(float64)
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_EXTERN
p.From.Sym = ctxt.Float64Sym(f64)
p.From.Offset = 0
}
}
if ctxt.Flag_dynlink {
rewriteToUseGot(ctxt, p, newprog)
}
if ctxt.Flag_shared && ctxt.Arch.Family == sys.I386 {
rewriteToPcrel(ctxt, p, newprog)
}
}
// Rewrite p, if necessary, to access global data via the global offset table.
func rewriteToUseGot(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
var lea, mov obj.As
var reg int16
if ctxt.Arch.Family == sys.AMD64 {
lea = ALEAQ
mov = AMOVQ
reg = REG_R15
} else {
lea = ALEAL
mov = AMOVL
reg = REG_CX
if p.As == ALEAL && p.To.Reg != p.From.Reg && p.To.Reg != p.From.Index {
// Special case: clobber the destination register with
// the PC so we don't have to clobber CX.
// The SSA backend depends on CX not being clobbered across LEAL.
// See cmd/compile/internal/ssa/gen/386.rules (search for Flag_shared).
reg = p.To.Reg
}
}
if p.As == obj.ADUFFCOPY || p.As == obj.ADUFFZERO {
// ADUFFxxx $offset
// becomes
// $MOV runtime.duffxxx@GOT, $reg
// $LEA $offset($reg), $reg
// CALL $reg
// (we use LEAx rather than ADDx because ADDx clobbers
// flags and duffzero on 386 does not otherwise do so).
var sym *obj.LSym
if p.As == obj.ADUFFZERO {
sym = ctxt.LookupABI("runtime.duffzero", obj.ABIInternal)
} else {
sym = ctxt.LookupABI("runtime.duffcopy", obj.ABIInternal)
}
offset := p.To.Offset
p.As = mov
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_GOTREF
p.From.Sym = sym
p.To.Type = obj.TYPE_REG
p.To.Reg = reg
p.To.Offset = 0
p.To.Sym = nil
p1 := obj.Appendp(p, newprog)
p1.As = lea
p1.From.Type = obj.TYPE_MEM
p1.From.Offset = offset
p1.From.Reg = reg
p1.To.Type = obj.TYPE_REG
p1.To.Reg = reg
p2 := obj.Appendp(p1, newprog)
p2.As = obj.ACALL
p2.To.Type = obj.TYPE_REG
p2.To.Reg = reg
}
// We only care about global data: NAME_EXTERN means a global
// symbol in the Go sense, and p.Sym.Local is true for a few
// internally defined symbols.
if p.As == lea && p.From.Type == obj.TYPE_MEM && p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
// $LEA sym, Rx becomes $MOV $sym, Rx which will be rewritten below
p.As = mov
p.From.Type = obj.TYPE_ADDR
}
if p.From.Type == obj.TYPE_ADDR && p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
// $MOV $sym, Rx becomes $MOV sym@GOT, Rx
// $MOV $sym+<off>, Rx becomes $MOV sym@GOT, Rx; $LEA <off>(Rx), Rx
// On 386 only, more complicated things like PUSHL $sym become $MOV sym@GOT, CX; PUSHL CX
cmplxdest := false
pAs := p.As
var dest obj.Addr
if p.To.Type != obj.TYPE_REG || pAs != mov {
if ctxt.Arch.Family == sys.AMD64 {
ctxt.Diag("do not know how to handle LEA-type insn to non-register in %v with -dynlink", p)
}
cmplxdest = true
dest = p.To
p.As = mov
p.To.Type = obj.TYPE_REG
p.To.Reg = reg
p.To.Sym = nil
p.To.Name = obj.NAME_NONE
}
p.From.Type = obj.TYPE_MEM
p.From.Name = obj.NAME_GOTREF
q := p
if p.From.Offset != 0 {
q = obj.Appendp(p, newprog)
q.As = lea
q.From.Type = obj.TYPE_MEM
q.From.Reg = p.To.Reg
q.From.Offset = p.From.Offset
q.To = p.To
p.From.Offset = 0
}
if cmplxdest {
q = obj.Appendp(q, newprog)
q.As = pAs
q.To = dest
q.From.Type = obj.TYPE_REG
q.From.Reg = reg
}
}
if p.GetFrom3() != nil && p.GetFrom3().Name == obj.NAME_EXTERN {
ctxt.Diag("don't know how to handle %v with -dynlink", p)
}
var source *obj.Addr
// MOVx sym, Ry becomes $MOV sym@GOT, R15; MOVx (R15), Ry
// MOVx Ry, sym becomes $MOV sym@GOT, R15; MOVx Ry, (R15)
// An addition may be inserted between the two MOVs if there is an offset.
if p.From.Name == obj.NAME_EXTERN && !p.From.Sym.Local() {
if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
ctxt.Diag("cannot handle NAME_EXTERN on both sides in %v with -dynlink", p)
}
source = &p.From
} else if p.To.Name == obj.NAME_EXTERN && !p.To.Sym.Local() {
source = &p.To
} else {
return
}
if p.As == obj.ACALL {
// When dynlinking on 386, almost any call might end up being a call
// to a PLT, so make sure the GOT pointer is loaded into BX.
// RegTo2 is set on the replacement call insn to stop it being
// processed when it is in turn passed to progedit.
//
// We disable open-coded defers in buildssa() on 386 ONLY with shared
// libraries because of this extra code added before deferreturn calls.
if ctxt.Arch.Family == sys.AMD64 || (p.To.Sym != nil && p.To.Sym.Local()) || p.RegTo2 != 0 {
return
}
p1 := obj.Appendp(p, newprog)
p2 := obj.Appendp(p1, newprog)
p1.As = ALEAL
p1.From.Type = obj.TYPE_MEM
p1.From.Name = obj.NAME_STATIC
p1.From.Sym = ctxt.Lookup("_GLOBAL_OFFSET_TABLE_")
p1.To.Type = obj.TYPE_REG
p1.To.Reg = REG_BX
p2.As = p.As
p2.Scond = p.Scond
p2.From = p.From
if p.RestArgs != nil {
p2.RestArgs = append(p2.RestArgs, p.RestArgs...)
}
p2.Reg = p.Reg
p2.To = p.To
// p.To.Type was set to TYPE_BRANCH above, but that makes checkaddr
// in ../pass.go complain, so set it back to TYPE_MEM here, until p2
// itself gets passed to progedit.
p2.To.Type = obj.TYPE_MEM
p2.RegTo2 = 1
obj.Nopout(p)
return
}
if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ARET || p.As == obj.AJMP {
return
}
if source.Type != obj.TYPE_MEM {
ctxt.Diag("don't know how to handle %v with -dynlink", p)
}
p1 := obj.Appendp(p, newprog)
p2 := obj.Appendp(p1, newprog)
p1.As = mov
p1.From.Type = obj.TYPE_MEM
p1.From.Sym = source.Sym
p1.From.Name = obj.NAME_GOTREF
p1.To.Type = obj.TYPE_REG
p1.To.Reg = reg
p2.As = p.As
p2.From = p.From
p2.To = p.To
if from3 := p.GetFrom3(); from3 != nil {
p2.AddRestSource(*from3)
}
if p.From.Name == obj.NAME_EXTERN {
p2.From.Reg = reg
p2.From.Name = obj.NAME_NONE
p2.From.Sym = nil
} else if p.To.Name == obj.NAME_EXTERN {
p2.To.Reg = reg
p2.To.Name = obj.NAME_NONE
p2.To.Sym = nil
} else {
return
}
obj.Nopout(p)
}
func rewriteToPcrel(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
// RegTo2 is set on the instructions we insert here so they don't get
// processed twice.
if p.RegTo2 != 0 {
return
}
if p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ACALL || p.As == obj.ARET || p.As == obj.AJMP {
return
}
// Any Prog (aside from the above special cases) with an Addr with Name ==
// NAME_EXTERN, NAME_STATIC or NAME_GOTREF has a CALL __x86.get_pc_thunk.XX
// inserted before it.
isName := func(a *obj.Addr) bool {
if a.Sym == nil || (a.Type != obj.TYPE_MEM && a.Type != obj.TYPE_ADDR) || a.Reg != 0 {
return false
}
if a.Sym.Type == objabi.STLSBSS {
return false
}
return a.Name == obj.NAME_EXTERN || a.Name == obj.NAME_STATIC || a.Name == obj.NAME_GOTREF
}
if isName(&p.From) && p.From.Type == obj.TYPE_ADDR {
// Handle things like "MOVL $sym, (SP)" or "PUSHL $sym" by rewriting
// to "MOVL $sym, CX; MOVL CX, (SP)" or "MOVL $sym, CX; PUSHL CX"
// respectively.
if p.To.Type != obj.TYPE_REG {
q := obj.Appendp(p, newprog)
q.As = p.As
q.From.Type = obj.TYPE_REG
q.From.Reg = REG_CX
q.To = p.To
p.As = AMOVL
p.To.Type = obj.TYPE_REG
p.To.Reg = REG_CX
p.To.Sym = nil
p.To.Name = obj.NAME_NONE
}
}
if !isName(&p.From) && !isName(&p.To) && (p.GetFrom3() == nil || !isName(p.GetFrom3())) {
return
}
var dst int16 = REG_CX
if (p.As == ALEAL || p.As == AMOVL) && p.To.Reg != p.From.Reg && p.To.Reg != p.From.Index {
dst = p.To.Reg
// Why? See the comment near the top of rewriteToUseGot above.
// AMOVLs might be introduced by the GOT rewrites.
}
q := obj.Appendp(p, newprog)
q.RegTo2 = 1
r := obj.Appendp(q, newprog)
r.RegTo2 = 1
q.As = obj.ACALL
thunkname := "__x86.get_pc_thunk." + strings.ToLower(rconv(int(dst)))
q.To.Sym = ctxt.LookupInit(thunkname, func(s *obj.LSym) { s.Set(obj.AttrLocal, true) })
q.To.Type = obj.TYPE_MEM
q.To.Name = obj.NAME_EXTERN
r.As = p.As
r.Scond = p.Scond
r.From = p.From
r.RestArgs = p.RestArgs
r.Reg = p.Reg
r.To = p.To
if isName(&p.From) {
r.From.Reg = dst
}
if isName(&p.To) {
r.To.Reg = dst
}
if p.GetFrom3() != nil && isName(p.GetFrom3()) {
r.GetFrom3().Reg = dst
}
obj.Nopout(p)
}
// Prog.mark
const (
markBit = 1 << 0 // used in errorCheck to avoid duplicate work
)
func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
if cursym.Func().Text == nil || cursym.Func().Text.Link == nil {
return
}
p := cursym.Func().Text
autoffset := int32(p.To.Offset)
if autoffset < 0 {
autoffset = 0
}
hasCall := false
for q := p; q != nil; q = q.Link {
if q.As == obj.ACALL || q.As == obj.ADUFFCOPY || q.As == obj.ADUFFZERO {
hasCall = true
break
}
}
var bpsize int
if ctxt.Arch.Family == sys.AMD64 &&
!p.From.Sym.NoFrame() && // (1) below
!(autoffset == 0 && !hasCall) { // (2) below
// Make room to save a base pointer.
// There are 2 cases we must avoid:
// 1) If noframe is set (which we do for functions which tail call).
// For performance, we also want to avoid:
// 2) Frameless leaf functions
bpsize = ctxt.Arch.PtrSize
autoffset += int32(bpsize)
p.To.Offset += int64(bpsize)
} else {
bpsize = 0
p.From.Sym.Set(obj.AttrNoFrame, true)
}
textarg := int64(p.To.Val.(int32))
cursym.Func().Args = int32(textarg)
cursym.Func().Locals = int32(p.To.Offset)
// TODO(rsc): Remove.
if ctxt.Arch.Family == sys.I386 && cursym.Func().Locals < 0 {
cursym.Func().Locals = 0
}
// TODO(rsc): Remove 'ctxt.Arch.Family == sys.AMD64 &&'.
if ctxt.Arch.Family == sys.AMD64 && autoffset < abi.StackSmall && !p.From.Sym.NoSplit() {
leaf := true
LeafSearch:
for q := p; q != nil; q = q.Link {
switch q.As {
case obj.ACALL:
// Treat common runtime calls that take no arguments
// the same as duffcopy and duffzero.
if !isZeroArgRuntimeCall(q.To.Sym) {
leaf = false
break LeafSearch
}
fallthrough
case obj.ADUFFCOPY, obj.ADUFFZERO:
if autoffset >= abi.StackSmall-8 {
leaf = false
break LeafSearch
}
}
}
if leaf {
p.From.Sym.Set(obj.AttrNoSplit, true)
}
}
var regEntryTmp0, regEntryTmp1 int16
if ctxt.Arch.Family == sys.AMD64 {
regEntryTmp0, regEntryTmp1 = REGENTRYTMP0, REGENTRYTMP1
} else {
regEntryTmp0, regEntryTmp1 = REG_BX, REG_DI
}
var regg int16
if !p.From.Sym.NoSplit() {
// Emit split check and load G register
p, regg = stacksplit(ctxt, cursym, p, newprog, autoffset, int32(textarg))
} else if p.From.Sym.Wrapper() {
// Load G register for the wrapper code
p, regg = loadG(ctxt, cursym, p, newprog)
}
if bpsize > 0 {
// Save caller's BP
p = obj.Appendp(p, newprog)
p.As = APUSHQ
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_BP
// Move current frame to BP
p = obj.Appendp(p, newprog)
p.As = AMOVQ
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_SP
p.To.Type = obj.TYPE_REG
p.To.Reg = REG_BP
}
if autoffset%int32(ctxt.Arch.RegSize) != 0 {
ctxt.Diag("unaligned stack size %d", autoffset)
}
// localoffset is autoffset discounting the frame pointer,
// which has already been allocated in the stack.
localoffset := autoffset - int32(bpsize)
if localoffset != 0 {
p = obj.Appendp(p, newprog)
p.As = AADJSP
p.From.Type = obj.TYPE_CONST
p.From.Offset = int64(localoffset)
p.Spadj = localoffset
}
// Delve debugger would like the next instruction to be noted as the end of the function prologue.
// TODO: are there other cases (e.g., wrapper functions) that need marking?
if autoffset != 0 {
p.Pos = p.Pos.WithXlogue(src.PosPrologueEnd)
}
if cursym.Func().Text.From.Sym.Wrapper() {
// if g._panic != nil && g._panic.argp == FP {
// g._panic.argp = bottom-of-frame
// }
//
// MOVQ g_panic(g), regEntryTmp0
// TESTQ regEntryTmp0, regEntryTmp0
// JNE checkargp
// end:
// NOP
// ... rest of function ...
// checkargp:
// LEAQ (autoffset+8)(SP), regEntryTmp1
// CMPQ panic_argp(regEntryTmp0), regEntryTmp1
// JNE end
// MOVQ SP, panic_argp(regEntryTmp0)
// JMP end
//
// The NOP is needed to give the jumps somewhere to land.
// It is a liblink NOP, not an x86 NOP: it encodes to 0 instruction bytes.
//
// The layout is chosen to help static branch prediction:
// Both conditional jumps are unlikely, so they are arranged to be forward jumps.
// MOVQ g_panic(g), regEntryTmp0
p = obj.Appendp(p, newprog)
p.As = AMOVQ
p.From.Type = obj.TYPE_MEM
p.From.Reg = regg
p.From.Offset = 4 * int64(ctxt.Arch.PtrSize) // g_panic
p.To.Type = obj.TYPE_REG
p.To.Reg = regEntryTmp0
if ctxt.Arch.Family == sys.I386 {
p.As = AMOVL
}
// TESTQ regEntryTmp0, regEntryTmp0
p = obj.Appendp(p, newprog)
p.As = ATESTQ
p.From.Type = obj.TYPE_REG
p.From.Reg = regEntryTmp0
p.To.Type = obj.TYPE_REG
p.To.Reg = regEntryTmp0
if ctxt.Arch.Family == sys.I386 {
p.As = ATESTL
}
// JNE checkargp (checkargp to be resolved later)
jne := obj.Appendp(p, newprog)
jne.As = AJNE
jne.To.Type = obj.TYPE_BRANCH
// end:
// NOP
end := obj.Appendp(jne, newprog)
end.As = obj.ANOP
// Fast forward to end of function.
var last *obj.Prog
for last = end; last.Link != nil; last = last.Link {
}
// LEAQ (autoffset+8)(SP), regEntryTmp1
p = obj.Appendp(last, newprog)
p.As = ALEAQ
p.From.Type = obj.TYPE_MEM
p.From.Reg = REG_SP
p.From.Offset = int64(autoffset) + int64(ctxt.Arch.RegSize)
p.To.Type = obj.TYPE_REG
p.To.Reg = regEntryTmp1
if ctxt.Arch.Family == sys.I386 {
p.As = ALEAL
}
// Set jne branch target.
jne.To.SetTarget(p)
// CMPQ panic_argp(regEntryTmp0), regEntryTmp1
p = obj.Appendp(p, newprog)
p.As = ACMPQ
p.From.Type = obj.TYPE_MEM
p.From.Reg = regEntryTmp0
p.From.Offset = 0 // Panic.argp
p.To.Type = obj.TYPE_REG
p.To.Reg = regEntryTmp1
if ctxt.Arch.Family == sys.I386 {
p.As = ACMPL
}
// JNE end
p = obj.Appendp(p, newprog)
p.As = AJNE
p.To.Type = obj.TYPE_BRANCH
p.To.SetTarget(end)
// MOVQ SP, panic_argp(regEntryTmp0)
p = obj.Appendp(p, newprog)
p.As = AMOVQ
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_SP
p.To.Type = obj.TYPE_MEM
p.To.Reg = regEntryTmp0
p.To.Offset = 0 // Panic.argp
if ctxt.Arch.Family == sys.I386 {
p.As = AMOVL
}
// JMP end
p = obj.Appendp(p, newprog)
p.As = obj.AJMP
p.To.Type = obj.TYPE_BRANCH
p.To.SetTarget(end)
// Reset p for following code.
p = end
}
var deltasp int32
for p = cursym.Func().Text; p != nil; p = p.Link {
pcsize := ctxt.Arch.RegSize
switch p.From.Name {
case obj.NAME_AUTO:
p.From.Offset += int64(deltasp) - int64(bpsize)
case obj.NAME_PARAM:
p.From.Offset += int64(deltasp) + int64(pcsize)
}
if p.GetFrom3() != nil {
switch p.GetFrom3().Name {
case obj.NAME_AUTO:
p.GetFrom3().Offset += int64(deltasp) - int64(bpsize)
case obj.NAME_PARAM:
p.GetFrom3().Offset += int64(deltasp) + int64(pcsize)
}
}
switch p.To.Name {
case obj.NAME_AUTO:
p.To.Offset += int64(deltasp) - int64(bpsize)
case obj.NAME_PARAM:
p.To.Offset += int64(deltasp) + int64(pcsize)
}
switch p.As {
default:
if p.To.Type == obj.TYPE_REG && p.To.Reg == REG_SP && p.As != ACMPL && p.As != ACMPQ {
f := cursym.Func()
if f.FuncFlag&abi.FuncFlagSPWrite == 0 {
f.FuncFlag |= abi.FuncFlagSPWrite
if ctxt.Debugvlog || !ctxt.IsAsm {
ctxt.Logf("auto-SPWRITE: %s %v\n", cursym.Name, p)
if !ctxt.IsAsm {
ctxt.Diag("invalid auto-SPWRITE in non-assembly")
ctxt.DiagFlush()
log.Fatalf("bad SPWRITE")
}
}
}
}
continue
case APUSHL, APUSHFL:
deltasp += 4
p.Spadj = 4
continue
case APUSHQ, APUSHFQ:
deltasp += 8
p.Spadj = 8
continue
case APUSHW, APUSHFW:
deltasp += 2
p.Spadj = 2
continue
case APOPL, APOPFL:
deltasp -= 4
p.Spadj = -4
continue
case APOPQ, APOPFQ:
deltasp -= 8
p.Spadj = -8
continue
case APOPW, APOPFW:
deltasp -= 2
p.Spadj = -2
continue
case AADJSP:
p.Spadj = int32(p.From.Offset)
deltasp += int32(p.From.Offset)
continue
case obj.ARET:
// do nothing
}
if autoffset != deltasp {
ctxt.Diag("%s: unbalanced PUSH/POP", cursym)
}
if autoffset != 0 {
to := p.To // Keep To attached to RET for retjmp below
p.To = obj.Addr{}
if localoffset != 0 {
p.As = AADJSP
p.From.Type = obj.TYPE_CONST
p.From.Offset = int64(-localoffset)
p.Spadj = -localoffset
p = obj.Appendp(p, newprog)
}
if bpsize > 0 {
// Restore caller's BP
p.As = APOPQ
p.To.Type = obj.TYPE_REG
p.To.Reg = REG_BP
p.Spadj = -int32(bpsize)
p = obj.Appendp(p, newprog)
}
p.As = obj.ARET
p.To = to
// If there are instructions following
// this ARET, they come from a branch
// with the same stackframe, so undo
// the cleanup.
p.Spadj = +autoffset
}
if p.To.Sym != nil { // retjmp
p.As = obj.AJMP
}
}
}
func isZeroArgRuntimeCall(s *obj.LSym) bool {
if s == nil {
return false
}
switch s.Name {
case "runtime.panicdivide", "runtime.panicwrap", "runtime.panicshift":
return true
}
if strings.HasPrefix(s.Name, "runtime.panicIndex") || strings.HasPrefix(s.Name, "runtime.panicSlice") {
// These functions do take arguments (in registers),
// but use no stack before they do a stack check. We
// should include them. See issue 31219.
return true
}
return false
}
func indir_cx(ctxt *obj.Link, a *obj.Addr) {
a.Type = obj.TYPE_MEM
a.Reg = REG_CX
}
// loadG ensures the G is loaded into a register (either CX or REGG),
// appending instructions to p if necessary. It returns the new last
// instruction and the G register.
func loadG(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc) (*obj.Prog, int16) {
if ctxt.Arch.Family == sys.AMD64 && cursym.ABI() == obj.ABIInternal {
// Use the G register directly in ABIInternal
return p, REGG
}
var regg int16 = REG_CX
if ctxt.Arch.Family == sys.AMD64 {
regg = REGG // == REG_R14
}
p = obj.Appendp(p, newprog)
p.As = AMOVQ
if ctxt.Arch.PtrSize == 4 {
p.As = AMOVL
}
p.From.Type = obj.TYPE_MEM
p.From.Reg = REG_TLS
p.From.Offset = 0
p.To.Type = obj.TYPE_REG
p.To.Reg = regg
// Rewrite TLS instruction if necessary.
next := p.Link
progedit(ctxt, p, newprog)
for p.Link != next {
p = p.Link
progedit(ctxt, p, newprog)
}
if p.From.Index == REG_TLS {
p.From.Scale = 2
}
return p, regg
}
// Append code to p to check for stack split.
// Appends to (does not overwrite) p.
// Assumes g is in rg.
// Returns last new instruction and G register.
func stacksplit(ctxt *obj.Link, cursym *obj.LSym, p *obj.Prog, newprog obj.ProgAlloc, framesize int32, textarg int32) (*obj.Prog, int16) {
cmp := ACMPQ
lea := ALEAQ
mov := AMOVQ
sub := ASUBQ
push, pop := APUSHQ, APOPQ
if ctxt.Arch.Family == sys.I386 {
cmp = ACMPL
lea = ALEAL
mov = AMOVL
sub = ASUBL
push, pop = APUSHL, APOPL
}
tmp := int16(REG_AX) // use AX for 32-bit
if ctxt.Arch.Family == sys.AMD64 {
// Avoid register parameters.
tmp = int16(REGENTRYTMP0)
}
if ctxt.Flag_maymorestack != "" {
p = cursym.Func().SpillRegisterArgs(p, newprog)
if cursym.Func().Text.From.Sym.NeedCtxt() {
p = obj.Appendp(p, newprog)
p.As = push
p.From.Type = obj.TYPE_REG
p.From.Reg = REGCTXT
}
// We call maymorestack with an ABI matching the
// caller's ABI. Since this is the first thing that
// happens in the function, we have to be consistent
// with the caller about CPU state (notably,
// fixed-meaning registers).
p = obj.Appendp(p, newprog)
p.As = obj.ACALL
p.To.Type = obj.TYPE_BRANCH
p.To.Name = obj.NAME_EXTERN
p.To.Sym = ctxt.LookupABI(ctxt.Flag_maymorestack, cursym.ABI())
if cursym.Func().Text.From.Sym.NeedCtxt() {
p = obj.Appendp(p, newprog)
p.As = pop
p.To.Type = obj.TYPE_REG
p.To.Reg = REGCTXT
}
p = cursym.Func().UnspillRegisterArgs(p, newprog)
}
// Jump back to here after morestack returns.
startPred := p
// Load G register
var rg int16
p, rg = loadG(ctxt, cursym, p, newprog)
var q1 *obj.Prog
if framesize <= abi.StackSmall {
// small stack: SP <= stackguard
// CMPQ SP, stackguard
p = obj.Appendp(p, newprog)
p.As = cmp
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_SP
p.To.Type = obj.TYPE_MEM
p.To.Reg = rg
p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0
if cursym.CFunc() {
p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1
}
// Mark the stack bound check and morestack call async nonpreemptible.
// If we get preempted here, when resumed the preemption request is
// cleared, but we'll still call morestack, which will double the stack
// unnecessarily. See issue #35470.
p = ctxt.StartUnsafePoint(p, newprog)
} else if framesize <= abi.StackBig {
// large stack: SP-framesize <= stackguard-StackSmall
// LEAQ -xxx(SP), tmp
// CMPQ tmp, stackguard
p = obj.Appendp(p, newprog)
p.As = lea
p.From.Type = obj.TYPE_MEM
p.From.Reg = REG_SP
p.From.Offset = -(int64(framesize) - abi.StackSmall)
p.To.Type = obj.TYPE_REG
p.To.Reg = tmp
p = obj.Appendp(p, newprog)
p.As = cmp
p.From.Type = obj.TYPE_REG
p.From.Reg = tmp
p.To.Type = obj.TYPE_MEM
p.To.Reg = rg
p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0
if cursym.CFunc() {
p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1
}
p = ctxt.StartUnsafePoint(p, newprog) // see the comment above
} else {
// Such a large stack we need to protect against underflow.
// The runtime guarantees SP > objabi.StackBig, but
// framesize is large enough that SP-framesize may
// underflow, causing a direct comparison with the
// stack guard to incorrectly succeed. We explicitly
// guard against underflow.
//
// MOVQ SP, tmp
// SUBQ $(framesize - StackSmall), tmp
// // If subtraction wrapped (carry set), morestack.
// JCS label-of-call-to-morestack
// CMPQ tmp, stackguard
p = obj.Appendp(p, newprog)
p.As = mov
p.From.Type = obj.TYPE_REG
p.From.Reg = REG_SP
p.To.Type = obj.TYPE_REG
p.To.Reg = tmp
p = ctxt.StartUnsafePoint(p, newprog) // see the comment above
p = obj.Appendp(p, newprog)
p.As = sub
p.From.Type = obj.TYPE_CONST
p.From.Offset = int64(framesize) - abi.StackSmall
p.To.Type = obj.TYPE_REG
p.To.Reg = tmp
p = obj.Appendp(p, newprog)
p.As = AJCS
p.To.Type = obj.TYPE_BRANCH
q1 = p
p = obj.Appendp(p, newprog)
p.As = cmp
p.From.Type = obj.TYPE_REG
p.From.Reg = tmp
p.To.Type = obj.TYPE_MEM
p.To.Reg = rg
p.To.Offset = 2 * int64(ctxt.Arch.PtrSize) // G.stackguard0
if cursym.CFunc() {
p.To.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1
}
}
// common
jls := obj.Appendp(p, newprog)
jls.As = AJLS
jls.To.Type = obj.TYPE_BRANCH
end := ctxt.EndUnsafePoint(jls, newprog, -1)
var last *obj.Prog
for last = cursym.Func().Text; last.Link != nil; last = last.Link {
}
// Now we are at the end of the function, but logically
// we are still in function prologue. We need to fix the
// SP data and PCDATA.
spfix := obj.Appendp(last, newprog)
spfix.As = obj.ANOP
spfix.Spadj = -framesize
pcdata := ctxt.EmitEntryStackMap(cursym, spfix, newprog)
spill := ctxt.StartUnsafePoint(pcdata, newprog)
pcdata = cursym.Func().SpillRegisterArgs(spill, newprog)
call := obj.Appendp(pcdata, newprog)
call.Pos = cursym.Func().Text.Pos
call.As = obj.ACALL
call.To.Type = obj.TYPE_BRANCH
call.To.Name = obj.NAME_EXTERN
morestack := "runtime.morestack"
switch {
case cursym.CFunc():
morestack = "runtime.morestackc"
case !cursym.Func().Text.From.Sym.NeedCtxt():
morestack = "runtime.morestack_noctxt"
}
call.To.Sym = ctxt.Lookup(morestack)
// When compiling 386 code for dynamic linking, the call needs to be adjusted
// to follow PIC rules. This in turn can insert more instructions, so we need
// to keep track of the start of the call (where the jump will be to) and the
// end (which following instructions are appended to).
callend := call
progedit(ctxt, callend, newprog)
for ; callend.Link != nil; callend = callend.Link {
progedit(ctxt, callend.Link, newprog)
}
// The instructions which unspill regs should be preemptible.
pcdata = ctxt.EndUnsafePoint(callend, newprog, -1)
unspill := cursym.Func().UnspillRegisterArgs(pcdata, newprog)
jmp := obj.Appendp(unspill, newprog)
jmp.As = obj.AJMP
jmp.To.Type = obj.TYPE_BRANCH
jmp.To.SetTarget(startPred.Link)
jmp.Spadj = +framesize
jls.To.SetTarget(spill)
if q1 != nil {
q1.To.SetTarget(spill)
}
return end, rg
}
func isR15(r int16) bool {
return r == REG_R15 || r == REG_R15B
}
func addrMentionsR15(a *obj.Addr) bool {
if a == nil {
return false
}
return isR15(a.Reg) || isR15(a.Index)
}
func progMentionsR15(p *obj.Prog) bool {
return addrMentionsR15(&p.From) || addrMentionsR15(&p.To) || isR15(p.Reg) || addrMentionsR15(p.GetFrom3())
}
func addrUsesGlobal(a *obj.Addr) bool {
if a == nil {
return false
}
return a.Name == obj.NAME_EXTERN && !a.Sym.Local()
}
func progUsesGlobal(p *obj.Prog) bool {
if p.As == obj.ACALL || p.As == obj.ATEXT || p.As == obj.AFUNCDATA || p.As == obj.ARET || p.As == obj.AJMP {
// These opcodes don't use a GOT to access their argument (see rewriteToUseGot),
// or R15 would be dead at them anyway.
return false
}
if p.As == ALEAQ {
// The GOT entry is placed directly in the destination register; R15 is not used.
return false
}
return addrUsesGlobal(&p.From) || addrUsesGlobal(&p.To) || addrUsesGlobal(p.GetFrom3())
}
type rwMask int
const (
readFrom rwMask = 1 << iota
readTo
readReg
readFrom3
writeFrom
writeTo
writeReg
writeFrom3
)
// progRW returns a mask describing the effects of the instruction p.
// Note: this isn't exhaustively accurate. It is only currently used for detecting
// reads/writes to R15, so SSE register behavior isn't fully correct, and
// other weird cases (e.g. writes to DX by CLD) also aren't captured.
func progRW(p *obj.Prog) rwMask {
var m rwMask
// Default for most instructions
if p.From.Type != obj.TYPE_NONE {
m |= readFrom
}
if p.To.Type != obj.TYPE_NONE {
// Most x86 instructions update the To value
m |= readTo | writeTo
}
if p.Reg != 0 {
m |= readReg
}
if p.GetFrom3() != nil {
m |= readFrom3
}
// Lots of exceptions to the above defaults.
name := p.As.String()
if strings.HasPrefix(name, "MOV") || strings.HasPrefix(name, "PMOV") {
// MOV instructions don't read To.
m &^= readTo
}
switch p.As {
case APOPW, APOPL, APOPQ,
ALEAL, ALEAQ,
AIMUL3W, AIMUL3L, AIMUL3Q,
APEXTRB, APEXTRW, APEXTRD, APEXTRQ, AVPEXTRB, AVPEXTRW, AVPEXTRD, AVPEXTRQ, AEXTRACTPS,
ABSFW, ABSFL, ABSFQ, ABSRW, ABSRL, ABSRQ, APOPCNTW, APOPCNTL, APOPCNTQ, ALZCNTW, ALZCNTL, ALZCNTQ,
ASHLXL, ASHLXQ, ASHRXL, ASHRXQ, ASARXL, ASARXQ:
// These instructions are pure writes to To. They don't use its old value.
m &^= readTo
case AXORL, AXORQ:
// Register-clearing idiom doesn't read previous value.
if p.From.Type == obj.TYPE_REG && p.To.Type == obj.TYPE_REG && p.From.Reg == p.To.Reg {
m &^= readFrom | readTo
}
case AMULXL, AMULXQ:
// These are write-only to both To and From3.
m &^= readTo | readFrom3
m |= writeFrom3
}
return m
}
// progReadsR15 reports whether p reads the register R15.
func progReadsR15(p *obj.Prog) bool {
m := progRW(p)
if m&readFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) {
return true
}
if m&readTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) {
return true
}
if m&readReg != 0 && isR15(p.Reg) {
return true
}
if m&readFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) {
return true
}
// reads of the index registers
if p.From.Type == obj.TYPE_MEM && (isR15(p.From.Reg) || isR15(p.From.Index)) {
return true
}
if p.To.Type == obj.TYPE_MEM && (isR15(p.To.Reg) || isR15(p.To.Index)) {
return true
}
if f3 := p.GetFrom3(); f3 != nil && f3.Type == obj.TYPE_MEM && (isR15(f3.Reg) || isR15(f3.Index)) {
return true
}
return false
}
// progWritesR15 reports whether p writes the register R15.
func progWritesR15(p *obj.Prog) bool {
m := progRW(p)
if m&writeFrom != 0 && p.From.Type == obj.TYPE_REG && isR15(p.From.Reg) {
return true
}
if m&writeTo != 0 && p.To.Type == obj.TYPE_REG && isR15(p.To.Reg) {
return true
}
if m&writeReg != 0 && isR15(p.Reg) {
return true
}
if m&writeFrom3 != 0 && p.GetFrom3().Type == obj.TYPE_REG && isR15(p.GetFrom3().Reg) {
return true
}
return false
}
func errorCheck(ctxt *obj.Link, s *obj.LSym) {
// When dynamic linking, R15 is used to access globals. Reject code that
// uses R15 after a global variable access.
if !ctxt.Flag_dynlink {
return
}
// Flood fill all the instructions where R15's value is junk.
// If there are any uses of R15 in that set, report an error.
var work []*obj.Prog
var mentionsR15 bool
for p := s.Func().Text; p != nil; p = p.Link {
if progUsesGlobal(p) {
work = append(work, p)
p.Mark |= markBit
}
if progMentionsR15(p) {
mentionsR15 = true
}
}
if mentionsR15 {
for len(work) > 0 {
p := work[len(work)-1]
work = work[:len(work)-1]
if progReadsR15(p) {
pos := ctxt.PosTable.Pos(p.Pos)
ctxt.Diag("%s:%s: when dynamic linking, R15 is clobbered by a global variable access and is used here: %v", path.Base(pos.Filename()), pos.LineNumber(), p)
break // only report one error
}
if progWritesR15(p) {
// R15 is overwritten by this instruction. Its value is not junk any more.
continue
}
if q := p.To.Target(); q != nil && q.Mark&markBit == 0 {
q.Mark |= markBit
work = append(work, q)
}
if p.As == obj.AJMP || p.As == obj.ARET {
continue // no fallthrough
}
if q := p.Link; q != nil && q.Mark&markBit == 0 {
q.Mark |= markBit
work = append(work, q)
}
}
}
// Clean up.
for p := s.Func().Text; p != nil; p = p.Link {
p.Mark &^= markBit
}
}
var unaryDst = map[obj.As]bool{
ABSWAPL: true,
ABSWAPQ: true,
ACLDEMOTE: true,
ACLFLUSH: true,
ACLFLUSHOPT: true,
ACLWB: true,
ACMPXCHG16B: true,
ACMPXCHG8B: true,
ADECB: true,
ADECL: true,
ADECQ: true,
ADECW: true,
AFBSTP: true,
AFFREE: true,
AFLDENV: true,
AFSAVE: true,
AFSTCW: true,
AFSTENV: true,
AFSTSW: true,
AFXSAVE64: true,
AFXSAVE: true,
AINCB: true,
AINCL: true,
AINCQ: true,
AINCW: true,
ANEGB: true,
ANEGL: true,
ANEGQ: true,
ANEGW: true,
ANOTB: true,
ANOTL: true,
ANOTQ: true,
ANOTW: true,
APOPL: true,
APOPQ: true,
APOPW: true,
ARDFSBASEL: true,
ARDFSBASEQ: true,
ARDGSBASEL: true,
ARDGSBASEQ: true,
ARDPID: true,
ARDRANDL: true,
ARDRANDQ: true,
ARDRANDW: true,
ARDSEEDL: true,
ARDSEEDQ: true,
ARDSEEDW: true,
ASETCC: true,
ASETCS: true,
ASETEQ: true,
ASETGE: true,
ASETGT: true,
ASETHI: true,
ASETLE: true,
ASETLS: true,
ASETLT: true,
ASETMI: true,
ASETNE: true,
ASETOC: true,
ASETOS: true,
ASETPC: true,
ASETPL: true,
ASETPS: true,
ASGDT: true,
ASIDT: true,
ASLDTL: true,
ASLDTQ: true,
ASLDTW: true,
ASMSWL: true,
ASMSWQ: true,
ASMSWW: true,
ASTMXCSR: true,
ASTRL: true,
ASTRQ: true,
ASTRW: true,
AXSAVE64: true,
AXSAVE: true,
AXSAVEC64: true,
AXSAVEC: true,
AXSAVEOPT64: true,
AXSAVEOPT: true,
AXSAVES64: true,
AXSAVES: true,
}
var Linkamd64 = obj.LinkArch{
Arch: sys.ArchAMD64,
Init: instinit,
ErrorCheck: errorCheck,
Preprocess: preprocess,
Assemble: span6,
Progedit: progedit,
SEH: populateSeh,
UnaryDst: unaryDst,
DWARFRegisters: AMD64DWARFRegisters,
}
var Link386 = obj.LinkArch{
Arch: sys.Arch386,
Init: instinit,
Preprocess: preprocess,
Assemble: span6,
Progedit: progedit,
UnaryDst: unaryDst,
DWARFRegisters: X86DWARFRegisters,
}