}
}
+ // if we rewrite a tuple generator to a new one in a different block,
+ // copy its selectors to the new generator's block, so tuple generator
+ // and selectors stay together.
+ for _, b := range f.Blocks {
+ for _, v := range b.Values {
+ if rewrite[v.ID] != nil {
+ continue
+ }
+ if !v.Op.isTupleSelector() {
+ continue
+ }
+ if !v.Args[0].Op.isTupleGenerator() {
+ f.Fatalf("arg of tuple selector %s is not a tuple: %s", v.String(), v.Args[0].LongString())
+ }
+ t := rewrite[v.Args[0].ID]
+ if t != nil && t.Block != b {
+ // v.Args[0] is tuple generator, CSE'd into a different block as t, v is left behind
+ c := v.copyInto(t.Block)
+ rewrite[v.ID] = c
+ }
+ }
+ }
+
rewrites := int64(0)
// Apply substitutions
}
return makeValAndOff(x.Val(), x.Off()+off)
}
+
+func (op Op) isTupleGenerator() bool {
+ switch op {
+ case OpAdd32carry, OpSub32carry, OpMul32uhilo,
+ OpARMADDS, OpARMSUBS, OpARMMULLU:
+ return true
+ }
+ return false
+}
+
+func (op Op) isTupleSelector() bool {
+ switch op {
+ case OpSelect0, OpSelect1,
+ OpARMLoweredSelect0, OpARMLoweredSelect1, OpARMCarry:
+ return true
+ }
+ return false
+}
for i := 0; i < len(b.Values); i++ {
v := b.Values[i]
switch v.Op {
- case OpPhi, OpGetClosurePtr, OpConvert, OpArg, OpSelect0, OpSelect1:
+ case OpPhi, OpGetClosurePtr, OpConvert, OpArg:
// GetClosurePtr & Arg must stay in entry block.
// OpConvert must not float over call sites.
- // Select{0,1} reads a tuple, it must stay with the tuple-generating op.
// TODO do we instead need a dependence edge of some sort for OpConvert?
// Would memory do the trick, or do we need something else that relates
// to safe point operations?
continue
default:
}
+ if v.Op.isTupleSelector() {
+ // tuple selector must stay with tuple generator
+ continue
+ }
if len(v.Args) > 0 && v.Args[len(v.Args)-1].Type.IsMemory() {
// We can't move values which have a memory arg - it might
// make two memory values live across a block boundary.