mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Passes toolstash -cmp Update #14473. Change-Id: I15b35d40a5ec1f4355ee38bc6d131920933ac95c Reviewed-on: https://go-review.googlesource.com/20237 Run-TryBot: Ian Lance Taylor <iant@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
1212 lines
34 KiB
Go
1212 lines
34 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gc
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// Rewrite tree to use separate statements to enforce
|
|
// order of evaluation. Makes walk easier, because it
|
|
// can (after this runs) reorder at will within an expression.
|
|
//
|
|
// Rewrite x op= y into x = x op y.
|
|
//
|
|
// Introduce temporaries as needed by runtime routines.
|
|
// For example, the map runtime routines take the map key
|
|
// by reference, so make sure all map keys are addressable
|
|
// by copying them to temporaries as needed.
|
|
// The same is true for channel operations.
|
|
//
|
|
// Arrange that map index expressions only appear in direct
|
|
// assignments x = m[k] or m[k] = x, never in larger expressions.
|
|
//
|
|
// Arrange that receive expressions only appear in direct assignments
|
|
// x = <-c or as standalone statements <-c, never in larger expressions.
|
|
|
|
// TODO(rsc): The temporary introduction during multiple assignments
|
|
// should be moved into this file, so that the temporaries can be cleaned
|
|
// and so that conversions implicit in the OAS2FUNC and OAS2RECV
|
|
// nodes can be made explicit and then have their temporaries cleaned.
|
|
|
|
// TODO(rsc): Goto and multilevel break/continue can jump over
|
|
// inserted VARKILL annotations. Work out a way to handle these.
|
|
// The current implementation is safe, in that it will execute correctly.
|
|
// But it won't reuse temporaries as aggressively as it might, and
|
|
// it can result in unnecessary zeroing of those variables in the function
|
|
// prologue.
|
|
|
|
// Order holds state during the ordering process.
|
|
type Order struct {
|
|
out []*Node // list of generated statements
|
|
temp []*Node // stack of temporary variables
|
|
}
|
|
|
|
// Order rewrites fn->nbody to apply the ordering constraints
|
|
// described in the comment at the top of the file.
|
|
func order(fn *Node) {
|
|
if Debug['W'] > 1 {
|
|
s := fmt.Sprintf("\nbefore order %v", fn.Func.Nname.Sym)
|
|
dumplist(s, fn.Nbody)
|
|
}
|
|
|
|
orderblockNodes(&fn.Nbody)
|
|
}
|
|
|
|
// Ordertemp allocates a new temporary with the given type,
|
|
// pushes it onto the temp stack, and returns it.
|
|
// If clear is true, ordertemp emits code to zero the temporary.
|
|
func ordertemp(t *Type, order *Order, clear bool) *Node {
|
|
var_ := temp(t)
|
|
if clear {
|
|
a := Nod(OAS, var_, nil)
|
|
typecheck(&a, Etop)
|
|
order.out = append(order.out, a)
|
|
}
|
|
|
|
order.temp = append(order.temp, var_)
|
|
return var_
|
|
}
|
|
|
|
// Ordercopyexpr behaves like ordertemp but also emits
|
|
// code to initialize the temporary to the value n.
|
|
//
|
|
// The clear argument is provided for use when the evaluation
|
|
// of tmp = n turns into a function call that is passed a pointer
|
|
// to the temporary as the output space. If the call blocks before
|
|
// tmp has been written, the garbage collector will still treat the
|
|
// temporary as live, so we must zero it before entering that call.
|
|
// Today, this only happens for channel receive operations.
|
|
// (The other candidate would be map access, but map access
|
|
// returns a pointer to the result data instead of taking a pointer
|
|
// to be filled in.)
|
|
func ordercopyexpr(n *Node, t *Type, order *Order, clear int) *Node {
|
|
var_ := ordertemp(t, order, clear != 0)
|
|
a := Nod(OAS, var_, n)
|
|
typecheck(&a, Etop)
|
|
order.out = append(order.out, a)
|
|
return var_
|
|
}
|
|
|
|
// Ordercheapexpr returns a cheap version of n.
|
|
// The definition of cheap is that n is a variable or constant.
|
|
// If not, ordercheapexpr allocates a new tmp, emits tmp = n,
|
|
// and then returns tmp.
|
|
func ordercheapexpr(n *Node, order *Order) *Node {
|
|
if n == nil {
|
|
return nil
|
|
}
|
|
switch n.Op {
|
|
case ONAME, OLITERAL:
|
|
return n
|
|
case OLEN, OCAP:
|
|
l := ordercheapexpr(n.Left, order)
|
|
if l == n.Left {
|
|
return n
|
|
}
|
|
a := Nod(OXXX, nil, nil)
|
|
*a = *n
|
|
a.Orig = a
|
|
a.Left = l
|
|
typecheck(&a, Erv)
|
|
return a
|
|
}
|
|
|
|
return ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
// Ordersafeexpr returns a safe version of n.
|
|
// The definition of safe is that n can appear multiple times
|
|
// without violating the semantics of the original program,
|
|
// and that assigning to the safe version has the same effect
|
|
// as assigning to the original n.
|
|
//
|
|
// The intended use is to apply to x when rewriting x += y into x = x + y.
|
|
func ordersafeexpr(n *Node, order *Order) *Node {
|
|
switch n.Op {
|
|
case ONAME, OLITERAL:
|
|
return n
|
|
|
|
case ODOT, OLEN, OCAP:
|
|
l := ordersafeexpr(n.Left, order)
|
|
if l == n.Left {
|
|
return n
|
|
}
|
|
a := Nod(OXXX, nil, nil)
|
|
*a = *n
|
|
a.Orig = a
|
|
a.Left = l
|
|
typecheck(&a, Erv)
|
|
return a
|
|
|
|
case ODOTPTR, OIND:
|
|
l := ordercheapexpr(n.Left, order)
|
|
if l == n.Left {
|
|
return n
|
|
}
|
|
a := Nod(OXXX, nil, nil)
|
|
*a = *n
|
|
a.Orig = a
|
|
a.Left = l
|
|
typecheck(&a, Erv)
|
|
return a
|
|
|
|
case OINDEX, OINDEXMAP:
|
|
var l *Node
|
|
if Isfixedarray(n.Left.Type) {
|
|
l = ordersafeexpr(n.Left, order)
|
|
} else {
|
|
l = ordercheapexpr(n.Left, order)
|
|
}
|
|
r := ordercheapexpr(n.Right, order)
|
|
if l == n.Left && r == n.Right {
|
|
return n
|
|
}
|
|
a := Nod(OXXX, nil, nil)
|
|
*a = *n
|
|
a.Orig = a
|
|
a.Left = l
|
|
a.Right = r
|
|
typecheck(&a, Erv)
|
|
return a
|
|
}
|
|
|
|
Fatalf("ordersafeexpr %v", Oconv(int(n.Op), 0))
|
|
return nil // not reached
|
|
}
|
|
|
|
// Istemp reports whether n is a temporary variable.
|
|
func istemp(n *Node) bool {
|
|
if n.Op != ONAME {
|
|
return false
|
|
}
|
|
return strings.HasPrefix(n.Sym.Name, "autotmp_")
|
|
}
|
|
|
|
// Isaddrokay reports whether it is okay to pass n's address to runtime routines.
|
|
// Taking the address of a variable makes the liveness and optimization analyses
|
|
// lose track of where the variable's lifetime ends. To avoid hurting the analyses
|
|
// of ordinary stack variables, those are not 'isaddrokay'. Temporaries are okay,
|
|
// because we emit explicit VARKILL instructions marking the end of those
|
|
// temporaries' lifetimes.
|
|
func isaddrokay(n *Node) bool {
|
|
return islvalue(n) && (n.Op != ONAME || n.Class == PEXTERN || istemp(n))
|
|
}
|
|
|
|
// Orderaddrtemp ensures that *np is okay to pass by address to runtime routines.
|
|
// If the original argument *np is not okay, orderaddrtemp creates a tmp, emits
|
|
// tmp = *np, and then sets *np to the tmp variable.
|
|
func orderaddrtemp(np **Node, order *Order) {
|
|
n := *np
|
|
if isaddrokay(n) {
|
|
return
|
|
}
|
|
*np = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
type ordermarker int
|
|
|
|
// Marktemp returns the top of the temporary variable stack.
|
|
func marktemp(order *Order) ordermarker {
|
|
return ordermarker(len(order.temp))
|
|
}
|
|
|
|
// Poptemp pops temporaries off the stack until reaching the mark,
|
|
// which must have been returned by marktemp.
|
|
func poptemp(mark ordermarker, order *Order) {
|
|
order.temp = order.temp[:mark]
|
|
}
|
|
|
|
// Cleantempnopop emits to *out VARKILL instructions for each temporary
|
|
// above the mark on the temporary stack, but it does not pop them
|
|
// from the stack.
|
|
func cleantempnopop(mark ordermarker, order *Order, out *[]*Node) {
|
|
var kill *Node
|
|
|
|
for i := len(order.temp) - 1; i >= int(mark); i-- {
|
|
n := order.temp[i]
|
|
if n.Name.Keepalive {
|
|
n.Name.Keepalive = false
|
|
n.Addrtaken = true // ensure SSA keeps the n variable
|
|
kill = Nod(OVARLIVE, n, nil)
|
|
typecheck(&kill, Etop)
|
|
*out = append(*out, kill)
|
|
}
|
|
kill = Nod(OVARKILL, n, nil)
|
|
typecheck(&kill, Etop)
|
|
*out = append(*out, kill)
|
|
}
|
|
}
|
|
|
|
// Cleantemp emits VARKILL instructions for each temporary above the
|
|
// mark on the temporary stack and removes them from the stack.
|
|
func cleantemp(top ordermarker, order *Order) {
|
|
cleantempnopop(top, order, &order.out)
|
|
poptemp(top, order)
|
|
}
|
|
|
|
// Orderstmtlist orders each of the statements in the list.
|
|
func orderstmtlist(l nodesOrNodeList, order *Order) {
|
|
for it := nodeSeqIterate(l); !it.Done(); it.Next() {
|
|
orderstmt(it.N(), order)
|
|
}
|
|
}
|
|
|
|
// Orderblock orders the block of statements *l onto a new list,
|
|
// and then replaces *l with that list.
|
|
func orderblock(l **NodeList) {
|
|
var order Order
|
|
mark := marktemp(&order)
|
|
orderstmtlist(*l, &order)
|
|
cleantemp(mark, &order)
|
|
var ll *NodeList
|
|
for _, n := range order.out {
|
|
ll = list(ll, n)
|
|
}
|
|
*l = ll
|
|
}
|
|
|
|
// OrderblockNodes orders the block of statements in n into a new slice,
|
|
// and then replaces the old slice in n with the new slice.
|
|
func orderblockNodes(n *Nodes) {
|
|
var order Order
|
|
mark := marktemp(&order)
|
|
orderstmtlist(n.Slice(), &order)
|
|
cleantemp(mark, &order)
|
|
n.Set(order.out)
|
|
}
|
|
|
|
// Orderexprinplace orders the side effects in *np and
|
|
// leaves them as the init list of the final *np.
|
|
func orderexprinplace(np **Node, outer *Order) {
|
|
n := *np
|
|
var order Order
|
|
orderexpr(&n, &order, nil)
|
|
addinit(&n, order.out)
|
|
|
|
// insert new temporaries from order
|
|
// at head of outer list.
|
|
outer.temp = append(outer.temp, order.temp...)
|
|
|
|
*np = n
|
|
}
|
|
|
|
// Orderstmtinplace orders the side effects of the single statement *np
|
|
// and replaces it with the resulting statement list.
|
|
func orderstmtinplace(np **Node) {
|
|
n := *np
|
|
var order Order
|
|
mark := marktemp(&order)
|
|
orderstmt(n, &order)
|
|
cleantemp(mark, &order)
|
|
*np = liststmt(order.out)
|
|
}
|
|
|
|
// Orderinit moves n's init list to order->out.
|
|
func orderinit(n *Node, order *Order) {
|
|
orderstmtlist(n.Ninit, order)
|
|
setNodeSeq(&n.Ninit, nil)
|
|
}
|
|
|
|
// Ismulticall reports whether the list l is f() for a multi-value function.
|
|
// Such an f() could appear as the lone argument to a multi-arg function.
|
|
func ismulticall(l *NodeList) bool {
|
|
// one arg only
|
|
if l == nil || l.Next != nil {
|
|
return false
|
|
}
|
|
n := l.N
|
|
|
|
// must be call
|
|
switch n.Op {
|
|
default:
|
|
return false
|
|
|
|
case OCALLFUNC, OCALLMETH, OCALLINTER:
|
|
break
|
|
}
|
|
|
|
// call must return multiple values
|
|
return n.Left.Type.Outtuple > 1
|
|
}
|
|
|
|
// Copyret emits t1, t2, ... = n, where n is a function call,
|
|
// and then returns the list t1, t2, ....
|
|
func copyret(n *Node, order *Order) *NodeList {
|
|
if n.Type.Etype != TSTRUCT || !n.Type.Funarg {
|
|
Fatalf("copyret %v %d", n.Type, n.Left.Type.Outtuple)
|
|
}
|
|
|
|
var l1 *NodeList
|
|
var l2 *NodeList
|
|
var tl Iter
|
|
var tmp *Node
|
|
for t := Structfirst(&tl, &n.Type); t != nil; t = structnext(&tl) {
|
|
tmp = temp(t.Type)
|
|
l1 = list(l1, tmp)
|
|
l2 = list(l2, tmp)
|
|
}
|
|
|
|
as := Nod(OAS2, nil, nil)
|
|
setNodeSeq(&as.List, l1)
|
|
setNodeSeq(&as.Rlist, list1(n))
|
|
typecheck(&as, Etop)
|
|
orderstmt(as, order)
|
|
|
|
return l2
|
|
}
|
|
|
|
// Ordercallargs orders the list of call arguments *l.
|
|
func ordercallargs(l **NodeList, order *Order) {
|
|
if ismulticall(*l) {
|
|
// return f() where f() is multiple values.
|
|
*l = copyret((*l).N, order)
|
|
} else {
|
|
orderexprlist(*l, order)
|
|
}
|
|
}
|
|
|
|
// Ordercall orders the call expression n.
|
|
// n->op is OCALLMETH/OCALLFUNC/OCALLINTER or a builtin like OCOPY.
|
|
func ordercall(n *Node, order *Order) {
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil) // ODDDARG temp
|
|
ordercallargs(&n.List, order)
|
|
|
|
if n.Op == OCALLFUNC {
|
|
t := getinargx(n.Left.Type).Type
|
|
for it := nodeSeqIterate(n.List); !it.Done() && t != nil; it.Next() {
|
|
// Check for "unsafe-uintptr" tag provided by escape analysis.
|
|
// If present and the argument is really a pointer being converted
|
|
// to uintptr, arrange for the pointer to be kept alive until the call
|
|
// returns, by copying it into a temp and marking that temp
|
|
// still alive when we pop the temp stack.
|
|
if t.Note != nil && *t.Note == unsafeUintptrTag {
|
|
xp := it.P()
|
|
for (*xp).Op == OCONVNOP && !Isptr[(*xp).Type.Etype] {
|
|
xp = &(*xp).Left
|
|
}
|
|
x := *xp
|
|
if Isptr[x.Type.Etype] {
|
|
x = ordercopyexpr(x, x.Type, order, 0)
|
|
x.Name.Keepalive = true
|
|
*xp = x
|
|
}
|
|
}
|
|
t = t.Down
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ordermapassign appends n to order->out, introducing temporaries
|
|
// to make sure that all map assignments have the form m[k] = x,
|
|
// where x is addressable.
|
|
// (Orderexpr has already been called on n, so we know k is addressable.)
|
|
//
|
|
// If n is m[k] = x where x is not addressable, the rewrite is:
|
|
// tmp = x
|
|
// m[k] = tmp
|
|
//
|
|
// If n is the multiple assignment form ..., m[k], ... = ..., the rewrite is
|
|
// t1 = m
|
|
// t2 = k
|
|
// ...., t3, ... = x
|
|
// t1[t2] = t3
|
|
//
|
|
// The temporaries t1, t2 are needed in case the ... being assigned
|
|
// contain m or k. They are usually unnecessary, but in the unnecessary
|
|
// cases they are also typically registerizable, so not much harm done.
|
|
// And this only applies to the multiple-assignment form.
|
|
// We could do a more precise analysis if needed, like in walk.go.
|
|
//
|
|
// Ordermapassign also inserts these temporaries if needed for
|
|
// calling writebarrierfat with a pointer to n->right.
|
|
func ordermapassign(n *Node, order *Order) {
|
|
switch n.Op {
|
|
default:
|
|
Fatalf("ordermapassign %v", Oconv(int(n.Op), 0))
|
|
|
|
case OAS:
|
|
order.out = append(order.out, n)
|
|
|
|
// We call writebarrierfat only for values > 4 pointers long. See walk.go.
|
|
if (n.Left.Op == OINDEXMAP || (needwritebarrier(n.Left, n.Right) && n.Left.Type.Width > int64(4*Widthptr))) && !isaddrokay(n.Right) {
|
|
m := n.Left
|
|
n.Left = ordertemp(m.Type, order, false)
|
|
a := Nod(OAS, m, n.Left)
|
|
typecheck(&a, Etop)
|
|
order.out = append(order.out, a)
|
|
}
|
|
|
|
case OAS2, OAS2DOTTYPE, OAS2MAPR, OAS2FUNC:
|
|
var post []*Node
|
|
var m *Node
|
|
var a *Node
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
if it.N().Op == OINDEXMAP {
|
|
m = it.N()
|
|
if !istemp(m.Left) {
|
|
m.Left = ordercopyexpr(m.Left, m.Left.Type, order, 0)
|
|
}
|
|
if !istemp(m.Right) {
|
|
m.Right = ordercopyexpr(m.Right, m.Right.Type, order, 0)
|
|
}
|
|
*it.P() = ordertemp(m.Type, order, false)
|
|
a = Nod(OAS, m, it.N())
|
|
typecheck(&a, Etop)
|
|
post = append(post, a)
|
|
} else if instrumenting && n.Op == OAS2FUNC && !isblank(it.N()) {
|
|
m = it.N()
|
|
*it.P() = ordertemp(m.Type, order, false)
|
|
a = Nod(OAS, m, it.N())
|
|
typecheck(&a, Etop)
|
|
post = append(post, a)
|
|
}
|
|
}
|
|
|
|
order.out = append(order.out, n)
|
|
order.out = append(order.out, post...)
|
|
}
|
|
}
|
|
|
|
// Orderstmt orders the statement n, appending to order->out.
|
|
// Temporaries created during the statement are cleaned
|
|
// up using VARKILL instructions as possible.
|
|
func orderstmt(n *Node, order *Order) {
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
lno := setlineno(n)
|
|
|
|
orderinit(n, order)
|
|
|
|
switch n.Op {
|
|
default:
|
|
Fatalf("orderstmt %v", Oconv(int(n.Op), 0))
|
|
|
|
case OVARKILL, OVARLIVE:
|
|
order.out = append(order.out, n)
|
|
|
|
case OAS:
|
|
t := marktemp(order)
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, n.Left)
|
|
ordermapassign(n, order)
|
|
cleantemp(t, order)
|
|
|
|
case OAS2,
|
|
OCLOSE,
|
|
OCOPY,
|
|
OPRINT,
|
|
OPRINTN,
|
|
ORECOVER,
|
|
ORECV:
|
|
t := marktemp(order)
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
orderexprlist(n.List, order)
|
|
orderexprlist(n.Rlist, order)
|
|
switch n.Op {
|
|
case OAS2, OAS2DOTTYPE:
|
|
ordermapassign(n, order)
|
|
default:
|
|
order.out = append(order.out, n)
|
|
}
|
|
cleantemp(t, order)
|
|
|
|
case OASOP:
|
|
// Special: rewrite l op= r into l = l op r.
|
|
// This simplifies quite a few operations;
|
|
// most important is that it lets us separate
|
|
// out map read from map write when l is
|
|
// a map index expression.
|
|
t := marktemp(order)
|
|
|
|
orderexpr(&n.Left, order, nil)
|
|
n.Left = ordersafeexpr(n.Left, order)
|
|
tmp1 := treecopy(n.Left, 0)
|
|
if tmp1.Op == OINDEXMAP {
|
|
tmp1.Etype = 0 // now an rvalue not an lvalue
|
|
}
|
|
tmp1 = ordercopyexpr(tmp1, n.Left.Type, order, 0)
|
|
// TODO(marvin): Fix Node.EType type union.
|
|
n.Right = Nod(Op(n.Etype), tmp1, n.Right)
|
|
typecheck(&n.Right, Erv)
|
|
orderexpr(&n.Right, order, nil)
|
|
n.Etype = 0
|
|
n.Op = OAS
|
|
ordermapassign(n, order)
|
|
cleantemp(t, order)
|
|
|
|
// Special: make sure key is addressable,
|
|
// and make sure OINDEXMAP is not copied out.
|
|
case OAS2MAPR:
|
|
t := marktemp(order)
|
|
|
|
orderexprlist(n.List, order)
|
|
r := nodeSeqFirst(n.Rlist)
|
|
orderexpr(&r.Left, order, nil)
|
|
orderexpr(&r.Right, order, nil)
|
|
|
|
// See case OINDEXMAP below.
|
|
if r.Right.Op == OARRAYBYTESTR {
|
|
r.Right.Op = OARRAYBYTESTRTMP
|
|
}
|
|
orderaddrtemp(&r.Right, order)
|
|
ordermapassign(n, order)
|
|
cleantemp(t, order)
|
|
|
|
// Special: avoid copy of func call n->rlist->n.
|
|
case OAS2FUNC:
|
|
t := marktemp(order)
|
|
|
|
orderexprlist(n.List, order)
|
|
ordercall(nodeSeqFirst(n.Rlist), order)
|
|
ordermapassign(n, order)
|
|
cleantemp(t, order)
|
|
|
|
// Special: use temporary variables to hold result,
|
|
// so that assertI2Tetc can take address of temporary.
|
|
// No temporary for blank assignment.
|
|
case OAS2DOTTYPE:
|
|
t := marktemp(order)
|
|
|
|
orderexprlist(n.List, order)
|
|
orderexpr(&nodeSeqFirst(n.Rlist).Left, order, nil) // i in i.(T)
|
|
if isblank(nodeSeqFirst(n.List)) {
|
|
order.out = append(order.out, n)
|
|
} else {
|
|
typ := nodeSeqFirst(n.Rlist).Type
|
|
tmp1 := ordertemp(typ, order, haspointers(typ))
|
|
order.out = append(order.out, n)
|
|
r := Nod(OAS, nodeSeqFirst(n.List), tmp1)
|
|
typecheck(&r, Etop)
|
|
ordermapassign(r, order)
|
|
setNodeSeq(&n.List, []*Node{tmp1, nodeSeqSecond(n.List)})
|
|
}
|
|
|
|
cleantemp(t, order)
|
|
|
|
// Special: use temporary variables to hold result,
|
|
// so that chanrecv can take address of temporary.
|
|
case OAS2RECV:
|
|
t := marktemp(order)
|
|
|
|
orderexprlist(n.List, order)
|
|
orderexpr(&nodeSeqFirst(n.Rlist).Left, order, nil) // arg to recv
|
|
ch := nodeSeqFirst(n.Rlist).Left.Type
|
|
tmp1 := ordertemp(ch.Type, order, haspointers(ch.Type))
|
|
var tmp2 *Node
|
|
if !isblank(nodeSeqSecond(n.List)) {
|
|
tmp2 = ordertemp(nodeSeqSecond(n.List).Type, order, false)
|
|
} else {
|
|
tmp2 = ordertemp(Types[TBOOL], order, false)
|
|
}
|
|
order.out = append(order.out, n)
|
|
r := Nod(OAS, nodeSeqFirst(n.List), tmp1)
|
|
typecheck(&r, Etop)
|
|
ordermapassign(r, order)
|
|
r = Nod(OAS, nodeSeqSecond(n.List), tmp2)
|
|
typecheck(&r, Etop)
|
|
ordermapassign(r, order)
|
|
setNodeSeq(&n.List, []*Node{tmp1, tmp2})
|
|
cleantemp(t, order)
|
|
|
|
// Special: does not save n onto out.
|
|
case OBLOCK, OEMPTY:
|
|
orderstmtlist(n.List, order)
|
|
|
|
// Special: n->left is not an expression; save as is.
|
|
case OBREAK,
|
|
OCONTINUE,
|
|
ODCL,
|
|
ODCLCONST,
|
|
ODCLTYPE,
|
|
OFALL,
|
|
OXFALL,
|
|
OGOTO,
|
|
OLABEL,
|
|
ORETJMP:
|
|
order.out = append(order.out, n)
|
|
|
|
// Special: handle call arguments.
|
|
case OCALLFUNC, OCALLINTER, OCALLMETH:
|
|
t := marktemp(order)
|
|
|
|
ordercall(n, order)
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
// Special: order arguments to inner call but not call itself.
|
|
case ODEFER, OPROC:
|
|
t := marktemp(order)
|
|
|
|
switch n.Left.Op {
|
|
// Delete will take the address of the key.
|
|
// Copy key into new temp and do not clean it
|
|
// (it persists beyond the statement).
|
|
case ODELETE:
|
|
orderexprlist(n.Left.List, order)
|
|
|
|
t1 := marktemp(order)
|
|
it := nodeSeqIterate(n.Left.List)
|
|
it.Next()
|
|
np := it.P() // map key
|
|
*np = ordercopyexpr(*np, (*np).Type, order, 0)
|
|
poptemp(t1, order)
|
|
|
|
default:
|
|
ordercall(n.Left, order)
|
|
}
|
|
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
case ODELETE:
|
|
t := marktemp(order)
|
|
it := nodeSeqIterate(n.List)
|
|
orderexpr(it.P(), order, nil)
|
|
it.Next()
|
|
orderexpr(it.P(), order, nil)
|
|
orderaddrtemp(it.P(), order) // map key
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
// Clean temporaries from condition evaluation at
|
|
// beginning of loop body and after for statement.
|
|
case OFOR:
|
|
t := marktemp(order)
|
|
|
|
orderexprinplace(&n.Left, order)
|
|
var l []*Node
|
|
cleantempnopop(t, order, &l)
|
|
n.Nbody.Set(append(l, n.Nbody.Slice()...))
|
|
orderblockNodes(&n.Nbody)
|
|
orderstmtinplace(&n.Right)
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
// Clean temporaries from condition at
|
|
// beginning of both branches.
|
|
case OIF:
|
|
t := marktemp(order)
|
|
|
|
orderexprinplace(&n.Left, order)
|
|
var l []*Node
|
|
cleantempnopop(t, order, &l)
|
|
n.Nbody.Set(append(l, n.Nbody.Slice()...))
|
|
l = nil
|
|
cleantempnopop(t, order, &l)
|
|
setNodeSeq(&n.Rlist, append(l, nodeSeqSlice(n.Rlist)...))
|
|
poptemp(t, order)
|
|
orderblockNodes(&n.Nbody)
|
|
orderblock(&n.Rlist)
|
|
order.out = append(order.out, n)
|
|
|
|
// Special: argument will be converted to interface using convT2E
|
|
// so make sure it is an addressable temporary.
|
|
case OPANIC:
|
|
t := marktemp(order)
|
|
|
|
orderexpr(&n.Left, order, nil)
|
|
if !Isinter(n.Left.Type) {
|
|
orderaddrtemp(&n.Left, order)
|
|
}
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
// n->right is the expression being ranged over.
|
|
// order it, and then make a copy if we need one.
|
|
// We almost always do, to ensure that we don't
|
|
// see any value changes made during the loop.
|
|
// Usually the copy is cheap (e.g., array pointer, chan, slice, string are all tiny).
|
|
// The exception is ranging over an array value (not a slice, not a pointer to array),
|
|
// which must make a copy to avoid seeing updates made during
|
|
// the range body. Ranging over an array value is uncommon though.
|
|
case ORANGE:
|
|
t := marktemp(order)
|
|
|
|
orderexpr(&n.Right, order, nil)
|
|
switch n.Type.Etype {
|
|
default:
|
|
Fatalf("orderstmt range %v", n.Type)
|
|
|
|
// Mark []byte(str) range expression to reuse string backing storage.
|
|
// It is safe because the storage cannot be mutated.
|
|
case TARRAY:
|
|
if n.Right.Op == OSTRARRAYBYTE {
|
|
n.Right.Op = OSTRARRAYBYTETMP
|
|
}
|
|
if nodeSeqLen(n.List) < 2 || isblank(nodeSeqSecond(n.List)) {
|
|
// for i := range x will only use x once, to compute len(x).
|
|
// No need to copy it.
|
|
break
|
|
}
|
|
fallthrough
|
|
|
|
// chan, string, slice, array ranges use value multiple times.
|
|
// make copy.
|
|
// fall through
|
|
case TCHAN, TSTRING:
|
|
r := n.Right
|
|
|
|
if r.Type.Etype == TSTRING && r.Type != Types[TSTRING] {
|
|
r = Nod(OCONV, r, nil)
|
|
r.Type = Types[TSTRING]
|
|
typecheck(&r, Erv)
|
|
}
|
|
|
|
n.Right = ordercopyexpr(r, r.Type, order, 0)
|
|
|
|
// copy the map value in case it is a map literal.
|
|
// TODO(rsc): Make tmp = literal expressions reuse tmp.
|
|
// For maps tmp is just one word so it hardly matters.
|
|
case TMAP:
|
|
r := n.Right
|
|
|
|
n.Right = ordercopyexpr(r, r.Type, order, 0)
|
|
|
|
// n->alloc is the temp for the iterator.
|
|
prealloc[n] = ordertemp(Types[TUINT8], order, true)
|
|
}
|
|
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
orderexprinplace(it.P(), order)
|
|
}
|
|
orderblockNodes(&n.Nbody)
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
case ORETURN:
|
|
ordercallargs(&n.List, order)
|
|
order.out = append(order.out, n)
|
|
|
|
// Special: clean case temporaries in each block entry.
|
|
// Select must enter one of its blocks, so there is no
|
|
// need for a cleaning at the end.
|
|
// Doubly special: evaluation order for select is stricter
|
|
// than ordinary expressions. Even something like p.c
|
|
// has to be hoisted into a temporary, so that it cannot be
|
|
// reordered after the channel evaluation for a different
|
|
// case (if p were nil, then the timing of the fault would
|
|
// give this away).
|
|
case OSELECT:
|
|
t := marktemp(order)
|
|
|
|
var tmp1 *Node
|
|
var tmp2 *Node
|
|
var r *Node
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
if it.N().Op != OXCASE {
|
|
Fatalf("order select case %v", Oconv(int(it.N().Op), 0))
|
|
}
|
|
r = it.N().Left
|
|
setlineno(it.N())
|
|
|
|
// Append any new body prologue to ninit.
|
|
// The next loop will insert ninit into nbody.
|
|
if nodeSeqLen(it.N().Ninit) != 0 {
|
|
Fatalf("order select ninit")
|
|
}
|
|
if r != nil {
|
|
switch r.Op {
|
|
default:
|
|
Yyerror("unknown op in select %v", Oconv(int(r.Op), 0))
|
|
Dump("select case", r)
|
|
|
|
// If this is case x := <-ch or case x, y := <-ch, the case has
|
|
// the ODCL nodes to declare x and y. We want to delay that
|
|
// declaration (and possible allocation) until inside the case body.
|
|
// Delete the ODCL nodes here and recreate them inside the body below.
|
|
case OSELRECV, OSELRECV2:
|
|
if r.Colas {
|
|
itinit := nodeSeqIterate(r.Ninit)
|
|
if itinit.Len() != 0 && itinit.N().Op == ODCL && itinit.N().Left == r.Left {
|
|
itinit.Next()
|
|
}
|
|
if itinit.Len() != 0 && itinit.N().Op == ODCL && nodeSeqLen(r.List) != 0 && itinit.N().Left == nodeSeqFirst(r.List) {
|
|
itinit.Next()
|
|
}
|
|
if itinit.Done() {
|
|
setNodeSeq(&r.Ninit, nil)
|
|
}
|
|
}
|
|
|
|
if nodeSeqLen(r.Ninit) != 0 {
|
|
Yyerror("ninit on select recv")
|
|
dumplist("ninit", r.Ninit)
|
|
}
|
|
|
|
// case x = <-c
|
|
// case x, ok = <-c
|
|
// r->left is x, r->ntest is ok, r->right is ORECV, r->right->left is c.
|
|
// r->left == N means 'case <-c'.
|
|
// c is always evaluated; x and ok are only evaluated when assigned.
|
|
orderexpr(&r.Right.Left, order, nil)
|
|
|
|
if r.Right.Left.Op != ONAME {
|
|
r.Right.Left = ordercopyexpr(r.Right.Left, r.Right.Left.Type, order, 0)
|
|
}
|
|
|
|
// Introduce temporary for receive and move actual copy into case body.
|
|
// avoids problems with target being addressed, as usual.
|
|
// NOTE: If we wanted to be clever, we could arrange for just one
|
|
// temporary per distinct type, sharing the temp among all receives
|
|
// with that temp. Similarly one ok bool could be shared among all
|
|
// the x,ok receives. Not worth doing until there's a clear need.
|
|
if r.Left != nil && isblank(r.Left) {
|
|
r.Left = nil
|
|
}
|
|
if r.Left != nil {
|
|
// use channel element type for temporary to avoid conversions,
|
|
// such as in case interfacevalue = <-intchan.
|
|
// the conversion happens in the OAS instead.
|
|
tmp1 = r.Left
|
|
|
|
if r.Colas {
|
|
tmp2 = Nod(ODCL, tmp1, nil)
|
|
typecheck(&tmp2, Etop)
|
|
appendNodeSeqNode(&it.N().Ninit, tmp2)
|
|
}
|
|
|
|
r.Left = ordertemp(r.Right.Left.Type.Type, order, haspointers(r.Right.Left.Type.Type))
|
|
tmp2 = Nod(OAS, tmp1, r.Left)
|
|
typecheck(&tmp2, Etop)
|
|
appendNodeSeqNode(&it.N().Ninit, tmp2)
|
|
}
|
|
|
|
if nodeSeqLen(r.List) != 0 && isblank(nodeSeqFirst(r.List)) {
|
|
setNodeSeq(&r.List, nil)
|
|
}
|
|
if nodeSeqLen(r.List) != 0 {
|
|
tmp1 = nodeSeqFirst(r.List)
|
|
if r.Colas {
|
|
tmp2 = Nod(ODCL, tmp1, nil)
|
|
typecheck(&tmp2, Etop)
|
|
appendNodeSeqNode(&it.N().Ninit, tmp2)
|
|
}
|
|
|
|
setNodeSeq(&r.List, list1(ordertemp(tmp1.Type, order, false)))
|
|
tmp2 = Nod(OAS, tmp1, nodeSeqFirst(r.List))
|
|
typecheck(&tmp2, Etop)
|
|
appendNodeSeqNode(&it.N().Ninit, tmp2)
|
|
}
|
|
|
|
orderblock(&it.N().Ninit)
|
|
|
|
case OSEND:
|
|
if nodeSeqLen(r.Ninit) != 0 {
|
|
Yyerror("ninit on select send")
|
|
dumplist("ninit", r.Ninit)
|
|
}
|
|
|
|
// case c <- x
|
|
// r->left is c, r->right is x, both are always evaluated.
|
|
orderexpr(&r.Left, order, nil)
|
|
|
|
if !istemp(r.Left) {
|
|
r.Left = ordercopyexpr(r.Left, r.Left.Type, order, 0)
|
|
}
|
|
orderexpr(&r.Right, order, nil)
|
|
if !istemp(r.Right) {
|
|
r.Right = ordercopyexpr(r.Right, r.Right.Type, order, 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
orderblockNodes(&it.N().Nbody)
|
|
}
|
|
|
|
// Now that we have accumulated all the temporaries, clean them.
|
|
// Also insert any ninit queued during the previous loop.
|
|
// (The temporary cleaning must follow that ninit work.)
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
s := nodeSeqSlice(it.N().Ninit)
|
|
cleantempnopop(t, order, &s)
|
|
it.N().Nbody.Set(append(s, it.N().Nbody.Slice()...))
|
|
setNodeSeq(&it.N().Ninit, nil)
|
|
}
|
|
|
|
order.out = append(order.out, n)
|
|
poptemp(t, order)
|
|
|
|
// Special: value being sent is passed as a pointer; make it addressable.
|
|
case OSEND:
|
|
t := marktemp(order)
|
|
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
orderaddrtemp(&n.Right, order)
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
|
|
// TODO(rsc): Clean temporaries more aggressively.
|
|
// Note that because walkswitch will rewrite some of the
|
|
// switch into a binary search, this is not as easy as it looks.
|
|
// (If we ran that code here we could invoke orderstmt on
|
|
// the if-else chain instead.)
|
|
// For now just clean all the temporaries at the end.
|
|
// In practice that's fine.
|
|
case OSWITCH:
|
|
t := marktemp(order)
|
|
|
|
orderexpr(&n.Left, order, nil)
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
if it.N().Op != OXCASE {
|
|
Fatalf("order switch case %v", Oconv(int(it.N().Op), 0))
|
|
}
|
|
orderexprlistinplace(it.N().List, order)
|
|
orderblockNodes(&it.N().Nbody)
|
|
}
|
|
|
|
order.out = append(order.out, n)
|
|
cleantemp(t, order)
|
|
}
|
|
|
|
lineno = lno
|
|
}
|
|
|
|
// Orderexprlist orders the expression list l into order.
|
|
func orderexprlist(l *NodeList, order *Order) {
|
|
for ; l != nil; l = l.Next {
|
|
orderexpr(&l.N, order, nil)
|
|
}
|
|
}
|
|
|
|
// Orderexprlist orders the expression list l but saves
|
|
// the side effects on the individual expression ninit lists.
|
|
func orderexprlistinplace(l *NodeList, order *Order) {
|
|
for ; l != nil; l = l.Next {
|
|
orderexprinplace(&l.N, order)
|
|
}
|
|
}
|
|
|
|
// prealloc[x] records the allocation to use for x.
|
|
var prealloc = map[*Node]*Node{}
|
|
|
|
// Orderexpr orders a single expression, appending side
|
|
// effects to order->out as needed.
|
|
// If this is part of an assignment lhs = *np, lhs is given.
|
|
// Otherwise lhs == nil. (When lhs != nil it may be possible
|
|
// to avoid copying the result of the expression to a temporary.)
|
|
func orderexpr(np **Node, order *Order, lhs *Node) {
|
|
n := *np
|
|
if n == nil {
|
|
return
|
|
}
|
|
|
|
lno := setlineno(n)
|
|
orderinit(n, order)
|
|
|
|
switch n.Op {
|
|
default:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
orderexprlist(n.List, order)
|
|
orderexprlist(n.Rlist, order)
|
|
|
|
// Addition of strings turns into a function call.
|
|
// Allocate a temporary to hold the strings.
|
|
// Fewer than 5 strings use direct runtime helpers.
|
|
case OADDSTR:
|
|
orderexprlist(n.List, order)
|
|
|
|
if nodeSeqLen(n.List) > 5 {
|
|
t := typ(TARRAY)
|
|
t.Bound = int64(nodeSeqLen(n.List))
|
|
t.Type = Types[TSTRING]
|
|
prealloc[n] = ordertemp(t, order, false)
|
|
}
|
|
|
|
// Mark string(byteSlice) arguments to reuse byteSlice backing
|
|
// buffer during conversion. String concatenation does not
|
|
// memorize the strings for later use, so it is safe.
|
|
// However, we can do it only if there is at least one non-empty string literal.
|
|
// Otherwise if all other arguments are empty strings,
|
|
// concatstrings will return the reference to the temp string
|
|
// to the caller.
|
|
hasbyte := false
|
|
|
|
haslit := false
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
hasbyte = hasbyte || it.N().Op == OARRAYBYTESTR
|
|
haslit = haslit || it.N().Op == OLITERAL && len(it.N().Val().U.(string)) != 0
|
|
}
|
|
|
|
if haslit && hasbyte {
|
|
for it := nodeSeqIterate(n.List); !it.Done(); it.Next() {
|
|
if it.N().Op == OARRAYBYTESTR {
|
|
it.N().Op = OARRAYBYTESTRTMP
|
|
}
|
|
}
|
|
}
|
|
|
|
case OCMPSTR:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
|
|
// Mark string(byteSlice) arguments to reuse byteSlice backing
|
|
// buffer during conversion. String comparison does not
|
|
// memorize the strings for later use, so it is safe.
|
|
if n.Left.Op == OARRAYBYTESTR {
|
|
n.Left.Op = OARRAYBYTESTRTMP
|
|
}
|
|
if n.Right.Op == OARRAYBYTESTR {
|
|
n.Right.Op = OARRAYBYTESTRTMP
|
|
}
|
|
|
|
// key must be addressable
|
|
case OINDEXMAP:
|
|
orderexpr(&n.Left, order, nil)
|
|
|
|
orderexpr(&n.Right, order, nil)
|
|
|
|
// For x = m[string(k)] where k is []byte, the allocation of
|
|
// backing bytes for the string can be avoided by reusing
|
|
// the []byte backing array. This is a special case that it
|
|
// would be nice to handle more generally, but because
|
|
// there are no []byte-keyed maps, this specific case comes
|
|
// up in important cases in practice. See issue 3512.
|
|
// Nothing can change the []byte we are not copying before
|
|
// the map index, because the map access is going to
|
|
// be forced to happen immediately following this
|
|
// conversion (by the ordercopyexpr a few lines below).
|
|
if n.Etype == 0 && n.Right.Op == OARRAYBYTESTR {
|
|
n.Right.Op = OARRAYBYTESTRTMP
|
|
}
|
|
|
|
orderaddrtemp(&n.Right, order)
|
|
if n.Etype == 0 {
|
|
// use of value (not being assigned);
|
|
// make copy in temporary.
|
|
n = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
// concrete type (not interface) argument must be addressable
|
|
// temporary to pass to runtime.
|
|
case OCONVIFACE:
|
|
orderexpr(&n.Left, order, nil)
|
|
|
|
if !Isinter(n.Left.Type) {
|
|
orderaddrtemp(&n.Left, order)
|
|
}
|
|
|
|
case OANDAND, OOROR:
|
|
mark := marktemp(order)
|
|
orderexpr(&n.Left, order, nil)
|
|
|
|
// Clean temporaries from first branch at beginning of second.
|
|
// Leave them on the stack so that they can be killed in the outer
|
|
// context in case the short circuit is taken.
|
|
var s []*Node
|
|
|
|
cleantempnopop(mark, order, &s)
|
|
setNodeSeq(&n.Right.Ninit, append(s, nodeSeqSlice(n.Right.Ninit)...))
|
|
orderexprinplace(&n.Right, order)
|
|
|
|
case OCALLFUNC,
|
|
OCALLINTER,
|
|
OCALLMETH,
|
|
OCAP,
|
|
OCOMPLEX,
|
|
OCOPY,
|
|
OIMAG,
|
|
OLEN,
|
|
OMAKECHAN,
|
|
OMAKEMAP,
|
|
OMAKESLICE,
|
|
ONEW,
|
|
OREAL,
|
|
ORECOVER,
|
|
OSTRARRAYBYTE,
|
|
OSTRARRAYBYTETMP,
|
|
OSTRARRAYRUNE:
|
|
ordercall(n, order)
|
|
if lhs == nil || lhs.Op != ONAME || instrumenting {
|
|
n = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
case OAPPEND:
|
|
ordercallargs(&n.List, order)
|
|
if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, nodeSeqFirst(n.List)) {
|
|
n = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
case OSLICE, OSLICEARR, OSLICESTR:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right.Left, order, nil)
|
|
n.Right.Left = ordercheapexpr(n.Right.Left, order)
|
|
orderexpr(&n.Right.Right, order, nil)
|
|
n.Right.Right = ordercheapexpr(n.Right.Right, order)
|
|
if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, n.Left) {
|
|
n = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
case OSLICE3, OSLICE3ARR:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right.Left, order, nil)
|
|
n.Right.Left = ordercheapexpr(n.Right.Left, order)
|
|
orderexpr(&n.Right.Right.Left, order, nil)
|
|
n.Right.Right.Left = ordercheapexpr(n.Right.Right.Left, order)
|
|
orderexpr(&n.Right.Right.Right, order, nil)
|
|
n.Right.Right.Right = ordercheapexpr(n.Right.Right.Right, order)
|
|
if lhs == nil || lhs.Op != ONAME && !samesafeexpr(lhs, n.Left) {
|
|
n = ordercopyexpr(n, n.Type, order, 0)
|
|
}
|
|
|
|
case OCLOSURE:
|
|
if n.Noescape && len(n.Func.Cvars.Slice()) > 0 {
|
|
prealloc[n] = ordertemp(Types[TUINT8], order, false) // walk will fill in correct type
|
|
}
|
|
|
|
case OARRAYLIT, OCALLPART:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
orderexprlist(n.List, order)
|
|
orderexprlist(n.Rlist, order)
|
|
if n.Noescape {
|
|
prealloc[n] = ordertemp(Types[TUINT8], order, false) // walk will fill in correct type
|
|
}
|
|
|
|
case ODDDARG:
|
|
if n.Noescape {
|
|
// The ddd argument does not live beyond the call it is created for.
|
|
// Allocate a temporary that will be cleaned up when this statement
|
|
// completes. We could be more aggressive and try to arrange for it
|
|
// to be cleaned up when the call completes.
|
|
prealloc[n] = ordertemp(n.Type.Type, order, false)
|
|
}
|
|
|
|
case ODOTTYPE, ODOTTYPE2:
|
|
orderexpr(&n.Left, order, nil)
|
|
// TODO(rsc): The Isfat is for consistency with componentgen and walkexpr.
|
|
// It needs to be removed in all three places.
|
|
// That would allow inlining x.(struct{*int}) the same as x.(*int).
|
|
if !isdirectiface(n.Type) || Isfat(n.Type) || instrumenting {
|
|
n = ordercopyexpr(n, n.Type, order, 1)
|
|
}
|
|
|
|
case ORECV:
|
|
orderexpr(&n.Left, order, nil)
|
|
n = ordercopyexpr(n, n.Type, order, 1)
|
|
|
|
case OEQ, ONE:
|
|
orderexpr(&n.Left, order, nil)
|
|
orderexpr(&n.Right, order, nil)
|
|
t := n.Left.Type
|
|
if t.Etype == TSTRUCT || Isfixedarray(t) {
|
|
// for complex comparisons, we need both args to be
|
|
// addressable so we can pass them to the runtime.
|
|
orderaddrtemp(&n.Left, order)
|
|
orderaddrtemp(&n.Right, order)
|
|
}
|
|
}
|
|
|
|
lineno = lno
|
|
|
|
*np = n
|
|
}
|