cmd/compile: change StaticCall to return a "Results"

StaticLECall (multiple value in +mem, multiple value result +mem) ->
StaticCall (multiple ergister value in +mem,
   multiple register-sized-value result +mem) ->
ARCH CallStatic (multiple ergister value in +mem,
   multiple register-sized-value result +mem)

But the architecture-dependent stuff is indifferent to whether
it is mem->mem or (mem)->(mem) until Prog generation.

Deal with OpSelectN -> Prog in ssagen/ssa.go, others, as they
appear.

For #40724.

Change-Id: I1d0436f6371054f1881862641d8e7e418e4a6a16
Reviewed-on: https://go-review.googlesource.com/c/go/+/293391
Trust: David Chase <drchase@google.com>
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
David Chase 2021-02-04 16:42:35 -05:00
parent 9a555fc24c
commit e25040d162
18 changed files with 387 additions and 382 deletions

View file

@ -27,6 +27,8 @@ type ABIParamResultInfo struct {
outparams []ABIParamAssignment
offsetToSpillArea int64
spillAreaSize int64
inRegistersUsed int
outRegistersUsed int
config *ABIConfig // to enable String() method
}
@ -42,6 +44,14 @@ func (a *ABIParamResultInfo) OutParams() []ABIParamAssignment {
return a.outparams
}
func (a *ABIParamResultInfo) InRegistersUsed() int {
return a.inRegistersUsed
}
func (a *ABIParamResultInfo) OutRegistersUsed() int {
return a.outRegistersUsed
}
func (a *ABIParamResultInfo) InParam(i int) ABIParamAssignment {
return a.inparams[i]
}
@ -164,6 +174,55 @@ func (a *ABIConfig) NumParamRegs(t *types.Type) int {
return n
}
// preAllocateParams gets the slice sizes right for inputs and outputs.
func (a *ABIParamResultInfo) preAllocateParams(hasRcvr bool, nIns, nOuts int) {
if hasRcvr {
nIns++
}
a.inparams = make([]ABIParamAssignment, 0, nIns)
a.outparams = make([]ABIParamAssignment, 0, nOuts)
}
// ABIAnalyzeTypes takes an optional receiver type, arrays of ins and outs, and returns an ABIParamResultInfo,
// based on the given configuration. This is the same result computed by config.ABIAnalyze applied to the
// corresponding method/function type, except that all the embedded parameter names are nil.
// This is intended for use by ssagen/ssa.go:(*state).rtcall, for runtime functions that lack a parsed function type.
func (config *ABIConfig) ABIAnalyzeTypes(rcvr *types.Type, ins, outs []*types.Type) *ABIParamResultInfo {
setup()
s := assignState{
rTotal: config.regAmounts,
}
result := &ABIParamResultInfo{config: config}
result.preAllocateParams(rcvr != nil, len(ins), len(outs))
// Receiver
if rcvr != nil {
result.inparams = append(result.inparams,
s.assignParamOrReturn(rcvr, nil, false))
}
// Inputs
for _, t := range ins {
result.inparams = append(result.inparams,
s.assignParamOrReturn(t, nil, false))
}
s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize))
result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
// Outputs
s.rUsed = RegAmounts{}
for _, t := range outs {
result.outparams = append(result.outparams, s.assignParamOrReturn(t, nil, true))
}
// The spill area is at a register-aligned offset and its size is rounded up to a register alignment.
// TODO in theory could align offset only to minimum required by spilled data types.
result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize)
result.spillAreaSize = alignTo(s.spillOffset, types.RegSize)
result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
return result
}
// ABIAnalyze takes a function type 't' and an ABI rules description
// 'config' and analyzes the function to determine how its parameters
// and results will be passed (in registers or on the stack), returning
@ -174,33 +233,37 @@ func (config *ABIConfig) ABIAnalyze(t *types.Type) *ABIParamResultInfo {
rTotal: config.regAmounts,
}
result := &ABIParamResultInfo{config: config}
ft := t.FuncType()
result.preAllocateParams(t.NumRecvs() != 0, ft.Params.NumFields(), ft.Results.NumFields())
// Receiver
ft := t.FuncType()
// TODO(register args) ? seems like "struct" and "fields" is not right anymore for describing function parameters
if t.NumRecvs() != 0 {
rfsl := ft.Receiver.FieldSlice()
r := ft.Receiver.FieldSlice()[0]
result.inparams = append(result.inparams,
s.assignParamOrReturn(rfsl[0], false))
s.assignParamOrReturn(r.Type, r.Nname, false))
}
// Inputs
ifsl := ft.Params.FieldSlice()
for _, f := range ifsl {
result.inparams = append(result.inparams,
s.assignParamOrReturn(f, false))
s.assignParamOrReturn(f.Type, f.Nname, false))
}
s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize))
result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
// Outputs
s.rUsed = RegAmounts{}
ofsl := ft.Results.FieldSlice()
for _, f := range ofsl {
result.outparams = append(result.outparams, s.assignParamOrReturn(f, true))
result.outparams = append(result.outparams, s.assignParamOrReturn(f.Type, f.Nname, true))
}
// The spill area is at a register-aligned offset and its size is rounded up to a register alignment.
// TODO in theory could align offset only to minimum required by spilled data types.
result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize)
result.spillAreaSize = alignTo(s.spillOffset, types.RegSize)
result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
return result
}
@ -460,17 +523,15 @@ func (state *assignState) regassign(pt *types.Type) bool {
// of field f to determine whether it can be register assigned.
// The result of the analysis is recorded in the result
// ABIParamResultInfo held in 'state'.
func (state *assignState) assignParamOrReturn(f *types.Field, isReturn bool) ABIParamAssignment {
// TODO(register args) ? seems like "struct" and "fields" is not right anymore for describing function parameters
pt := f.Type
func (state *assignState) assignParamOrReturn(pt *types.Type, n types.Object, isReturn bool) ABIParamAssignment {
state.pUsed = RegAmounts{}
if pt.Width == types.BADWIDTH {
panic("should never happen")
} else if pt.Width == 0 {
return state.stackAllocate(pt, f.Nname)
return state.stackAllocate(pt, n)
} else if state.regassign(pt) {
return state.regAllocate(pt, f.Nname, isReturn)
return state.regAllocate(pt, n, isReturn)
} else {
return state.stackAllocate(pt, f.Nname)
return state.stackAllocate(pt, n)
}
}

View file

@ -299,7 +299,7 @@ func cmpVal(v, w *Value, auxIDs auxmap) types.Cmp {
// OpSelect is a pseudo-op. We need to be more aggressive
// regarding CSE to keep multiple OpSelect's of the same
// argument from existing.
if v.Op != OpSelect0 && v.Op != OpSelect1 {
if v.Op != OpSelect0 && v.Op != OpSelect1 && v.Op != OpSelectN {
if tc := v.Type.Compare(w.Type); tc != types.CMPeq {
return tc
}

View file

@ -170,6 +170,7 @@ type expandState struct {
sdom SparseTree
common map[selKey]*Value
offsets map[offsetKey]*Value
memForCall map[ID]*Value // For a call, need to know the unique selector that gets the mem.
}
// intPairTypes returns the pair of 32-bit int types needed to encode a 64-bit integer type on a target
@ -280,7 +281,6 @@ func (x *expandState) rewriteSelect(leaf *Value, selector *Value, offset int64,
if !x.isAlreadyExpandedAggregateType(selector.Type) {
if leafType == selector.Type { // OpIData leads us here, sometimes.
leaf.copyOf(selector)
} else {
x.f.Fatalf("Unexpected OpArg type, selector=%s, leaf=%s\n", selector.LongString(), leaf.LongString())
}
@ -357,13 +357,43 @@ func (x *expandState) rewriteSelect(leaf *Value, selector *Value, offset int64,
which := selector.AuxInt
if which == aux.NResults() { // mem is after the results.
// rewrite v as a Copy of call -- the replacement call will produce a mem.
leaf.copyOf(call)
if call.Op == OpStaticLECall {
if leaf != selector {
panic("Unexpected selector of memory")
}
// StaticCall selector will address last element of Result.
// TODO do this for all the other call types eventually.
if aux.abiInfo == nil {
panic(fmt.Errorf("aux.abiInfo nil for call %s", call.LongString()))
}
if existing := x.memForCall[call.ID]; existing == nil {
selector.AuxInt = int64(aux.abiInfo.OutRegistersUsed())
x.memForCall[call.ID] = selector
} else {
selector.copyOf(existing)
}
} else {
leaf.copyOf(call)
}
} else {
leafType := removeTrivialWrapperTypes(leaf.Type)
if x.canSSAType(leafType) {
pt := types.NewPtr(leafType)
off := x.offsetFrom(x.sp, offset+aux.OffsetOfResult(which), pt)
// Any selection right out of the arg area/registers has to be same Block as call, use call as mem input.
if call.Op == OpStaticLECall { // TODO this is temporary until all calls are register-able
// Create a "mem" for any loads that need to occur.
if mem := x.memForCall[call.ID]; mem != nil {
if mem.Block != call.Block {
panic(fmt.Errorf("selector and call need to be in same block, selector=%s; call=%s", selector.LongString(), call.LongString()))
}
call = mem
} else {
mem = call.Block.NewValue1I(call.Pos.WithNotStmt(), OpSelectN, types.TypeMem, int64(aux.abiInfo.OutRegistersUsed()), call)
x.memForCall[call.ID] = mem
call = mem
}
}
if leaf.Block == call.Block {
leaf.reset(OpLoad)
leaf.SetArgs2(off, call)
@ -835,6 +865,7 @@ func expandCalls(f *Func) {
sdom: f.Sdom(),
common: make(map[selKey]*Value),
offsets: make(map[offsetKey]*Value),
memForCall: make(map[ID]*Value),
}
// For 32-bit, need to deal with decomposition of 64-bit integers, which depends on endianness.
@ -1173,7 +1204,8 @@ func expandCalls(f *Func) {
switch v.Op {
case OpStaticLECall:
v.Op = OpStaticCall
v.Type = types.TypeMem
// TODO need to insert all the register types.
v.Type = types.NewResults([]*types.Type{types.TypeMem})
case OpClosureLECall:
v.Op = OpClosureCall
v.Type = types.TypeMem

View file

@ -1970,37 +1970,6 @@
(Sqrt (Const64F [c])) && !math.IsNaN(math.Sqrt(c)) => (Const64F [math.Sqrt(c)])
// recognize runtime.newobject and don't Zero/Nilcheck it
(Zero (Load (OffPtr [c] (SP)) mem) mem)
&& mem.Op == OpStaticCall
&& isSameCall(mem.Aux, "runtime.newobject")
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
=> mem
(Store (Load (OffPtr [c] (SP)) mem) x mem)
&& isConstZero(x)
&& mem.Op == OpStaticCall
&& isSameCall(mem.Aux, "runtime.newobject")
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
=> mem
(Store (OffPtr (Load (OffPtr [c] (SP)) mem)) x mem)
&& isConstZero(x)
&& mem.Op == OpStaticCall
&& isSameCall(mem.Aux, "runtime.newobject")
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
=> mem
// nil checks just need to rewrite to something useless.
// they will be deadcode eliminated soon afterwards.
(NilCheck (Load (OffPtr [c] (SP)) (StaticCall {sym} _)) _)
&& isSameCall(sym, "runtime.newobject")
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> (Invalid)
(NilCheck (OffPtr (Load (OffPtr [c] (SP)) (StaticCall {sym} _))) _)
&& isSameCall(sym, "runtime.newobject")
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> (Invalid)
// for rewriting results of some late-expanded rewrites (below)
(SelectN [0] (MakeResult a ___)) => a
(SelectN [1] (MakeResult a b ___)) => b
@ -2021,12 +1990,12 @@
&& isSameCall(call.Aux, "runtime.newobject")
=> mem
(NilCheck (SelectN [0] call:(StaticLECall _ _)) (SelectN [1] call))
(NilCheck (SelectN [0] call:(StaticLECall _ _)) _)
&& isSameCall(call.Aux, "runtime.newobject")
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> (Invalid)
(NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) (SelectN [1] call))
(NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) _)
&& isSameCall(call.Aux, "runtime.newobject")
&& warnRule(fe.Debug_checknil(), v, "removed nil check")
=> (Invalid)
@ -2083,15 +2052,19 @@
(IsNonNil (Addr _)) => (ConstBool [true])
(IsNonNil (LocalAddr _ _)) => (ConstBool [true])
// TODO REGISTER ARGS this will need revision.
// Because expand calls runs after prove, constants useful to this pattern may not appear
// In the future both versions need to exist; the memory and register variants.
// Inline small or disjoint runtime.memmove calls with constant length.
// See the comment in op Move in genericOps.go for discussion of the type.
(StaticCall {sym} s1:(Store _ (Const(64|32) [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))
(SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const(64|32) [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))))
&& sz >= 0
&& isSameCall(sym, "runtime.memmove")
&& t.IsPtr() // avoids TUINTPTR, see issue 30061
&& s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1
&& isInlinableMemmove(dst, src, int64(sz), config)
&& clobber(s1, s2, s3)
&& clobber(s1, s2, s3, call)
=> (Move {t.Elem()} [int64(sz)] dst src mem)
// Inline small or disjoint runtime.memmove calls with constant length.
@ -2105,13 +2078,6 @@
&& clobber(call)
=> (Move {dst.Type.Elem()} [int64(sz)] dst src mem)
// De-virtualize interface calls into static calls.
// Note that (ITab (IMake)) doesn't get
// rewritten until after the first opt pass,
// so this rule should trigger reliably.
(InterCall [argsize] {auxCall} (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem) && devirt(v, auxCall, itab, off) != nil =>
(StaticCall [int32(argsize)] {devirt(v, auxCall, itab, off)} mem)
// De-virtualize late-expanded interface calls into late-expanded static calls.
// Note that (ITab (IMake)) doesn't get rewritten until after the first opt pass,
// so this rule should trigger reliably.
@ -2499,8 +2465,8 @@
(Store {t5} (OffPtr <tt5> [o5] dst) d4
(Zero {t1} [n] dst mem)))))
// TODO this does not fire before call expansion; is that acceptable?
(StaticCall {sym} x) && needRaceCleanup(sym, v) => x
(SelectN [0] call:(StaticLECall {sym} a x)) && needRaceCleanup(sym, call) && clobber(call) => x
(SelectN [0] call:(StaticLECall {sym} x)) && needRaceCleanup(sym, call) && clobber(call) => x
// Collapse moving A -> B -> C into just A -> C.
// Later passes (deadstore, elim unread auto) will remove the A -> B move, if possible.

View file

@ -396,7 +396,7 @@ var genericOps = []opData{
// to match StaticCall's 32 bit arg size limit.
// TODO(drchase,josharian): could the arg size limit be bundled into the rules for CallOff?
{name: "ClosureCall", argLength: 3, aux: "CallOff", call: true}, // arg0=code pointer, arg1=context ptr, arg2=memory. auxint=arg size. Returns memory.
{name: "StaticCall", argLength: 1, aux: "CallOff", call: true}, // call function aux.(*obj.LSym), arg0=memory. auxint=arg size. Returns memory.
{name: "StaticCall", argLength: -1, aux: "CallOff", call: true}, // call function aux.(*obj.LSym), arg0..argN-1 are register inputs, argN=memory. auxint=arg size. Returns Result of register results, plus memory.
{name: "InterCall", argLength: 2, aux: "CallOff", call: true}, // interface call. arg0=code pointer, arg1=memory, auxint=arg size. Returns memory.
{name: "ClosureLECall", argLength: -1, aux: "CallOff", call: true}, // late-expanded closure call. arg0=code pointer, arg1=context ptr, arg2..argN-1 are inputs, argN is mem. auxint = arg size. Result is tuple of result(s), plus mem.
{name: "StaticLECall", argLength: -1, aux: "CallOff", call: true}, // late-expanded static call function aux.(*ssa.AuxCall.Fn). arg0..argN-1 are inputs, argN is mem. auxint = arg size. Result is tuple of result(s), plus mem.

View file

@ -88,6 +88,20 @@ func (t LocPair) String() string {
return fmt.Sprintf("<%s,%s>", n0, n1)
}
type LocResults []Location
func (t LocResults) String() string {
s := "<"
a := ""
for _, r := range t {
a += s
s = ","
a += r.String()
}
a += ">"
return a
}
type ArgPair struct {
reg *Register
mem LocalSlot

View file

@ -21,7 +21,7 @@ func checkLower(f *Func) {
continue // lowered
}
switch v.Op {
case OpSP, OpSB, OpInitMem, OpArg, OpPhi, OpVarDef, OpVarKill, OpVarLive, OpKeepAlive, OpSelect0, OpSelect1, OpConvert, OpInlMark:
case OpSP, OpSB, OpInitMem, OpArg, OpPhi, OpVarDef, OpVarKill, OpVarLive, OpKeepAlive, OpSelect0, OpSelect1, OpSelectN, OpConvert, OpInlMark:
continue // ok not to lower
case OpGetG:
if f.Config.hasGReg {

View file

@ -203,9 +203,19 @@ func (a *AuxCall) String() string {
return fn + "}"
}
func ACParamsToTypes(ps []Param) (ts []*types.Type) {
for _, p := range ps {
ts = append(ts, p.Type)
}
return
}
// StaticAuxCall returns an AuxCall for a static call.
func StaticAuxCall(sym *obj.LSym, args []Param, results []Param, paramResultInfo *abi.ABIParamResultInfo) *AuxCall {
// TODO Create regInfo for AuxCall
if paramResultInfo == nil {
panic(fmt.Errorf("Nil paramResultInfo, sym=%v", sym))
}
return &AuxCall{Fn: sym, args: args, results: results, abiInfo: paramResultInfo}
}

View file

@ -35435,7 +35435,7 @@ var opcodeTable = [...]opInfo{
{
name: "StaticCall",
auxType: auxCallOff,
argLen: 1,
argLen: -1,
call: true,
generic: true,
},

View file

@ -761,6 +761,9 @@ func (s *regAllocState) advanceUses(v *Value) {
// current instruction.
func (s *regAllocState) liveAfterCurrentInstruction(v *Value) bool {
u := s.values[v.ID].uses
if u == nil {
panic(fmt.Errorf("u is nil, v = %s, s.values[v.ID] = %v", v.LongString(), s.values[v.ID]))
}
d := u.dist
for u != nil && u.dist == d {
u = u.next
@ -1208,13 +1211,17 @@ func (s *regAllocState) regalloc(f *Func) {
s.sb = v.ID
continue
}
if v.Op == OpSelect0 || v.Op == OpSelect1 {
if v.Op == OpSelect0 || v.Op == OpSelect1 || v.Op == OpSelectN {
if s.values[v.ID].needReg {
var i = 0
if v.Op == OpSelect1 {
i = 1
if v.Op == OpSelectN {
s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocResults)[int(v.AuxInt)].(*Register).num), v, v)
} else {
var i = 0
if v.Op == OpSelect1 {
i = 1
}
s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocPair)[i].(*Register).num), v, v)
}
s.assignReg(register(s.f.getHome(v.Args[0].ID).(LocPair)[i].(*Register).num), v, v)
}
b.Values = append(b.Values, v)
s.advanceUses(v)
@ -1767,6 +1774,9 @@ func (s *regAllocState) placeSpills() {
// put the spill of v. At the start "best" is the best place
// we have found so far.
// TODO: find a way to make this O(1) without arbitrary cutoffs.
if v == nil {
panic(fmt.Errorf("nil v, s.orig[%d], vi = %v, spill = %s", i, vi, spill.LongString()))
}
best := v.Block
bestArg := v
var bestDepth int16

View file

@ -793,7 +793,10 @@ func devirtLESym(v *Value, aux Aux, sym Sym, offset int64) *obj.LSym {
func devirtLECall(v *Value, sym *obj.LSym) *Value {
v.Op = OpStaticLECall
v.Aux.(*AuxCall).Fn = sym
auxcall := v.Aux.(*AuxCall)
auxcall.Fn = sym
// TODO(register args) this should not be necessary when fully transition to the new register ABI.
auxcall.abiInfo = v.Block.Func.ABIDefault.ABIAnalyzeTypes(nil, ACParamsToTypes(auxcall.args), ACParamsToTypes(auxcall.results))
v.RemoveArg(0)
return v
}
@ -1617,7 +1620,7 @@ func needRaceCleanup(sym *AuxCall, v *Value) bool {
for _, b := range f.Blocks {
for _, v := range b.Values {
switch v.Op {
case OpStaticCall:
case OpStaticCall, OpStaticLECall:
// Check for racefuncenter/racefuncenterfp will encounter racefuncexit and vice versa.
// Allow calls to panic*
s := v.Aux.(*AuxCall).Fn.String()
@ -1632,15 +1635,20 @@ func needRaceCleanup(sym *AuxCall, v *Value) bool {
return false
case OpPanicBounds, OpPanicExtend:
// Note: these are panic generators that are ok (like the static calls above).
case OpClosureCall, OpInterCall:
case OpClosureCall, OpInterCall, OpClosureLECall, OpInterLECall:
// We must keep the race functions if there are any other call types.
return false
}
}
}
if isSameCall(sym, "runtime.racefuncenter") {
// TODO REGISTER ABI this needs to be cleaned up.
// If we're removing racefuncenter, remove its argument as well.
if v.Args[0].Op != OpStore {
if v.Op == OpStaticLECall {
// there is no store, yet.
return true
}
return false
}
mem := v.Args[0].Args[2]

View file

@ -122,8 +122,6 @@ func rewriteValuegeneric(v *Value) bool {
return rewriteValuegeneric_OpEqSlice(v)
case OpIMake:
return rewriteValuegeneric_OpIMake(v)
case OpInterCall:
return rewriteValuegeneric_OpInterCall(v)
case OpInterLECall:
return rewriteValuegeneric_OpInterLECall(v)
case OpIsInBounds:
@ -392,8 +390,6 @@ func rewriteValuegeneric(v *Value) bool {
return rewriteValuegeneric_OpSlicemask(v)
case OpSqrt:
return rewriteValuegeneric_OpSqrt(v)
case OpStaticCall:
return rewriteValuegeneric_OpStaticCall(v)
case OpStaticLECall:
return rewriteValuegeneric_OpStaticLECall(v)
case OpStore:
@ -8542,52 +8538,6 @@ func rewriteValuegeneric_OpIMake(v *Value) bool {
}
return false
}
func rewriteValuegeneric_OpInterCall(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (InterCall [argsize] {auxCall} (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem)
// cond: devirt(v, auxCall, itab, off) != nil
// result: (StaticCall [int32(argsize)] {devirt(v, auxCall, itab, off)} mem)
for {
argsize := auxIntToInt32(v.AuxInt)
auxCall := auxToCall(v.Aux)
if v_0.Op != OpLoad {
break
}
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpOffPtr {
break
}
off := auxIntToInt64(v_0_0.AuxInt)
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpITab {
break
}
v_0_0_0_0 := v_0_0_0.Args[0]
if v_0_0_0_0.Op != OpIMake {
break
}
v_0_0_0_0_0 := v_0_0_0_0.Args[0]
if v_0_0_0_0_0.Op != OpAddr {
break
}
itab := auxToSym(v_0_0_0_0_0.Aux)
v_0_0_0_0_0_0 := v_0_0_0_0_0.Args[0]
if v_0_0_0_0_0_0.Op != OpSB {
break
}
mem := v_1
if !(devirt(v, auxCall, itab, off) != nil) {
break
}
v.reset(OpStaticCall)
v.AuxInt = int32ToAuxInt(int32(argsize))
v.Aux = callToAux(devirt(v, auxCall, itab, off))
v.AddArg(mem)
return true
}
return false
}
func rewriteValuegeneric_OpInterLECall(v *Value) bool {
// match: (InterLECall [argsize] {auxCall} (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) ___)
// cond: devirtLESym(v, auxCall, itab, off) != nil
@ -16113,7 +16063,6 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
fe := b.Func.fe
// match: (NilCheck (GetG mem) mem)
// result: mem
@ -16128,67 +16077,7 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
v.copyOf(mem)
return true
}
// match: (NilCheck (Load (OffPtr [c] (SP)) (StaticCall {sym} _)) _)
// cond: isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")
// result: (Invalid)
for {
if v_0.Op != OpLoad {
break
}
_ = v_0.Args[1]
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpOffPtr {
break
}
c := auxIntToInt64(v_0_0.AuxInt)
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpSP {
break
}
v_0_1 := v_0.Args[1]
if v_0_1.Op != OpStaticCall {
break
}
sym := auxToCall(v_0_1.Aux)
if !(isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
break
}
v.reset(OpInvalid)
return true
}
// match: (NilCheck (OffPtr (Load (OffPtr [c] (SP)) (StaticCall {sym} _))) _)
// cond: isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")
// result: (Invalid)
for {
if v_0.Op != OpOffPtr {
break
}
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpLoad {
break
}
_ = v_0_0.Args[1]
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpOffPtr {
break
}
c := auxIntToInt64(v_0_0_0.AuxInt)
v_0_0_0_0 := v_0_0_0.Args[0]
if v_0_0_0_0.Op != OpSP {
break
}
v_0_0_1 := v_0_0.Args[1]
if v_0_0_1.Op != OpStaticCall {
break
}
sym := auxToCall(v_0_0_1.Aux)
if !(isSameCall(sym, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
break
}
v.reset(OpInvalid)
return true
}
// match: (NilCheck (SelectN [0] call:(StaticLECall _ _)) (SelectN [1] call))
// match: (NilCheck (SelectN [0] call:(StaticLECall _ _)) _)
// cond: isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")
// result: (Invalid)
for {
@ -16196,13 +16085,13 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
break
}
call := v_0.Args[0]
if call.Op != OpStaticLECall || len(call.Args) != 2 || v_1.Op != OpSelectN || auxIntToInt64(v_1.AuxInt) != 1 || call != v_1.Args[0] || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
if call.Op != OpStaticLECall || len(call.Args) != 2 || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
break
}
v.reset(OpInvalid)
return true
}
// match: (NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) (SelectN [1] call))
// match: (NilCheck (OffPtr (SelectN [0] call:(StaticLECall _ _))) _)
// cond: isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")
// result: (Invalid)
for {
@ -16214,7 +16103,7 @@ func rewriteValuegeneric_OpNilCheck(v *Value) bool {
break
}
call := v_0_0.Args[0]
if call.Op != OpStaticLECall || len(call.Args) != 2 || v_1.Op != OpSelectN || auxIntToInt64(v_1.AuxInt) != 1 || call != v_1.Args[0] || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
if call.Op != OpStaticLECall || len(call.Args) != 2 || !(isSameCall(call.Aux, "runtime.newobject") && warnRule(fe.Debug_checknil(), v, "removed nil check")) {
break
}
v.reset(OpInvalid)
@ -20799,6 +20688,94 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool {
v.copyOf(c)
return true
}
// match: (SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const64 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))))
// cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)
// result: (Move {t.Elem()} [int64(sz)] dst src mem)
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticCall || len(call.Args) != 1 {
break
}
sym := auxToCall(call.Aux)
s1 := call.Args[0]
if s1.Op != OpStore {
break
}
_ = s1.Args[2]
s1_1 := s1.Args[1]
if s1_1.Op != OpConst64 {
break
}
sz := auxIntToInt64(s1_1.AuxInt)
s2 := s1.Args[2]
if s2.Op != OpStore {
break
}
_ = s2.Args[2]
src := s2.Args[1]
s3 := s2.Args[2]
if s3.Op != OpStore {
break
}
t := auxToType(s3.Aux)
mem := s3.Args[2]
dst := s3.Args[1]
if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)) {
break
}
v.reset(OpMove)
v.AuxInt = int64ToAuxInt(int64(sz))
v.Aux = typeToAux(t.Elem())
v.AddArg3(dst, src, mem)
return true
}
// match: (SelectN [0] call:(StaticCall {sym} s1:(Store _ (Const32 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem)))))
// cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)
// result: (Move {t.Elem()} [int64(sz)] dst src mem)
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticCall || len(call.Args) != 1 {
break
}
sym := auxToCall(call.Aux)
s1 := call.Args[0]
if s1.Op != OpStore {
break
}
_ = s1.Args[2]
s1_1 := s1.Args[1]
if s1_1.Op != OpConst32 {
break
}
sz := auxIntToInt32(s1_1.AuxInt)
s2 := s1.Args[2]
if s2.Op != OpStore {
break
}
_ = s2.Args[2]
src := s2.Args[1]
s3 := s2.Args[2]
if s3.Op != OpStore {
break
}
t := auxToType(s3.Aux)
mem := s3.Args[2]
dst := s3.Args[1]
if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3, call)) {
break
}
v.reset(OpMove)
v.AuxInt = int64ToAuxInt(int64(sz))
v.Aux = typeToAux(t.Elem())
v.AddArg3(dst, src, mem)
return true
}
// match: (SelectN [0] call:(StaticLECall {sym} dst src (Const64 [sz]) mem))
// cond: sz >= 0 && call.Uses == 1 && isSameCall(sym, "runtime.memmove") && dst.Type.IsPtr() && isInlinableMemmove(dst, src, int64(sz), config) && clobber(call)
// result: (Move {dst.Type.Elem()} [int64(sz)] dst src mem)
@ -20857,6 +20834,44 @@ func rewriteValuegeneric_OpSelectN(v *Value) bool {
v.AddArg3(dst, src, mem)
return true
}
// match: (SelectN [0] call:(StaticLECall {sym} a x))
// cond: needRaceCleanup(sym, call) && clobber(call)
// result: x
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 2 {
break
}
sym := auxToCall(call.Aux)
x := call.Args[1]
if !(needRaceCleanup(sym, call) && clobber(call)) {
break
}
v.copyOf(x)
return true
}
// match: (SelectN [0] call:(StaticLECall {sym} x))
// cond: needRaceCleanup(sym, call) && clobber(call)
// result: x
for {
if auxIntToInt64(v.AuxInt) != 0 {
break
}
call := v_0
if call.Op != OpStaticLECall || len(call.Args) != 1 {
break
}
sym := auxToCall(call.Aux)
x := call.Args[0]
if !(needRaceCleanup(sym, call) && clobber(call)) {
break
}
v.copyOf(x)
return true
}
return false
}
func rewriteValuegeneric_OpSignExt16to32(v *Value) bool {
@ -21307,98 +21322,6 @@ func rewriteValuegeneric_OpSqrt(v *Value) bool {
}
return false
}
func rewriteValuegeneric_OpStaticCall(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
// match: (StaticCall {sym} s1:(Store _ (Const64 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))
// cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)
// result: (Move {t.Elem()} [int64(sz)] dst src mem)
for {
sym := auxToCall(v.Aux)
s1 := v_0
if s1.Op != OpStore {
break
}
_ = s1.Args[2]
s1_1 := s1.Args[1]
if s1_1.Op != OpConst64 {
break
}
sz := auxIntToInt64(s1_1.AuxInt)
s2 := s1.Args[2]
if s2.Op != OpStore {
break
}
_ = s2.Args[2]
src := s2.Args[1]
s3 := s2.Args[2]
if s3.Op != OpStore {
break
}
t := auxToType(s3.Aux)
mem := s3.Args[2]
dst := s3.Args[1]
if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)) {
break
}
v.reset(OpMove)
v.AuxInt = int64ToAuxInt(int64(sz))
v.Aux = typeToAux(t.Elem())
v.AddArg3(dst, src, mem)
return true
}
// match: (StaticCall {sym} s1:(Store _ (Const32 [sz]) s2:(Store _ src s3:(Store {t} _ dst mem))))
// cond: sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)
// result: (Move {t.Elem()} [int64(sz)] dst src mem)
for {
sym := auxToCall(v.Aux)
s1 := v_0
if s1.Op != OpStore {
break
}
_ = s1.Args[2]
s1_1 := s1.Args[1]
if s1_1.Op != OpConst32 {
break
}
sz := auxIntToInt32(s1_1.AuxInt)
s2 := s1.Args[2]
if s2.Op != OpStore {
break
}
_ = s2.Args[2]
src := s2.Args[1]
s3 := s2.Args[2]
if s3.Op != OpStore {
break
}
t := auxToType(s3.Aux)
mem := s3.Args[2]
dst := s3.Args[1]
if !(sz >= 0 && isSameCall(sym, "runtime.memmove") && t.IsPtr() && s1.Uses == 1 && s2.Uses == 1 && s3.Uses == 1 && isInlinableMemmove(dst, src, int64(sz), config) && clobber(s1, s2, s3)) {
break
}
v.reset(OpMove)
v.AuxInt = int64ToAuxInt(int64(sz))
v.Aux = typeToAux(t.Elem())
v.AddArg3(dst, src, mem)
return true
}
// match: (StaticCall {sym} x)
// cond: needRaceCleanup(sym, v)
// result: x
for {
sym := auxToCall(v.Aux)
x := v_0
if !(needRaceCleanup(sym, v)) {
break
}
v.copyOf(x)
return true
}
return false
}
func rewriteValuegeneric_OpStaticLECall(v *Value) bool {
b := v.Block
typ := &b.Func.Config.Types
@ -21442,7 +21365,6 @@ func rewriteValuegeneric_OpStore(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
fe := b.Func.fe
// match: (Store {t1} p1 (Load <t2> p2 mem) mem)
// cond: isSamePtr(p1, p2) && t2.Size() == t1.Size()
@ -21890,58 +21812,6 @@ func rewriteValuegeneric_OpStore(v *Value) bool {
v.AddArg3(dst, e, mem)
return true
}
// match: (Store (Load (OffPtr [c] (SP)) mem) x mem)
// cond: isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize
// result: mem
for {
if v_0.Op != OpLoad {
break
}
mem := v_0.Args[1]
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpOffPtr {
break
}
c := auxIntToInt64(v_0_0.AuxInt)
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpSP {
break
}
x := v_1
if mem != v_2 || !(isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) {
break
}
v.copyOf(mem)
return true
}
// match: (Store (OffPtr (Load (OffPtr [c] (SP)) mem)) x mem)
// cond: isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize
// result: mem
for {
if v_0.Op != OpOffPtr {
break
}
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpLoad {
break
}
mem := v_0_0.Args[1]
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpOffPtr {
break
}
c := auxIntToInt64(v_0_0_0.AuxInt)
v_0_0_0_0 := v_0_0_0.Args[0]
if v_0_0_0_0.Op != OpSP {
break
}
x := v_1
if mem != v_2 || !(isConstZero(x) && mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) {
break
}
v.copyOf(mem)
return true
}
// match: (Store (SelectN [0] call:(StaticLECall _ _)) x mem:(SelectN [1] call))
// cond: isConstZero(x) && isSameCall(call.Aux, "runtime.newobject")
// result: mem
@ -24660,27 +24530,6 @@ func rewriteValuegeneric_OpZero(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
// match: (Zero (Load (OffPtr [c] (SP)) mem) mem)
// cond: mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize() + config.RegSize
// result: mem
for {
if v_0.Op != OpLoad {
break
}
mem := v_0.Args[1]
v_0_0 := v_0.Args[0]
if v_0_0.Op != OpOffPtr {
break
}
c := auxIntToInt64(v_0_0.AuxInt)
v_0_0_0 := v_0_0.Args[0]
if v_0_0_0.Op != OpSP || mem != v_1 || !(mem.Op == OpStaticCall && isSameCall(mem.Aux, "runtime.newobject") && c == config.ctxt.FixedFrameSize()+config.RegSize) {
break
}
v.copyOf(mem)
return true
}
// match: (Zero (SelectN [0] call:(StaticLECall _ _)) mem:(SelectN [1] call))
// cond: isSameCall(call.Aux, "runtime.newobject")
// result: mem

View file

@ -145,7 +145,7 @@ func schedule(f *Func) {
// reduce register pressure. It also helps make sure
// VARDEF ops are scheduled before the corresponding LEA.
score[v.ID] = ScoreMemory
case v.Op == OpSelect0 || v.Op == OpSelect1:
case v.Op == OpSelect0 || v.Op == OpSelect1 || v.Op == OpSelectN:
// Schedule the pseudo-op of reading part of a tuple
// immediately after the tuple-generating op, since
// this value is already live. This also removes its
@ -270,6 +270,20 @@ func schedule(f *Func) {
tuples[v.Args[0].ID] = make([]*Value, 2)
}
tuples[v.Args[0].ID][1] = v
case v.Op == OpSelectN:
if tuples[v.Args[0].ID] == nil {
tuples[v.Args[0].ID] = make([]*Value, v.Args[0].Type.NumFields())
}
tuples[v.Args[0].ID][v.AuxInt] = v
case v.Type.IsResults() && tuples[v.ID] != nil:
tup := tuples[v.ID]
for i := len(tup) - 1; i >= 0; i-- {
if tup[i] != nil {
order = append(order, tup[i])
}
}
delete(tuples, v.ID)
order = append(order, v)
case v.Type.IsTuple() && tuples[v.ID] != nil:
if tuples[v.ID][1] != nil {
order = append(order, tuples[v.ID][1])

View file

@ -18,10 +18,11 @@ func tighten(f *Func) {
continue
}
switch v.Op {
case OpPhi, OpArg, OpSelect0, OpSelect1:
case OpPhi, OpArg, OpSelect0, OpSelect1, OpSelectN:
// Phis need to stay in their block.
// Arg must stay in the entry block.
// Tuple selectors must stay with the tuple generator.
// SelectN is typically, ultimately, a register.
continue
}
if v.MemoryArg() != nil {

View file

@ -4,8 +4,8 @@
package ssa
// tightenTupleSelectors ensures that tuple selectors (Select0 and
// Select1 ops) are in the same block as their tuple generator. The
// tightenTupleSelectors ensures that tuple selectors (Select0, Select1,
// and SelectN ops) are in the same block as their tuple generator. The
// function also ensures that there are no duplicate tuple selectors.
// These properties are expected by the scheduler but may not have
// been maintained by the optimization pipeline up to this point.
@ -13,28 +13,40 @@ package ssa
// See issues 16741 and 39472.
func tightenTupleSelectors(f *Func) {
selectors := make(map[struct {
id ID
op Op
id ID
which int
}]*Value)
for _, b := range f.Blocks {
for _, selector := range b.Values {
if selector.Op != OpSelect0 && selector.Op != OpSelect1 {
// Key fields for de-duplication
var tuple *Value
idx := 0
switch selector.Op {
default:
continue
}
// Get the tuple generator to use as a key for de-duplication.
tuple := selector.Args[0]
if !tuple.Type.IsTuple() {
f.Fatalf("arg of tuple selector %s is not a tuple: %s", selector.String(), tuple.LongString())
case OpSelect1:
idx = 1
fallthrough
case OpSelect0:
tuple = selector.Args[0]
if !tuple.Type.IsTuple() {
f.Fatalf("arg of tuple selector %s is not a tuple: %s", selector.String(), tuple.LongString())
}
case OpSelectN:
tuple = selector.Args[0]
idx = int(selector.AuxInt)
if !tuple.Type.IsResults() {
f.Fatalf("arg of result selector %s is not a results: %s", selector.String(), tuple.LongString())
}
}
// If there is a pre-existing selector in the target block then
// use that. Do this even if the selector is already in the
// target block to avoid duplicate tuple selectors.
key := struct {
id ID
op Op
}{tuple.ID, selector.Op}
id ID
which int
}{tuple.ID, idx}
if t := selectors[key]; t != nil {
if selector != t {
selector.copyOf(t)

View file

@ -487,12 +487,14 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
off := config.ctxt.FixedFrameSize()
var ACArgs []Param
var argTypes []*types.Type
if typ != nil { // for typedmemmove
taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
off = round(off, taddr.Type.Alignment())
arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp)
mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem)
ACArgs = append(ACArgs, Param{Type: b.Func.Config.Types.Uintptr, Offset: int32(off)})
argTypes = append(argTypes, b.Func.Config.Types.Uintptr)
off += taddr.Type.Size()
}
@ -500,6 +502,7 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp)
mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem)
ACArgs = append(ACArgs, Param{Type: ptr.Type, Offset: int32(off)})
argTypes = append(argTypes, ptr.Type)
off += ptr.Type.Size()
if val != nil {
@ -507,15 +510,15 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp)
mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem)
ACArgs = append(ACArgs, Param{Type: val.Type, Offset: int32(off)})
argTypes = append(argTypes, val.Type)
off += val.Type.Size()
}
off = round(off, config.PtrSize)
// issue call
// TODO(register args) -- will need more details
mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, StaticAuxCall(fn, ACArgs, nil, nil), mem)
mem = b.NewValue1A(pos, OpStaticCall, types.TypeResultMem, StaticAuxCall(fn, ACArgs, nil, b.Func.ABIDefault.ABIAnalyzeTypes(nil, argTypes, nil)), mem)
mem.AuxInt = off - config.ctxt.FixedFrameSize()
return mem
return b.NewValue1I(pos, OpSelectN, types.TypeMem, 0, mem)
}
// round to a multiple of r, r is a power of 2
@ -563,12 +566,20 @@ func IsReadOnlyGlobalAddr(v *Value) bool {
// IsNewObject reports whether v is a pointer to a freshly allocated & zeroed object at memory state mem.
func IsNewObject(v *Value, mem *Value) bool {
// TODO this will need updating for register args; the OpLoad is wrong.
if v.Op != OpLoad {
return false
}
if v.MemoryArg() != mem {
return false
}
if mem.Op != OpSelectN {
return false
}
if mem.Type != types.TypeMem {
return false
} // assume it is the right selection if true
mem = mem.Args[0]
if mem.Op != OpStaticCall {
return false
}

View file

@ -4734,7 +4734,8 @@ func (s *state) openDeferExit() {
aux := ssa.ClosureAuxCall(ACArgs, ACResults)
call = s.newValue2A(ssa.OpClosureLECall, aux.LateExpansionResultType(), aux, codeptr, v)
} else {
aux := ssa.StaticAuxCall(fn.(*ir.Name).Linksym(), ACArgs, ACResults, nil) // TODO will need types for this.
aux := ssa.StaticAuxCall(fn.(*ir.Name).Linksym(), ACArgs, ACResults,
s.f.ABIDefault.ABIAnalyzeTypes(nil, ssa.ACParamsToTypes(ACArgs), ssa.ACParamsToTypes(ACResults)))
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
}
callArgs = append(callArgs, s.mem())
@ -4896,7 +4897,8 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
// Call runtime.deferprocStack with pointer to _defer record.
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[types.TUINTPTR], Offset: int32(base.Ctxt.FixedFrameSize())})
aux := ssa.StaticAuxCall(ir.Syms.DeferprocStack, ACArgs, ACResults, nil)
aux := ssa.StaticAuxCall(ir.Syms.DeferprocStack, ACArgs, ACResults,
s.f.ABIDefault.ABIAnalyzeTypes(nil, ssa.ACParamsToTypes(ACArgs), ssa.ACParamsToTypes(ACResults)))
callArgs = append(callArgs, addr, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
@ -4956,10 +4958,12 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
// call target
switch {
case k == callDefer:
aux := ssa.StaticAuxCall(ir.Syms.Deferproc, ACArgs, ACResults, nil) // TODO paramResultInfo for DeferProc
aux := ssa.StaticAuxCall(ir.Syms.Deferproc, ACArgs, ACResults,
s.f.ABIDefault.ABIAnalyzeTypes(nil, ssa.ACParamsToTypes(ACArgs), ssa.ACParamsToTypes(ACResults))) // TODO paramResultInfo for DeferProc
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
case k == callGo:
aux := ssa.StaticAuxCall(ir.Syms.Newproc, ACArgs, ACResults, nil)
aux := ssa.StaticAuxCall(ir.Syms.Newproc, ACArgs, ACResults,
s.f.ABIDefault.ABIAnalyzeTypes(nil, ssa.ACParamsToTypes(ACArgs), ssa.ACParamsToTypes(ACResults)))
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux) // TODO paramResultInfo for NewProc
case closure != nil:
// rawLoad because loading the code pointer from a
@ -5434,6 +5438,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
var ACArgs []ssa.Param
var ACResults []ssa.Param
var callArgs []*ssa.Value
var callArgTypes []*types.Type
for _, arg := range args {
t := arg.Type
@ -5441,6 +5446,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
size := t.Size()
ACArgs = append(ACArgs, ssa.Param{Type: t, Offset: int32(off)})
callArgs = append(callArgs, arg)
callArgTypes = append(callArgTypes, t)
off += size
}
off = types.Rnd(off, int64(types.RegSize))
@ -5455,7 +5461,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
// Issue call
var call *ssa.Value
aux := ssa.StaticAuxCall(fn, ACArgs, ACResults, nil) // WILL NEED A TYPE FOR THIS.)
aux := ssa.StaticAuxCall(fn, ACArgs, ACResults, s.f.ABIDefault.ABIAnalyzeTypes(nil, callArgTypes, results))
callArgs = append(callArgs, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
@ -6520,7 +6526,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// input args need no code
case ssa.OpSP, ssa.OpSB:
// nothing to do
case ssa.OpSelect0, ssa.OpSelect1:
case ssa.OpSelect0, ssa.OpSelect1, ssa.OpSelectN:
// nothing to do
case ssa.OpGetG:
// nothing to do when there's a g register,

View file

@ -581,12 +581,19 @@ func NewTuple(t1, t2 *Type) *Type {
return t
}
func NewResults(types []*Type) *Type {
func newResults(types []*Type) *Type {
t := New(TRESULTS)
t.Extra.(*Results).Types = types
return t
}
func NewResults(types []*Type) *Type {
if len(types) == 1 && types[0] == TypeMem {
return TypeResultMem
}
return newResults(types)
}
func newSSA(name string) *Type {
t := New(TSSA)
t.Extra = name
@ -1407,6 +1414,9 @@ func (t *Type) PtrTo() *Type {
}
func (t *Type) NumFields() int {
if t.kind == TRESULTS {
return len(t.Extra.(*Results).Types)
}
return t.Fields().Len()
}
func (t *Type) FieldType(i int) *Type {
@ -1597,11 +1607,12 @@ func FakeRecvType() *Type {
var (
// TSSA types. HasPointers assumes these are pointer-free.
TypeInvalid = newSSA("invalid")
TypeMem = newSSA("mem")
TypeFlags = newSSA("flags")
TypeVoid = newSSA("void")
TypeInt128 = newSSA("int128")
TypeInvalid = newSSA("invalid")
TypeMem = newSSA("mem")
TypeFlags = newSSA("flags")
TypeVoid = newSSA("void")
TypeInt128 = newSSA("int128")
TypeResultMem = newResults([]*Type{TypeMem})
)
// NewNamed returns a new named type for the given type name.