mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
We currently make some parts of the preamble unpreemptible because it confuses morestack. See comments in the code. Instead, have morestack handle those weird cases so we can remove unpreemptible marks from most places. This CL makes user functions preemptible everywhere if they have no write barriers (at least, on x86). In cmd/go the fraction of functions that need preemptible markings drops from 82% to 36%. Makes the cmd/go binary 0.3% smaller. Update #35470 Change-Id: Ic83d5eabfd0f6d239a92e65684bcce7e67ff30bb Reviewed-on: https://go-review.googlesource.com/c/go/+/648518 Auto-Submit: Keith Randall <khr@google.com> Reviewed-by: Keith Randall <khr@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
1537 lines
40 KiB
Go
1537 lines
40 KiB
Go
// 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.
|
|
//
|
|
// computeDeferReturn in cmd/link/internal/ld/pcln.go depends
|
|
// on the size of these instructions.
|
|
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
|
|
}
|
|
|
|
} 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
|
|
}
|
|
|
|
} 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 = 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
|
|
|
|
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
|
|
|
|
spill := ctxt.EmitEntryStackMap(cursym, spfix, 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)
|
|
}
|
|
|
|
unspill := cursym.Func().UnspillRegisterArgs(callend, 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 jls, 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,
|
|
}
|