diff --git a/aeneas/src/ir/SsaNormalizer.v3 b/aeneas/src/ir/SsaNormalizer.v3 index 22b3b3593..bf6d8d3d4 100644 --- a/aeneas/src/ir/SsaNormalizer.v3 +++ b/aeneas/src/ir/SsaNormalizer.v3 @@ -8,7 +8,12 @@ class SsaRaNormalizer extends SsaRebuilder { def norm: ReachabilityNormalizer; var specSet: SpecSet; - new(context: SsaContext, norm) super(context) { } + new(context: SsaContext, norm) super(context) { + if (context.compiler.NormBuildOpt) { + opt = SsaOptimizer.new(context); + opt.remove_pure_ops = false; // instructions have no uses yet when added + } + } def build(newMethod: IrMethod, empty: bool) { newMethod.ssa = genGraph(empty); @@ -18,7 +23,7 @@ class SsaRaNormalizer extends SsaRebuilder { if (context.compiler.NormOptimize && context.compiler.optEnabled("(ra)", newMethod, null, null)) { var opt = SsaOptimizer.new(context); - opt.iopt.optimize_loads = context.compiler.LoadOptimize && + opt.optimize_loads = context.compiler.LoadOptimize && context.compiler.optEnabled("(ra loads)", newMethod, null, null); opt.optGraph(); context.printSsa("Norm Optimized"); diff --git a/aeneas/src/mach/MachDataWriter.v3 b/aeneas/src/mach/MachDataWriter.v3 index be52d8d8c..e9c92bc40 100644 --- a/aeneas/src/mach/MachDataWriter.v3 +++ b/aeneas/src/mach/MachDataWriter.v3 @@ -8,6 +8,7 @@ enum PatchKind { ABS_32, ABS_64, ABS_SLEB32, + ABS_ULEB32, ARM64_REL_IMM19, ARM64_ABS_IMM16, ARM64_REL_IMM26, @@ -68,7 +69,7 @@ class MachDataWriter extends DataWriter { put_b32(if(wrap, int.view(disp), int.!(disp))); } ABS_32 => { - put_b32(if(wrap, int.view(u32.!(absAddr)), int.!(absAddr))); + put_b32(if(wrap, int.view(absAddr), int.!(absAddr))); } ABS_64 => { put_b64(absAddr); @@ -76,6 +77,9 @@ class MachDataWriter extends DataWriter { ABS_SLEB32 => { overwrite_sleb32(int.!(absAddr)); } + ABS_ULEB32 => { + overwrite_uleb32(int.view(absAddr)); + } ARM64_REL_IMM19 => { var offset = int.!((absAddr - posAddr) / 4); var old = int.view(data[pos]) | (int.view(data[pos + 1]) << 8) | diff --git a/aeneas/src/mach/MachLowering.v3 b/aeneas/src/mach/MachLowering.v3 index 57ad44253..529bf240b 100644 --- a/aeneas/src/mach/MachLowering.v3 +++ b/aeneas/src/mach/MachLowering.v3 @@ -73,9 +73,9 @@ class MachLoweringConfig { class SsaGraphNormalizer(context: SsaContext) extends SsaInstrVisitor { def buffer = Vector.new(); // reusable input buffer def replacements = Vector>.new(); - var cfopt: SsaCfOptimizer; var singleMark: int; var ssa: SsaBuilder; + var opt: SsaOptimizer; // if non-null, optimizes instructions as they are added var phis: List; // Map {i_old} to multiple instructions in {i_new}. @@ -133,6 +133,7 @@ class SsaGraphNormalizer(context: SsaContext) extends SsaInstrVisitor { replacements.clear(); buffer.clear(); ssa = SsaBuilder.new(context, graph, null); + ssa.opt = opt; } // Get the replacements for a given instruction, if it is not a 1-1 mapping. // Returns {null} if there is a 1-1 mapping. @@ -280,7 +281,12 @@ class SsaGraphNormalizer(context: SsaContext) extends SsaInstrVisitor { // with loads and stores. Introduces truncations and wrapping for integer operations // according to the {config}. class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringConfig) extends SsaGraphNormalizer { - new() super(SsaContext.new(compiler, mach.prog)) { } + new() super(SsaContext.new(compiler, mach.prog)) { + if (compiler.MachBuildOpt) { + opt = SsaOptimizer.new(context); + opt.remove_pure_ops = false; // instructions have no uses yet when added + } + } var maybeDead: List; def doMethod(method: IrMethod) { context.enterMethod(method); @@ -893,6 +899,7 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo def visitPtrAtObject(opcode: Opcode.PtrAtObject, t: Type) { mapOrId(app, normRef1(app.inputs[0])); } def visitPtrAtArrayElem(opcode: Opcode.PtrAtArrayElem, at: ArrayType, it: IntType) { genBoundsCheck(app, Fact.O_NO_NULL_CHECK); // XXX: propagate nullity + if (ssa.end()) return; var i_array = normRef1(app.inputs[0]); var i_index = normRef1(app.inputs[1]); var hsize = mach.getArrayElemOffset(at, 0), scale = mach.getArrayElemScale(at); @@ -1152,8 +1159,9 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo var tv: SsaInstr, zero = context.graph.nullConst(tt); if (Opcode.IntDiv.?(i_old.op.opcode)) { - var apply = ssa.add(tt.opSub(), [zero, x], Facts.NONE); - tv = genTruncateInPlace(apply, tt); // XXX: weird to apply this to new instructions. + var neg = ssa.add(tt.opSub(), [zero, x], Facts.NONE); + // XXX: weird to apply this to new instructions. + tv = if(SsaApplyOp.?(neg), genTruncateInPlace(SsaApplyOp.!(neg), tt), neg); } else { tv = zero; // x % -1 == #0 } @@ -1943,6 +1951,7 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo var oarr = i_old.inputs[0]; // XXX: CSE the array length if possible var len = refLoad(nullity, Int.TYPE, i_old, oarr, narr, mach.getArrayLengthOffset(i_old.op.typeArgs[0])); + if (ssa.end()) return (len, nullity | Fact.O_NO_NULL_CHECK); // the load threw, killing {i_old} var index = normRef1(i_old.inputs[1]); var throwOp = V3Op.newConditionalThrow(V3Exception.BoundsCheck); var op = Int.getType(false, 32).opLtEq(); @@ -1967,12 +1976,14 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo } def genCallClassSelector(i_old: SsaApplyOp, selector: IrSelector) { var func = genClassGetSelector(i_old, selector); + if (ssa.end()) return; var methodRef = V3Op.extractIrSpec(i_old.op, selector); var args = Arrays.prepend(func, normRefs(i_old.inputs)); call(i_old, mach.funcRep(methodRef), args); } def genCallVariantSelector(i_old: SsaApplyOp, selector: IrSelector) { var func = genVariantGetSelector(i_old, selector); + if (ssa.end()) return; var methodRef = V3Op.extractIrSpec(i_old.op, selector); var args = Arrays.prepend(func, normRefs(i_old.inputs)); call(i_old, mach.funcRep(methodRef), args); @@ -2225,10 +2236,8 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo } def addThrow(exception: string) -> SsaInstr { if (ssa.end()) return null; - if (cfopt == null) cfopt = SsaCfOptimizer.new(context); - var r = cfopt.insertThrow(ssa.prev.next, ssa.source, exception, null); - ssa.setPrev(null); - return r; + ssa.addThrow(ssa.source, exception); + return context.graph.nop(); } def genAllocMethod(size: IntType) -> IrMethod { diff --git a/aeneas/src/main/Compiler.v3 b/aeneas/src/main/Compiler.v3 index 5b6192d61..ea06f5181 100644 --- a/aeneas/src/main/Compiler.v3 +++ b/aeneas/src/main/Compiler.v3 @@ -103,6 +103,9 @@ class Compiler(target: Target) { var LocalRegAlloc = flags.get("LocalRegAlloc", level >= 1) && !CLOptions.DWARF.get(); var NormOptimize = flags.get("NormOptimize", level >= 2); var MachOptimize = flags.get("MachOptimize", level >= 2); + var VstBuildOpt = flags.get("VstBuildOpt", false); + var NormBuildOpt = flags.get("NormBuildOpt", false); + var MachBuildOpt = flags.get("MachBuildOpt", false); var LoopInvariantCodeMotion = flags.get("LoopInvariantCodeMotion", CLOptions.LICM.val); var IrAlloc = CLOptions.IR_ALLOC.get(); var firstEnabled = CLOptions.FIRST_ENABLED.get(); @@ -400,6 +403,10 @@ class Compilation(compiler: Compiler, prog: Program) { if (meth.ssa == null) { var context = SsaContext.new(compiler, prog).enterSpec(memberRef); var gen = VstSsaGen.new(context, prog.opBuilder); + if (compiler.VstBuildOpt) { + var opt = gen.opt = SsaOptimizer.new(context); + opt.remove_pure_ops = false; // instructions have no uses yet when added + } gen.recordDirectCalls = compiler.InlineEarly || CLOptions.INLINE.val != VstMatcher.None; var graph = gen.generate(); context.verify(); @@ -411,14 +418,14 @@ class Compilation(compiler: Compiler, prog: Program) { } if (compiler.VstControlOpt && graph.isMultiBlock()) { // Perform control flow optimizations. - SsaCfOptimizer.new(context).optimize(); + SsaOptimizer.new(context).optControlFlow(); context.printSsa("Control Optimized"); } if (compiler.PostpassOptimize && compiler.optEnabled("(gen)", meth, null, null)) { // Run the post-pass optimizer. var opt = SsaOptimizer.new(context); - opt.iopt.optimize_loads = compiler.LoadOptimize && + opt.optimize_loads = compiler.LoadOptimize && compiler.optEnabled("(gen loads)", meth, null, null); opt.optGraph(); context.printSsa("Postpass Optimized"); diff --git a/aeneas/src/main/Version.v3 b/aeneas/src/main/Version.v3 index d2b531a15..bf2378760 100644 --- a/aeneas/src/main/Version.v3 +++ b/aeneas/src/main/Version.v3 @@ -4,6 +4,6 @@ // Updated by VCS scripts. DO NOT EDIT. component Version { def UNSTABLE = true; - def version: string = "III-13.2003"; + def version: string = "III-13.2004"; var buildData: string; } diff --git a/aeneas/src/ssa/Ssa.v3 b/aeneas/src/ssa/Ssa.v3 index 7d2d8228e..520bbe826 100644 --- a/aeneas/src/ssa/Ssa.v3 +++ b/aeneas/src/ssa/Ssa.v3 @@ -500,6 +500,44 @@ component Ssa { next = nn; } } + // Recursively kills {block}, its instructions, and any successors that become unreachable. + def killBlock(block: SsaBlock) { + if (block.facts.O_KILLED) return; + // Recursively kill this block and its successor blocks + block.facts |= Fact.O_KILLED; + var succs = block.succs(); + var i = block.next; + while (i != null) { + // Kill all instructions in the block + var n = i.next; + i.next = null; + i.prev = null; + if (SsaInstr.?(i)) { + var j = SsaInstr.!(i); + j.kill(); + j.facts |= Fact.O_KILLED; + } + i = n; + } + killSuccs(succs); + } + // Removes {edges} and kills any successor blocks that become unreachable. + def killSuccs(edges: Array) { + // kill the outgoing control flow edges + var succs = Vector.new(); + for (s in edges) { + var d = s.dest; + if (d != null) { + Ssa.removeEdge(s, true); + succs.put(d); + } + } + // kill any dead successor blocks. + for (i < succs.length) { + var d = succs[i]; + if (!d.hasPred()) killBlock(d); + } + } def killInstr(i: SsaInstr) -> SsaInstr { i.remove(); i.kill(); diff --git a/aeneas/src/ssa/SsaBuilder.v3 b/aeneas/src/ssa/SsaBuilder.v3 index 62c58fd2d..9b88b4558 100644 --- a/aeneas/src/ssa/SsaBuilder.v3 +++ b/aeneas/src/ssa/SsaBuilder.v3 @@ -21,6 +21,7 @@ class SsaBuilder { var block: SsaBlock; def var prev: SsaLink; // new instructions are inserted after this; {null} if ended or unreachable var source: Source; + var opt: SsaOptimizer; // if non-null, optimizes each apply instruction as it is added new(context, graph, block: SsaBlock) { set(block); @@ -40,8 +41,7 @@ class SsaBuilder { } var i = SsaApplyOp.new(source, op, args); i.setFact(Opcodes.facts(op.opcode)); - append(i); - return i; + return appendApply(i); } def addApplyF(op: Operator, args: Array, facts: Fact.set) -> SsaInstr { if (Debug.PARANOID) { checkNoTypeVarsInOp(op); checkInputs(args); } @@ -52,8 +52,7 @@ class SsaBuilder { } var i = SsaApplyOp.new(source, op, args); i.setFact(Opcodes.facts(op.opcode) | facts); - append(i); - return i; + return appendApply(i); } def addApplyVst(source: Source, op: Operator, vst: VstOperator, args: Array) -> SsaInstr { var pt = op.sig.paramTypes; @@ -371,6 +370,7 @@ class SsaBuilder { def split() -> SsaBuilder { var mergeBuilder = SsaBuilder.new(context, graph, SsaBlock.new()); mergeBuilder.source = this.source; + mergeBuilder.opt = this.opt; splitInto(mergeBuilder.block); return mergeBuilder; } @@ -395,12 +395,31 @@ class SsaBuilder { // use it. return SsaIfCascade.new(this, t, merge.block, merge.prev); } + // Ends the block with a throw at the insertion point, killing any instructions after it. def addThrow(source: Source, ex: string) -> SsaInstr { if (end()) return block.end(); var t = SsaThrow.new(source, ex); appendEnd(t); + killAfter(t); return t; } + // Kills the instructions after the new end {t}, including any previous end of the block. + // The optimizer, if any, kills the previous end's successors. + private def killAfter(t: SsaEnd) { + for (p = t.next; !SsaBlock.?(p); ()) { + var n = p.next; + p.remove(); + if (SsaEnd.?(p)) { + var end = SsaEnd.!(p); + if (opt != null) opt.killSuccs(end.succs); + else Ssa.killSuccs(end.succs); + Ssa.killInstr(end); + break; + } + if (SsaInstr.?(p)) Ssa.killInstr(SsaInstr.!(p)); + p = n; + } + } def addGoto(target: SsaBlock) { if (end()) return; appendEnd(SsaGoto.new(target)); @@ -466,6 +485,12 @@ class SsaBuilder { if (!t.open()) return graph.valConst(t, context.prog.getDefaultValue(t)); return add(V3Op.newDefaultValue(t), Ssa.NO_INSTRS, Fact.O_PURE); } + // Appends the apply {i} and then, if this builder has an optimizer, optimizes it. + // Returns the instruction that computes the value of {i}. + def appendApply(i: SsaApplyOp) -> SsaInstr { + append(i); + return if(opt == null, i, opt.optAppended(this, i)); + } // Inserts {i} after {prev} and advances {prev} to {i}. def append(i: SsaInstr) { insert(i); @@ -784,7 +809,7 @@ class SsaBuilder { private def tryElimBoundsCheck(x: SsaInstr, y: SsaInstr) -> Fact.set { if (SsaConst.?(y)) { var yk = SsaConst.!(y).val; - if (SsaConst.?(x)) { + if (SsaConst.?(x) && Record.?(SsaConst.!(x).val)) { var r = Record.!(SsaConst.!(x).val); if (r != null && (yk == null || Box.?(yk))) { var i = V3.unboxI32(yk); @@ -939,14 +964,13 @@ class SsaBuilder { } return val; } - def pure(op: Operator, args: Array) -> SsaApplyOp { + def pure(op: Operator, args: Array) -> SsaInstr { return add(op, args, Fact.O_PURE); } - def add(op: Operator, args: Array, facts: Fact.set) -> SsaApplyOp { + def add(op: Operator, args: Array, facts: Fact.set) -> SsaInstr { if (Opcode.VstSugar.?(op.opcode)) return V3.fail("unexpected sugar"); var i = SsaApplyOp.new(source, op, args).setFact(facts | Opcodes.facts(op.opcode)); - if (!end()) append(i); - return i; + return if(end(), i, appendApply(i)); } // add() specialized to 1 input. def add1(op: Operator, x: SsaInstr, facts: Fact.set) -> SsaInstr { @@ -956,8 +980,7 @@ class SsaBuilder { // if (r.0) return r.1; // } var i = SsaApplyOp.new(source, op, [x]).setFact(facts); - if (!end()) append(i); - return i; + return if(end(), i, appendApply(i)); } def pure1(op: Operator, x: SsaInstr) -> SsaInstr { return add1(op, x, Fact.O_PURE); // XXX: can skip an operator fact lookup for tryEval @@ -970,8 +993,7 @@ class SsaBuilder { // if (r.0) return r.1; // } var i = SsaApplyOp.new(source, op, [x, y]).setFact(facts); - if (!end()) append(i); - return i; + return if(end(), i, appendApply(i)); } def pure2(op: Operator, x: SsaInstr, y: SsaInstr) -> SsaInstr { return add2(op, x, y, Fact.O_PURE); // XXX: can skip an operator fact lookup for tryEval diff --git a/aeneas/src/ssa/SsaInliner.v3 b/aeneas/src/ssa/SsaInliner.v3 index 027bef61a..9fb9f10c1 100644 --- a/aeneas/src/ssa/SsaInliner.v3 +++ b/aeneas/src/ssa/SsaInliner.v3 @@ -169,7 +169,7 @@ class SsaInliner extends SsaRebuilder { for (i = contInstrs; i != null; i = i.next) { if (SsaInstr.?(i)) SsaInstr.!(i).kill(); } - SsaCfOptimizer.new(context).killSuccs(contEnd.succs); + Ssa.killSuccs(contEnd.succs); contBlock = null; } def visitApplyOp(oi: SsaApplyOp) { diff --git a/aeneas/src/ssa/SsaOptimizer.v3 b/aeneas/src/ssa/SsaOptimizer.v3 index 3f86a2182..3d3cbf02b 100644 --- a/aeneas/src/ssa/SsaOptimizer.v3 +++ b/aeneas/src/ssa/SsaOptimizer.v3 @@ -1,36 +1,52 @@ // Copyright 2015 Google Inc. All rights reserved. // See LICENSE for details of Apache 2.0 license. -// Coordinates optimizations of SSA graphs, blocks, and individual instructions. -// Provides a facade to the outside that hides details of reduction, analysis, etc. -class SsaOptimizer(context: SsaContext) { +// Optimizes SSA graphs, blocks, and individual instructions, performing such optimizations +// as const folding, strength reduction, load elimination, bounds check elimination, and +// control flow optimizations. Individual instructions can be optimized either in a pass over +// a graph or as an {SsaBuilder} adds them. +class SsaOptimizer(context: SsaContext) extends SsaInstrVisitor { + var optimize_loads = true; // activates load elimination + var optimize_nullchecks = true; // activates null check elimination + var optimize_inits = false; // activates init elimination + var optimize_bounds = true; + var remove_pure_ops = true; + var fold_pure_ops = true; + var fold_foldable_ops = true; + var ssa: SsaBuilder; // inserts new instructions before the current instruction + var state: SsaBlockState; // flow-sensitive state, managed by the user; {null} if none + var foldingArgs: FoldingArguments; + var result: SsaInstr; // replacement for the current instruction + var m: SsaInstrMatcher; // matcher for reductions, allocated on demand var marker = SsaInternalMarker.new(); - def iopt = SsaInstrReducer.new(context); + var mark: int; // mark for control flow optimization + def queue = Vector.new(); // queue for control flow optimization def reset(graph: SsaGraph) { - iopt.graph = graph; + useGraph(graph); marker.reset(graph); context.graph = graph; context.block = null; } def optGraph() { + useGraph(context.graph); marker.reset(context.graph); def queue = Vector<(SsaBlock, SsaBlockState)>.new(); queue.put(context.graph.startBlock, SsaBlockState.new()); var mark = marker.allocMark(context.graph.startBlock); for (i < queue.length) { var t = queue[i], block = t.0; - iopt.state = t.1; + state = t.1; if (block.facts.O_KILLED) continue; - iopt.reduceBlock(block); + reduceBlock(block); var succs = block.succs(); for (s in succs) { var d = s.dest; if (marker.getMark(d) < 0) { - var state: SsaBlockState; - if (succs.length == 1) state = if(s.dest.preds.length == 1, iopt.state, iopt.state.clear()); - else state = if(s.dest.preds.length == 1, iopt.state.copy(), SsaBlockState.new()); - queue.put(d, state); + var dstate: SsaBlockState; + if (succs.length == 1) dstate = if(s.dest.preds.length == 1, state, state.clear()); + else dstate = if(s.dest.preds.length == 1, state.copy(), SsaBlockState.new()); + queue.put(d, dstate); marker.setMark(d, mark); } } @@ -42,27 +58,6 @@ class SsaOptimizer(context: SsaContext) { l.licm(); } } - def optLoop(header: SsaBlock, loopBody: SsaBlock, loopEnd: SsaBlock) { - } - def optBlock(block: SsaBlock) { - } - def reduceIf(state: SsaBlockState, i: SsaIf) { - iopt.state = state; - iopt.prev = i.prev; - iopt.reduceIf(i); - iopt.state = null; - } - def reduceApply(state: SsaBlockState, app: SsaApplyOp) -> SsaInstr { - iopt.state = state; - iopt.prev = app.prev; - var res = iopt.reduceApplyOp(app); - if (res != app) { - app.replace(res); - app.remove(); - } - iopt.state = null; - return res; - } def checkAndPruneGraph() -> bool { var changed = false, print = context.shouldPrintOpt(); marker.reset(context.graph); @@ -95,7 +90,6 @@ class SsaOptimizer(context: SsaContext) { } } // Prune off unreachable predecessors and uses. - var cfopt = SsaCfOptimizer.new(context); for (i < reachable.length) { var block = reachable[i], j = 0; while (j < block.preds.length) { @@ -110,306 +104,48 @@ class SsaOptimizer(context: SsaContext) { } return changed; } -} - - -def ARRAY_LENGTH_FIELD = IrField.new(V3.stringType, Int.TYPE).setFlag(IrFlag.F_VALUE); - -// Matches various patterns of SSA instructions, recording the state of the match -// in the exposed fields. -class SsaInstrMatcher extends SsaInstrVisitor { - var x: SsaInstr; // First input - var y: SsaInstr; // Second input - var xconst: bool; // true if {x} is a constant - var yconst: bool; // true if {y} is a constant - var fold: bool; // true if all inputs are constants - var wide: bool; // true if this is a long int operation - var xval: Val; // if {x} is a constant, then its value - var yval: Val; // if {y} is a constant, then its value - var xint: int; // if {x} is an int constant, then its value - var yint: int; // if {y} is an int constant, then its value - var xlong: long; // if {x} is a long constant, then its value - var ylong: long; // if {y} is a long constant, then its value - var yzero: bool; // true if {y} is zero - var inttype: IntType; // if this is an integer operation, the result type - - // Resets this matcher - def clear() { - x = y = null; - xval = yval = null; - inttype = null; - xconst = yconst = yzero = fold = wide = false; - xint = yint = 0; - xlong = ylong = 0; - } - // Initializes the match state for a binary operation and returns {yval}. - def binop(i: SsaApplyOp) -> Val { - x = i.input0(); - y = i.input1(); - yzero = y.facts.V_ZERO; - fold = false; - if (xconst = SsaConst.?(x)) { - xval = SsaConst.!(x).val; - if (yconst = SsaConst.?(y)) { - fold = true; - return yval = SsaConst.!(y).val; - } else if (i.facts.O_COMMUTATIVE) { - i.inputs[0].update(y); - i.inputs[1].update(x); - xconst = false; - yconst = true; - var t = x; - x = y; - y = t; - yval = xval; - xval = null; - yzero = y.facts.V_ZERO; - return yval; - } else { - return yval = null; - } - } - return yval = if(yconst = SsaConst.?(y), SsaConst.!(y).val); - } - // Initializes the match state for an integer binary operation and returns {yint}. - def intbinop(i: SsaApplyOp) -> int { - binop(i); - inttype = IntType.!(i.op.sig.paramTypes[0]); - if (inttype.width <= 32) { - wide = false; - if (xconst) xint = V3.unboxI32(xval); - if (yconst) { - yint = V3.unboxI32(yval); - yzero = yint == 0; - } - } else { - wide = true; - if (xconst) xlong = Long.unboxSU(xval, inttype.signed); - if (yconst) { - ylong = Long.unboxSU(yval, inttype.signed); - yzero = ylong == 0; - } - } - return yint; - } - // Initializes the match state for an integer binary operation and returns {yint}. - def boolbinop(i: SsaApplyOp) -> int { - binop(i); - inttype = null; - if (xconst) xint = if(Bool.unbox(xval), 1); - if (yconst) { - yint = if(Bool.unbox(yval), 1); - yzero = yint == 0; - } - return yint; - } - // Initializes the match state for a unary operation and returns {xval}. - def unop(i: SsaApplyOp) -> Val { - x = i.input0(); - return xval = if(fold = xconst = SsaConst.?(x), SsaConst.!(x).val); - } - // Initializes the match state for an n-ary operation and returns {xval}. - def n_op(i: SsaApplyOp) -> Val { - fold = true; - var max = i.inputs.length; - for (j < max) { - if (!SsaConst.?(i.inputs[j].dest)) return (fold = false, null).last; - } - if (max >= 1) { - x = i.input0(); - xconst = true; - xval = SsaConst.!(x).val; - } - if (max >= 2) { - y = i.input1(); - yconst = true; - yval = SsaConst.!(y).val; - } - return xval; - } - def yis(val: int) -> bool { - return yconst && if(wide, ylong == val, yint == val); + // Uses a new builder for insertions during a pass over {g}. + def useGraph(g: SsaGraph) { + ssa = SsaBuilder.new(context, g, null); + ssa.opt = this; } - def yrange(min: int, max: int) -> bool { - if (Debug.PARANOID && !yconst) return V3.fail("not const"); - if (wide) return min <= ylong && ylong <= max; - else return min <= yint && yint <= max; - } - def yisPowerOf2() -> bool { - return yconst && if(wide, (ylong & (ylong - 1)) == 0, (yint & (yint - 1)) == 0); - } - def ylog2() -> int { - if (Debug.PARANOID && !yconst) return V3.fail("not const"); - return if(wide, Longs.log(u64.view(ylong)), Ints.log(u32.view(yint))); - } -} - -// Flow-sensitive state for local optimizations. -class SsaBlockState { - var end: bool; - var inbounds: List<(SsaInstr, SsaInstr)>; - var nonnull: List; - var inits: List; - def pure_loads = Vector<(SsaInstr, IrMember, SsaInstr)>.new(); - def impure_loads = Vector<(SsaInstr, IrMember, SsaInstr)>.new(); - def ptr_loads = Vector<(SsaInstr, Type, SsaInstr)>.new(); - - def clear() -> this { - nonnull = null; - inits = null; - inbounds = null; - end = false; - pure_loads.resize(0); - impure_loads.resize(0); - ptr_loads.resize(0); - } - def kill() -> this { - impure_loads.resize(0); - ptr_loads.resize(0); - } - def load(obj: SsaInstr, m: IrMember, apply: SsaApplyOp) -> SsaInstr { -//TODO:dce if (apply.useList == null && apply.facts.O_NO_NULL_CHECK) { -// apply.replace(null); -// return context.graph.zeroConst(); // XXX: recursively delete dead code -// } - var val = find(obj, m); - if (val != null) return val; - if (m.isConst() || IrMethod.?(m) && !m.flags.F_POINTED_AT) pure_loads.put(obj, m, apply); - else impure_loads.put(obj, m, apply); - return apply; - } - def loadPtr(ptr: SsaInstr, loadType: Type, apply: SsaApplyOp) -> SsaInstr { - var prev = find0(ptr_loads, ptr, loadType); - if (prev != null) return prev; - ptr_loads.put(ptr, loadType, apply); - return apply; - } - def store(obj: SsaInstr, field: IrField, apply: SsaApplyOp, val: SsaInstr) -> SsaInstr { - var prev = find(obj, field); - if (prev == val) { // redundant store of previous value to field - apply.remove(); - apply.kill(); - return null; - } - killField(field); - impure_loads.put(obj, field, val); - return apply; - } - def storePtr(ptr: SsaInstr, storeType: Type, apply: SsaApplyOp, val: SsaInstr) -> SsaInstr { - var prev = find0(ptr_loads, ptr, storeType); - if (prev == val) { // redundant store of previous value to field - apply.remove(); - apply.kill(); - return null; - } - ptr_loads.resize(0); // XXX: improve pointer alias analysis, distinguish initializing stores - if (!isNarrowingStore(storeType, val)) ptr_loads.put(ptr, storeType, val); // XXX: cache narrowing stores properly - return apply; - } - def isNarrowingStore(storeType: Type, val: SsaInstr) -> bool { - match (storeType) { - x: PrimType => match (val.getType()) { - y: PrimType => return x.width != y.width; - } - } - return false; - } - def init(obj: SsaInstr, f: IrField, val: SsaInstr) { - if (f.isConst() && !f.flags.F_POINTED_AT) pure_loads.put(obj, f, val); - else impure_loads.put(obj, f, val); - } - def copy() -> SsaBlockState { - var that = SsaBlockState.new(); - that.end = this.end; - that.inbounds = this.inbounds; - that.nonnull = this.nonnull; - that.inits = this.inits; - that.pure_loads.putv(this.pure_loads); - that.impure_loads.putv(this.impure_loads); - that.ptr_loads.putv(this.ptr_loads); - return that; - } - def find(obj: SsaInstr, m: IrMember) -> SsaInstr { - var r = find0(pure_loads, obj, m); - if (r == null) r = find0(impure_loads, obj, m); - return r; - } - def find0(v: Vector<(SsaInstr, T, SsaInstr)>, obj: SsaInstr, m: T) -> SsaInstr { - for (i < v.length) { - var t = v[i]; - if (t.0 == obj && t.1 == m) return t.2; - } - return null; - } - def killField(f: IrField) { - var v = impure_loads, i = 0; - for (j < v.length) { - var t = v[i]; - if (t.1 != f) { - if (i != j) v[i] = t; - i++; - } - } - v.resize(i); - } - def killPointedAt() { - var v = impure_loads, i = 0; - for (j < v.length) { - var t = v[i]; - if (!t.1.flags.F_POINTED_AT) { - if (i != j) v[i] = t; - i++; - } - } - v.resize(i); - } - def killArrayElems() { - ptr_loads.resize(0); // assume pointer loads could alias array elements. - } -} - -// Optimizes SSA instructions one at a time, performing such optimizations -// as const folding, strength reduction, load elimination, and bounds check elimination. -class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { - var optimize_loads = true; // activates load elimination - var optimize_nullchecks = true; // activates null check elimination - var optimize_inits = false; // activates init elimination - var optimize_bounds = true; - var remove_pure_ops = true; - var fold_pure_ops = true; - var fold_foldable_ops = true; - var graph: SsaGraph; - var prev: SsaLink; // instruction before the current instruction - var next: SsaLink; // instruction after the current instruction - var state = SsaBlockState.new(); // flow sensitive state - var foldingArgs: FoldingArguments; - var result: SsaInstr; // replacement for the current instruction - - def reset(graph: SsaGraph, block: SsaBlock, prev: SsaLink) { - this.context.graph = graph; - this.context.block = block; - this.graph = graph; - this.prev = prev; - this.state.clear(); + // Returns {true} if a reduction has ended the current block with a throw. + def end() -> bool { + return ssa.end(); } def reduceBlock(block: SsaBlock) { - graph = context.graph; context.block = block; + ssa.block = block; for (i = block.next; SsaInstr.?(i); ()) { - next = i.next; + var n = i.next; reduceInstr(SsaInstr.!(i)); - if (state.end) break; - i = next; + if (end()) break; + // Optimizing a block end may have merged a successor block into this one. + i = if(SsaEnd.?(i) && i.next == null, ssa.prev.next, n); } } def reduceInstr(i: SsaInstr) -> SsaInstr { - prev = i.prev; + ssa.setPrev(i.prev); result = i; visit(i); return result; } + // Optimizes {i}, which {b} has just appended, and leaves {b} positioned after the result. + def optAppended(b: SsaBuilder, i: SsaApplyOp) -> SsaInstr { + ssa = b; + b.setPrev(i.prev); // insert any new instructions before {i} + var res = tryOptInstr(i); + if (b.end()) return b.unreachable(i.getType()); // a throw killed {i} + if (res == i && i.prev != null) { + b.setPrev(i); + } else { // replaced or killed + i.remove(); + if (res == null) res = ssa.graph.nop(); // e.g. a redundant Init + } + return res; + } def visitApplyOp(i: SsaApplyOp) { - var res = reduceApplyOp(i); + var res = tryOptInstr(i); if (res != i) { i.replace(res); i.remove(); @@ -422,24 +158,21 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } def visitIf(i: SsaIf) { result = reduceIf(i); } def visitGoto(i: SsaGoto) { - var cfopt = SsaCfOptimizer.new(context), block = i.block(); - if (cfopt.optEdge(block.succ(0))) next = prev.next; + optEdge(i.block().succ(0)); } def visitSwitch(i: SsaSwitch) { var val = i.input0(); if (SsaConst.?(val)) { - var cfopt = SsaCfOptimizer.new(context), block = i.block(); - var succ = i.findSucc(SsaConst.!(val).val); - cfopt.replaceWithGoto(block, i, succ); - if (cfopt.optEdge(block.succ(0))) next = prev.next; + var block = i.block(), succ = i.findSucc(SsaConst.!(val).val); + replaceWithGoto(block, i, succ); + optEdge(block.succ(0)); } } def reduceIf(ifi: SsaIf) -> SsaInstr { var cond = ifi.input0(), block = ifi.block(); if (SsaConst.?(cond)) { - var cfopt = SsaCfOptimizer.new(context); - cfopt.replaceWithGoto(block, ifi, block.succ(if(cond.unbox(), 0, 1))); - if (cfopt.optEdge(block.succ(0))) next = prev.next; + replaceWithGoto(block, ifi, block.succ(if(cond.unbox(), 0, 1))); + optEdge(block.succ(0)); } else if (SsaApplyOp.?(cond)) { if (SsaApplyOp.!(cond).op.opcode == Opcode.BoolNot) { var cc = cond.input0(); @@ -451,236 +184,247 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } return ifi; } - def reduceApplyOp(i: SsaApplyOp) -> SsaInstr { + // Tries to optimize {i}, first by constant folding, then by reduction, and then by CSE. + // Returns {i} or the instruction that replaces it. + def tryOptInstr(i: SsaApplyOp) -> SsaInstr { + var r = tryFold(i); + if (r != null) return r; + if (remove_pure_ops && i.facts.O_PURE && !i.hasNonDebugUses()) return killUnused(i); + r = tryReduce(i); + if (r != i) return r; + return tryCSE(i); + } + // Tries to evaluate {i} if its inputs are constants. Returns {null} on failure. + def tryFold(i: SsaApplyOp) -> SsaInstr { if (i.facts.O_PURE) { - if (fold_pure_ops) { - var folded = tryEval(i); - if (folded != null) return folded; - } - if (remove_pure_ops && !i.hasNonDebugUses()) { - return killUnused(i); - } + if (fold_pure_ops) return tryEval(i); } else if (i.facts.O_FOLDABLE) { - if (fold_foldable_ops) { - var folded = tryEval(i); - if (folded != null) return folded; - } + if (fold_foldable_ops) return tryEval(i); } + return null; + } + // Tries to reduce {i} to a simpler form through its opcode's visit method. + def tryReduce(i: SsaApplyOp) -> SsaInstr { + if (m == null) m = SsaInstrMatcher.new(); result = app = i; visitOp(i.op); return result; } + // Tries to replace {i} with an equivalent instruction that dominates it. + def tryCSE(i: SsaApplyOp) -> SsaInstr { + return i; // TODO: common subexpression elimination + } // Replaces the current instruction with {i}. def reduceTo(i: SsaInstr) { result = i; } def visitBoolEq(opcode: Opcode.BoolEq) { reduceEq(); } def visitBoolAnd(opcode: Opcode.BoolAnd) { norm_binop(app); - if (fold) return reduceTo(graph.boolConst(Bool.unbox(xval) && Bool.unbox(yval))); // K && K => K - if (yconst) return reduceTo(if(Bool.unbox(yval), x, graph.falseConst())); // x && K => x + if (m.fold) return reduceTo(ssa.graph.boolConst(Bool.unbox(m.xval) && Bool.unbox(m.yval))); // K && K => K + if (m.yconst) return reduceTo(if(Bool.unbox(m.yval), m.x, ssa.graph.falseConst())); // x && K => x } def visitBoolOr(opcode: Opcode.BoolOr) { norm_binop(app); - if (fold) return reduceTo(graph.boolConst(Bool.unbox(xval) || Bool.unbox(yval))); // K || K => K - if (yconst) return reduceTo(if(Bool.unbox(yval), graph.trueConst(), x)); // x || K => x + if (m.fold) return reduceTo(ssa.graph.boolConst(Bool.unbox(m.xval) || Bool.unbox(m.yval))); // K || K => K + if (m.yconst) return reduceTo(if(Bool.unbox(m.yval), ssa.graph.trueConst(), m.x)); // x || K => x } def visitBoolNot(opcode: Opcode.BoolNot) { - var xval = unop(app); - if (xconst) return reduceTo(graph.boolConst(!Bool.unbox(xval))); // !K => K - if (x.optag() == Opcode.BoolNot.tag) return reduceTo(x.input0()); // !!x => x + var xval = m.unop(app); + if (m.xconst) return reduceTo(ssa.graph.boolConst(!Bool.unbox(xval))); // !K => K + if (m.x.optag() == Opcode.BoolNot.tag) return reduceTo(m.x.input0()); // !!x => x } def visitIntEq(opcode: Opcode.IntEq, t: Type) { reduceEq(); } def visitIntAdd(opcode: Opcode.IntAdd, it: IntType) { var yint = norm_intbinop(app); - if (fold) return reduceTo(fold2i(int.+, long.+)); // K + K == K - if (yzero) return reduceTo(x); // x + 0 == x + if (m.fold) return reduceTo(fold2i(int.+, long.+)); // K + K == K + if (m.yzero) return reduceTo(m.x); // x + 0 == x } def visitIntSub(opcode: Opcode.IntSub, it: IntType) { var yint = norm_intbinop(app); - if (fold) return reduceTo(fold2i(int.-, long.-)); // K - K == K - if (yzero) return reduceTo(x); // x - 0 == x + if (m.fold) return reduceTo(fold2i(int.-, long.-)); // K - K == K + if (m.yzero) return reduceTo(m.x); // x - 0 == x } def visitIntMul(opcode: Opcode.IntMul, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(y); // x * 0 == 0 - if (fold) return reduceTo(fold2i(int.*, long.*)); // K * K == K - if (yconst) { - if (yis(1)) return reduceTo(x); // x * 1 == 1 - if (yis(-1)) return reduceTo(addBinop(inttype.opSub(), graph.nullConst(inttype), x)); // x * -1 == 0 - x - if (yis(2)) return reduceTo(addBinop(inttype.opAdd(), x, x)); - if (yisPowerOf2()) { + if (m.yzero) return reduceTo(m.y); // x * 0 == 0 + if (m.fold) return reduceTo(fold2i(int.*, long.*)); // K * K == K + if (m.yconst) { + if (m.yis(1)) return reduceTo(m.x); // x * 1 == 1 + if (m.yis(-1)) return reduceTo(addBinop(m.inttype.opSub(), ssa.graph.nullConst(m.inttype), m.x)); // x * -1 == 0 - x + if (m.yis(2)) return reduceTo(addBinop(m.inttype.opAdd(), m.x, m.x)); + if (m.yisPowerOf2()) { // x * 2^k == x << k - return reduceTo(addBinop(inttype.opShl(), x, graph.intConst(ylog2())).setFact(Fact.O_NO_SHIFT_CHECK)); + return reduceTo(addBinop(m.inttype.opShl(), m.x, ssa.graph.intConst(m.ylog2())).setFact(Fact.O_NO_SHIFT_CHECK)); } } } def visitIntDiv(opcode: Opcode.IntDiv, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(addThrow(app.source, V3Exception.DivideByZero)); // fold x / 0 - if (fold) return reduceTo(fold4i(int./, u32./, long./, u64./)); - if (yconst) { + if (m.yzero) return reduceTo(addThrow(app.source, V3Exception.DivideByZero)); // fold x / 0 + if (m.fold) return reduceTo(fold4i(int./, u32./, long./, u64./)); + if (m.yconst) { app.facts |= Facts.O_SAFE_DIVIDE; - if (yis(1)) return reduceTo(x); // x / 1 == x - if (yis(-1) && inttype.signed) return reduceTo(addBinop(inttype.opSub(), graph.nullConst(inttype), x)); // x / -1 == 0 - x - if (yisPowerOf2() && x.facts.V_NON_NEGATIVE) { + if (m.yis(1)) return reduceTo(m.x); // x / 1 == x + if (m.yis(-1) && m.inttype.signed) return reduceTo(addBinop(m.inttype.opSub(), ssa.graph.nullConst(m.inttype), m.x)); // x / -1 == 0 - x + if (m.yisPowerOf2() && m.x.facts.V_NON_NEGATIVE) { // x / 2^k == x >> k - return reduceTo(addBinop(inttype.opShr(), x, graph.intConst(ylog2())).setFact(Fact.O_NO_SHIFT_CHECK)); + return reduceTo(addBinop(m.inttype.opShr(), m.x, ssa.graph.intConst(m.ylog2())).setFact(Fact.O_NO_SHIFT_CHECK)); } } } def visitIntMod(opcode: Opcode.IntMod, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(addThrow(app.source, V3Exception.DivideByZero)); // fold x % 0 - if (fold) return reduceTo(fold4i(int.%, u32.%, long.%, u64.%)); - if (yconst) { + if (m.yzero) return reduceTo(addThrow(app.source, V3Exception.DivideByZero)); // fold x % 0 + if (m.fold) return reduceTo(fold4i(int.%, u32.%, long.%, u64.%)); + if (m.yconst) { app.facts |= Facts.O_SAFE_DIVIDE; - if (yis(1) || (yis(-1) && inttype.signed)) { + if (m.yis(1) || (m.yis(-1) && m.inttype.signed)) { // x % +/-1 == 0 - return reduceTo(if(wide, l_const(0), i_const(0))); + return reduceTo(if(m.wide, l_const(0), i_const(0))); } - if (yisPowerOf2() && x.facts.V_NON_NEGATIVE) { + if (m.yisPowerOf2() && m.x.facts.V_NON_NEGATIVE) { // x % 2^k == x & (2^k - 1) - var mask = if(wide, l_const(ylong - 1), i_const(yint - 1)); - return reduceTo(addBinop(inttype.opAnd(), x, mask).setFact(Fact.V_NON_NEGATIVE)); + var mask = if(m.wide, l_const(m.ylong - 1), i_const(yint - 1)); + return reduceTo(addBinop(m.inttype.opAnd(), m.x, mask).setFact(Fact.V_NON_NEGATIVE)); } } - if ((x.facts & y.facts & Fact.V_NON_NEGATIVE) == Fact.V_NON_NEGATIVE) app.facts |= Fact.V_NON_NEGATIVE; + if ((m.x.facts & m.y.facts & Fact.V_NON_NEGATIVE) == Fact.V_NON_NEGATIVE) app.facts |= Fact.V_NON_NEGATIVE; } def visitIntAnd(opcode: Opcode.IntAnd, it: IntType) { norm_intbinop(app); - if (yzero) return reduceTo(y); // x & 0 == 0 - if (fold) return reduceTo(fold2i(int.&, long.&)); // K & K == K - if (yconst && yis(-1)) return reduceTo(x); // x & -1 == x + if (m.yzero) return reduceTo(m.y); // x & 0 == 0 + if (m.fold) return reduceTo(fold2i(int.&, long.&)); // K & K == K + if (m.yconst && m.yis(-1)) return reduceTo(m.x); // x & -1 == x // (nonneg & x) => nonneg - app.facts |= ((x.facts | y.facts) & Fact.V_NON_NEGATIVE); + app.facts |= ((m.x.facts | m.y.facts) & Fact.V_NON_NEGATIVE); } def visitIntOr(opcode: Opcode.IntOr, it: IntType) { norm_intbinop(app); - if (yzero) return reduceTo(x); // x | 0 == x - if (fold) return reduceTo(fold2i(int.|, long.|)); // K | K == K - if (yconst && yis(-1)) return reduceTo(y); // x | -1 == -1 + if (m.yzero) return reduceTo(m.x); // x | 0 == x + if (m.fold) return reduceTo(fold2i(int.|, long.|)); // K | K == K + if (m.yconst && m.yis(-1)) return reduceTo(m.y); // x | -1 == -1 // (neg | x) => neg, (nonzero | x) => nonzero - app.facts |= ((x.facts | y.facts) & (Fact.V_BELOW_ZERO | Fact.V_NON_ZERO)); + app.facts |= ((m.x.facts | m.y.facts) & (Fact.V_BELOW_ZERO | Fact.V_NON_ZERO)); } def visitIntXor(opcode: Opcode.IntXor, it: IntType) { norm_intbinop(app); - if (yzero) return reduceTo(x); // x ^ 0 == x - if (x == y) return reduceTo(graph.nullConst(inttype)); // x ^ x == 0 - if (fold) return reduceTo(fold2i(int.^, long.^)); // K ^ K == K - app.facts |= (x.facts & y.facts & Fact.V_NON_NEGATIVE); // (nonneg ^ nonneg) => nonneg - if ((x.facts & y.facts & Fact.V_BELOW_ZERO) == Fact.V_NON_NEGATIVE) app.facts |= Fact.V_NON_NEGATIVE; + if (m.yzero) return reduceTo(m.x); // x ^ 0 == x + if (m.x == m.y) return reduceTo(ssa.graph.nullConst(m.inttype)); // x ^ x == 0 + if (m.fold) return reduceTo(fold2i(int.^, long.^)); // K ^ K == K + app.facts |= (m.x.facts & m.y.facts & Fact.V_NON_NEGATIVE); // (nonneg ^ nonneg) => nonneg + if ((m.x.facts & m.y.facts & Fact.V_BELOW_ZERO) == Fact.V_NON_NEGATIVE) app.facts |= Fact.V_NON_NEGATIVE; } // XXX: optimize x << (y & 0x1F) if target automatically masks shiftor values def visitIntShl(opcode: Opcode.IntShl, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(x); // x << 0 == x - if (fold) { // K << K == K - if (wide) { - var y6 = u6.view(ylong); - return reduceTo(if(ylong == y6, l_const(xlong << y6), graph.nullConst(inttype))); + if (m.yzero) return reduceTo(m.x); // x << 0 == x + if (m.fold) { // K << K == K + if (m.wide) { + var y6 = u6.view(m.ylong); + return reduceTo(if(m.ylong == y6, l_const(m.xlong << y6), ssa.graph.nullConst(m.inttype))); } else { var y5 = u5.view(yint); - return reduceTo(if(yint == y5, i_const(xint << y5), graph.nullConst(inttype))); + return reduceTo(if(yint == y5, i_const(m.xint << y5), ssa.graph.nullConst(m.inttype))); } } - if (yconst) { - if (yrange(0, inttype.width - 1)) app.facts |= Fact.O_NO_SHIFT_CHECK; - else return reduceTo(graph.nullConst(inttype)); + if (m.yconst) { + if (m.yrange(0, m.inttype.width - 1)) app.facts |= Fact.O_NO_SHIFT_CHECK; + else return reduceTo(ssa.graph.nullConst(m.inttype)); } else { - shiftcheck(app, y); + shiftcheck(app, m.y); } } // XXX: optimize x >> (y & 0x1F) if target automatically masks shiftor values def visitIntSar(opcode: Opcode.IntSar, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(x); // x >> 0 == x - if (fold) { // K >> K == K - if (wide) { - var y6 = u6.view(if(u64.view(ylong) > 63, 63, ylong)); - return reduceTo(l_const(xlong >> y6)); + if (m.yzero) return reduceTo(m.x); // x >> 0 == x + if (m.fold) { // K >> K == K + if (m.wide) { + var y6 = u6.view(if(u64.view(m.ylong) > 63, 63, m.ylong)); + return reduceTo(l_const(m.xlong >> y6)); } else { var y5 = u5.view(if(u32.view(yint) > 31, 31, yint)); - return reduceTo(i_const(xint >> y5)); + return reduceTo(i_const(m.xint >> y5)); } } - if (yconst) { - if (!yrange(0, inttype.width - 1)) { - if (x.facts.V_NON_NEGATIVE) return reduceTo(graph.nullConst(inttype)); - if (x.facts.V_BELOW_ZERO) return reduceTo(graph.intConst(-1)); - app.inputs[1].update(graph.intConst(inttype.width - 1)); + if (m.yconst) { + if (!m.yrange(0, m.inttype.width - 1)) { + if (m.x.facts.V_NON_NEGATIVE) return reduceTo(ssa.graph.nullConst(m.inttype)); + if (m.x.facts.V_BELOW_ZERO) return reduceTo(ssa.graph.intConst(-1)); + app.inputs[1].update(ssa.graph.intConst(m.inttype.width - 1)); } app.facts |= Fact.O_NO_SHIFT_CHECK; } else { - shiftcheck(app, y); + shiftcheck(app, m.y); } } // XXX: optimize x >>> (y & 0x1F) if target automatically masks shiftor values def visitIntShr(opcode: Opcode.IntShr, it: IntType) { var yint = norm_intbinop(app); - if (yzero) return reduceTo(x); // x >>> 0 = x - if (fold) { // K >>> K = K - if (wide) { - var y6 = u6.view(ylong); - if (ylong == y6) return reduceTo(l_const(Eval.doIntTruncU64(inttype, xlong) >>> y6)); - return reduceTo(graph.nullConst(inttype)); + if (m.yzero) return reduceTo(m.x); // x >>> 0 = x + if (m.fold) { // K >>> K = K + if (m.wide) { + var y6 = u6.view(m.ylong); + if (m.ylong == y6) return reduceTo(l_const(Eval.doIntTruncU64(m.inttype, m.xlong) >>> y6)); + return reduceTo(ssa.graph.nullConst(m.inttype)); } else { var y5 = u5.view(yint); - if (yint == y5) return reduceTo(i_const(Eval.doIntTruncU32(inttype, xint) >>> y5)); - return reduceTo(graph.nullConst(inttype)); + if (yint == y5) return reduceTo(i_const(Eval.doIntTruncU32(m.inttype, m.xint) >>> y5)); + return reduceTo(ssa.graph.nullConst(m.inttype)); } } - if (yconst) { - if (yrange(0, inttype.width - 1)) app.facts |= Fact.O_NO_SHIFT_CHECK; - else return reduceTo(graph.nullConst(inttype)); + if (m.yconst) { + if (m.yrange(0, m.inttype.width - 1)) app.facts |= Fact.O_NO_SHIFT_CHECK; + else return reduceTo(ssa.graph.nullConst(m.inttype)); } else { - shiftcheck(app, y); + shiftcheck(app, m.y); } - if (y.facts.V_NON_ZERO) app.facts |= Fact.V_NON_NEGATIVE; + if (m.y.facts.V_NON_ZERO) app.facts |= Fact.V_NON_NEGATIVE; } def visitIntLt(opcode: Opcode.IntLt, it: IntType) { norm_intbinop(app); // XXX: range analysis for comparisons against constants, particularly 0 - if (fold) return reduceTo(fold4b(int.<, u32.<, long.<, u64.<)); + if (m.fold) return reduceTo(fold4b(int.<, u32.<, long.<, u64.<)); } def visitIntLteq(opcode: Opcode.IntLteq, it: IntType) { norm_intbinop(app); // XXX: range analysis for comparisons against constants, particularly 0 - if (fold) return reduceTo(fold4b(int.<=, u32.<=, long.<=, u64.<=)); + if (m.fold) return reduceTo(fold4b(int.<=, u32.<=, long.<=, u64.<=)); } def visitFloatEq(opcode: Opcode.FloatEq, ft: FloatType) { norm_binop(app); - if (xconst && Float.isNan(xval)) return reduceTo(graph.falseConst()); // nan == y == false - if (yconst && Float.isNan(yval)) return reduceTo(graph.falseConst()); // x == nan == false + if (m.xconst && Float.isNan(m.xval)) return reduceTo(ssa.graph.falseConst()); // nan == y == false + if (m.yconst && Float.isNan(m.yval)) return reduceTo(ssa.graph.falseConst()); // x == nan == false } def visitFloatNe(opcode: Opcode.FloatNe, ft: FloatType) { norm_binop(app); - if (xconst && Float.isNan(xval)) return reduceTo(graph.trueConst()); // nan != y == true - if (yconst && Float.isNan(yval)) return reduceTo(graph.trueConst()); // x != nan == true + if (m.xconst && Float.isNan(m.xval)) return reduceTo(ssa.graph.trueConst()); // nan != y == true + if (m.yconst && Float.isNan(m.yval)) return reduceTo(ssa.graph.trueConst()); // x != nan == true } def visitIntViewI(opcode: Opcode.IntViewI, ft: IntType, tt: IntType) { - var xval = unop(app); - if (xconst) return reduceTo(graph.valConst(tt, Eval.doIntView(ft, tt, xval))); + var xval = m.unop(app); + if (m.xconst) return reduceTo(ssa.graph.valConst(tt, Eval.doIntView(ft, tt, xval))); } def visitRefEq(opcode: Opcode.RefEq, t: Type) { reduceEq(); } def visitTupleCreate(opcode: Opcode.TupleCreate, tt: TupleType) { if (op.isPolymorphic()) return; - n_op(app); - if (!fold) return; + m.n_op(app); + if (!m.fold) return; // (K, ..., K) => K var inputs = app.inputs; var vals = Array.new(inputs.length); for (j < inputs.length) vals[j] = SsaConst.!(inputs[j].dest).val; - return reduceTo(graph.valConst(op.sig.returnType(), BoxVal.new(vals))); + return reduceTo(ssa.graph.valConst(op.sig.returnType(), BoxVal.new(vals))); } def visitTupleGetElem(opcode: Opcode.TupleGetElem, tt: TupleType) { - var xval = unop(app); - if (xconst) { + var xval = m.unop(app); + if (m.xconst) { // fold K.N var val = BoxVal.!(xval), t = Lists.get(tt.nested, opcode.index); - return reduceTo(graph.valConst(t, if(val != null, val.values[opcode.index], null))); + return reduceTo(ssa.graph.valConst(t, if(val != null, val.values[opcode.index], null))); } - if (x.optag() == Opcode.TupleCreate.tag) { + if (m.x.optag() == Opcode.TupleCreate.tag) { // TupleGetElem[n](x:TupleCreate(...)) => x[n] - return reduceTo(SsaApplyOp.!(x).inputs[opcode.index].dest); + return reduceTo(SsaApplyOp.!(m.x).inputs[opcode.index].dest); } } def visitArrayAlloc(opcode: Opcode.ArrayAlloc, at: ArrayType) { @@ -689,62 +433,62 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (remove_pure_ops && app.useList == null) { // unused array allocation if (!app.facts.O_NO_NEGATIVE_CHECK) { var op = IntType.!(len.getType()).opLt(); - var check = addBinop(op, len, graph.zeroConst()); + var check = addBinop(op, len, ssa.graph.zeroConst()); return reduceTo(addConditionalThrow(app.source, V3Exception.LengthCheck, check)); } return reduceTo(killUnused(app)); } - state.init(app, ARRAY_LENGTH_FIELD, app.input0()); + if (state != null) state.init(app, ARRAY_LENGTH_FIELD, app.input0()); } def visitArrayInit(opcode: Opcode.ArrayInit, at: ArrayType) { if (op.isPolymorphic()) return; - n_op(app); + m.n_op(app); if (remove_pure_ops && app.useList == null) return reduceTo(killUnused(app)); // unused empty array init - if (true || !fold) return; // TODO: can fold array init if identity not observed + if (true || !m.fold) return; // TODO: can fold array init if identity not observed var record = context.prog.newRecord(at, app.inputs.length); for (j < app.inputs.length) record.values[j] = SsaConst.!(app.inputs[j].dest).val; - return reduceTo(graph.recordConst(record)); + return reduceTo(ssa.graph.recordConst(record)); } def visitArrayGetElem(opcode: Opcode.ArrayGetElem, at: ArrayType, it: IntType) { var receiver = boundscheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (!app.facts.O_PURE) return; var yval = norm_binop(app); // ArrayGetElem(#array, #index) => K var values: Array; - match (xval) { + match (m.xval) { x: Record => values = x.values; x: Address => values = x.val.values; _ => ; } - if (fold) return reduceTo(graph.valConst(app.getType(), values[V3.unboxI32(yval)])); + if (m.fold) return reduceTo(ssa.graph.valConst(app.getType(), values[V3.unboxI32(yval)])); } def visitArraySetElem(opcode: Opcode.ArraySetElem, at: ArrayType, it: IntType, et: Type) { var receiver = boundscheck(app); - if (state.end) return reduceTo(receiver); - if (optimize_loads) state.killArrayElems(); + if (end()) return reduceTo(receiver); + if (optimize_loads && state != null) state.killArrayElems(); } - def visitArraySetElemElem(opcode: Opcode.ArraySetElemElem, at: ArrayType, it: IntType, et: Type) { if (optimize_loads) state.killArrayElems(); } + def visitArraySetElemElem(opcode: Opcode.ArraySetElemElem, at: ArrayType, it: IntType, et: Type) { if (optimize_loads && state != null) state.killArrayElems(); } def visitArrayGetLength(opcode: Opcode.ArrayGetLength, at: ArrayType) { var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); - var xval = unop(app); - if (xconst) return reduceTo(graph.intConst(Record.!(xval).values.length)); + if (end()) return reduceTo(receiver); + var xval = m.unop(app); + if (m.xconst) return reduceTo(ssa.graph.intConst(Record.!(xval).values.length)); if (receiver.optag() == Opcode.ArrayInit.tag) { // ArrayGetLength(ArrayInit(...)) => |...| - return reduceTo(graph.intConst(receiver.inputs.length)); + return reduceTo(ssa.graph.intConst(receiver.inputs.length)); } if (receiver.optag() == Opcode.ArrayAlloc.tag) { // ArrayGetLength(ArrayAlloc(x)) => x return reduceTo(receiver.input0()); } - if (optimize_loads) return reduceTo(state.load(receiver, ARRAY_LENGTH_FIELD, app)); + if (optimize_loads && state != null) return reduceTo(state.load(receiver, ARRAY_LENGTH_FIELD, app)); } - def visitRangeSetElem(opcode: Opcode.RangeSetElem, rt: RangeType, it: IntType, et: Type) { if (optimize_loads) state.killArrayElems(); } - def visitNormRangeSetElem(opcode: Opcode.NormRangeSetElem, at: ArrayType, it: IntType) { if (optimize_loads) state.killArrayElems(); } - def visitNormRangeSetElemElem(opcode: Opcode.NormRangeSetElemElem, at: ArrayType, it: IntType) { if (optimize_loads) state.killArrayElems(); } + def visitRangeSetElem(opcode: Opcode.RangeSetElem, rt: RangeType, it: IntType, et: Type) { if (optimize_loads && state != null) state.killArrayElems(); } + def visitNormRangeSetElem(opcode: Opcode.NormRangeSetElem, at: ArrayType, it: IntType) { if (optimize_loads && state != null) state.killArrayElems(); } + def visitNormRangeSetElemElem(opcode: Opcode.NormRangeSetElemElem, at: ArrayType, it: IntType) { if (optimize_loads && state != null) state.killArrayElems(); } def visitInit(opcode: Opcode.Init) { - if (!optimize_inits) return; + if (!optimize_inits || state == null) return; for (l = state.inits; l != null; l = l.tail) { if (l.head == opcode.method) return reduceTo(null); // init[m]; init[m] => init[m] } @@ -755,68 +499,68 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (sf != null && sf.writability == Writability.READ_ONLY) { if (Literal.?(sf.init)) { // C.f => K var lit = Literal.!(sf.init); - var const = graph.valConst(lit.exactType, lit.val); + var const = ssa.graph.valConst(lit.exactType, lit.val); return reduceTo(if(lit.exactType != opcode.field.fieldType, convert(const, opcode.field.fieldType), const)); } } - if (optimize_loads) return reduceTo(state.load(null, opcode.field, app)); + if (optimize_loads && state != null) return reduceTo(state.load(null, opcode.field, app)); } def visitComponentSetField(opcode: Opcode.ComponentSetField, ct: ComponentType) { - if (optimize_loads) state.store(null, opcode.field, app, app.input1()); // forward stores + if (optimize_loads && state != null) state.store(null, opcode.field, app, app.input1()); // forward stores } def visitClassAlloc(opcode: Opcode.ClassAlloc, ct: ClassType) { if (remove_pure_ops && app.useList == null) return reduceTo(killUnused(app)); // unused empty alloc } def visitClassNew(opcode: Opcode.ClassNew, ct: ClassType) { - if (!app.facts.O_PURE) state.kill(); // XXX: Fact.O_NO_SIDEFFECT + if (!app.facts.O_PURE && state != null) state.kill(); // XXX: Fact.O_NO_SIDEFFECT } def visitClassGetField(opcode: Opcode.ClassGetField, ct: ClassType) { // XXX: optimize ClassGetField[descriptor](ClassAlloc(x, ...) => x var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (opcode.field.isConst()) { if (SsaConst.?(receiver)) { // ClassGetField(#K) => #K var record = asRecord(receiver); var ftype = opcode.field.fieldType; if (ftype.open()) ftype = IrSpec.new(record.rtype, TypeUtil.NO_TYPES, opcode.field).getFieldType(); - return reduceTo(graph.valConst(ftype, record.values[opcode.field.index])); + return reduceTo(ssa.graph.valConst(ftype, record.values[opcode.field.index])); } app.setFactIf(Fact.O_NO_NULL_CHECK, Fact.O_PURE); app.facts |= Fact.O_FOLDABLE; if (remove_pure_ops && app.facts.O_PURE && app.useList == null) return reduceTo(killUnused(app)); } - if (optimize_loads) return reduceTo(state.load(receiver, opcode.field, app)); + if (optimize_loads && state != null) return reduceTo(state.load(receiver, opcode.field, app)); } def visitClassInitField(opcode: Opcode.ClassInitField, ct: ClassType) { app.facts |= Fact.O_NO_NULL_CHECK; - if (optimize_loads) state.init(app.input0(), opcode.field, app.input1()); // forward stores + if (optimize_loads && state != null) state.init(app.input0(), opcode.field, app.input1()); // forward stores } def visitClassSetField(opcode: Opcode.ClassSetField, ct: ClassType) { var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); - if (optimize_loads) state.store(receiver, opcode.field, app, app.input1()); // forward stores + if (end()) return reduceTo(receiver); + if (optimize_loads && state != null) state.store(receiver, opcode.field, app, app.input1()); // forward stores } def visitClassGetMethod(opcode: Opcode.ClassGetMethod, meth: IrSpec) { var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (op.isPolymorphic()) return; - var xval = unop(app); - if (xconst) { + var xval = m.unop(app); + if (m.xconst) { // ClassGetMethod[m](#K) => (m, #K) var obj = Record.!(xval); - return reduceTo(graph.valConst(meth.getBoundType(), Closure.new(obj, meth))); + return reduceTo(ssa.graph.valConst(meth.getBoundType(), Closure.new(obj, meth))); } } def visitClassGetVirtual(opcode: Opcode.ClassGetVirtual, meth: IrSpec) { var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); var dv = devirtualize(meth, receiver); if (dv != null) { - var xval = unop(app); - if (xconst && !op.isPolymorphic()) { // ClassGetVirtual[m](K) => (m, K) + var xval = m.unop(app); + if (m.xconst && !op.isPolymorphic()) { // ClassGetVirtual[m](K) => (m, K) var obj = Record.!(xval); - return reduceTo(graph.valConst(dv.getBoundType(), Closure.new(obj, dv))); + return reduceTo(ssa.graph.valConst(dv.getBoundType(), Closure.new(obj, dv))); } return reduceTo(replaceOp(app, V3Op.newClassGetMethod(dv))); // ClassGetVirtual[m](K) => ClassGetMethod[m](K) } @@ -824,32 +568,32 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { // XXX: combine with ClassGetVirtual def visitClassGetSelector(opcode: Opcode.ClassGetSelector, meth: IrSpec) { var receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); var dv = devirtualize(meth, receiver); if (dv != null) { var val = if(!dv.member.flags.M_UNREACHABLE, FuncVal.new(dv)); - return reduceTo(graph.valConst(dv.getFuncType(), val)); + return reduceTo(ssa.graph.valConst(dv.getFuncType(), val)); } } def visitVariantEq(opcode: Opcode.VariantEq, vt: ClassType) { norm_binop(app); - if (fold) return reduceTo(graph.boolConst(Values.equal(xval, yval))); + if (m.fold) return reduceTo(ssa.graph.boolConst(Values.equal(m.xval, m.yval))); // XXX: look through type casts and subsumes for VariantEq - var and = x.facts & y.facts, or = x.facts | y.facts; - if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(graph.trueConst()); // 0 == 0 == true + var and = m.x.facts & m.y.facts, or = m.x.facts | m.y.facts; + if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(ssa.graph.trueConst()); // 0 == 0 == true } def visitVariantGetTag(opcode: Opcode.VariantGetTag, vt: ClassType) { - var xval = unop(app); - if (fold) { + var xval = m.unop(app); + if (m.fold) { // GetVariantTag(#K) => #K - if (Record.?(xval)) return reduceTo(graph.intConst(V3.getVariantTag(Record.!(xval).rtype))); - return reduceTo(graph.intConst(V3.unboxI32(xval))); + if (Record.?(xval)) return reduceTo(ssa.graph.intConst(V3.getVariantTag(Record.!(xval).rtype))); + return reduceTo(ssa.graph.intConst(V3.unboxI32(xval))); } } def visitVariantAlloc(opcode: Opcode.VariantAlloc, vt: ClassType) { if (op.isPolymorphic()) return; - n_op(app); - if (!fold) return; + m.n_op(app); + if (!m.fold) return; // VariantAlloc(#K, #K ..) => #K (variant) var ic = context.prog.ir.getIrClass(vt); if (ic == null) return; // TODO: can occur for closure classes @@ -872,17 +616,17 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { for (j < app.inputs.length) record.values[j] = SsaConst.!(app.inputs[j].dest).val; } if (ic != null) ic.flags |= IrFlag.C_HEAP; - return reduceTo(graph.recordConst(record)); + return reduceTo(ssa.graph.recordConst(record)); } def visitVariantGetField(opcode: Opcode.VariantGetField, vt: ClassType) { var receiver = app.input0(); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (SsaConst.?(receiver)) { // VariantGetField(#K) => #K var record = asRecord(receiver); var ftype = opcode.field.fieldType; if (ftype.open()) ftype = IrSpec.new(record.rtype, TypeUtil.NO_TYPES, opcode.field).getFieldType(); - return reduceTo(graph.valConst(ftype, if(record != null, record.values[opcode.field.index]))); + return reduceTo(ssa.graph.valConst(ftype, if(record != null, record.values[opcode.field.index]))); } if (!opcode.field.flags.F_POINTED_AT) app.facts |= Fact.O_PURE; if (receiver.optag() == Opcode.VariantAlloc.tag) { @@ -892,17 +636,17 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { // Ugly: avoid arity mismatch problems for tuple/void fields if (ic != null && r.inputs.length == ic.fields.length) return reduceTo(r.inputs[opcode.field.index].dest); } - if (optimize_loads) return reduceTo(state.load(receiver, opcode.field, app)); + if (optimize_loads && state != null) return reduceTo(state.load(receiver, opcode.field, app)); } def visitVariantGetMethod(opcode: Opcode.VariantGetMethod, meth: IrSpec) { var receiver = app.input0(); if (op.isPolymorphic()) return; - var xval = unop(app); - if (xconst) { + var xval = m.unop(app); + if (m.xconst) { // VariantGetMethod[m](#K) => (m, #K) var obj = Record.!(xval); var val = if(!meth.member.flags.M_UNREACHABLE, Closure.new(obj, meth)); - return reduceTo(graph.valConst(meth.getBoundType(), val)); + return reduceTo(ssa.graph.valConst(meth.getBoundType(), val)); } } // XXX: also optimize VariantGetSelector @@ -910,11 +654,11 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var receiver = app.input0(); var dv = devirtualize(meth, receiver); if (dv != null) { - var xval = unop(app); - if (xconst && !op.isPolymorphic()) { // VariantGetVirtual[m](K) => (m, K) + var xval = m.unop(app); + if (m.xconst && !op.isPolymorphic()) { // VariantGetVirtual[m](K) => (m, K) var obj = Record.!(xval); var val = if(!dv.member.flags.M_UNREACHABLE, Closure.new(obj, dv)); - return reduceTo(graph.valConst(dv.getBoundType(), val)); + return reduceTo(ssa.graph.valConst(dv.getBoundType(), val)); } return reduceTo(replaceOp(app, V3Op.newCreateClosure(dv, op.typeArgs[0]))); // VariantGetVirtual[m](K) => CreateClosure[m](K) } @@ -925,21 +669,21 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } def visitBoundsCheck(opcode: Opcode.BoundsCheck, t: Type) { var receiver = boundscheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (app.checkFact(Facts.O_SAFE_BOUNDS)) return reduceTo(Ssa.killInstr(app)); } def visitConditionalThrow(opcode: Opcode.ConditionalThrow) { - var xval = unop(app); - if (!fold) return; + var xval = m.unop(app); + if (!m.fold) return; // ConditionalThrow(#K) => throw | nop if (Bool.unbox(xval)) return reduceTo(addThrow(app.source, opcode.exception)); else return reduceTo(Ssa.killInstr(app)); } def visitOverloadedEq(opcode: Opcode.OverloadedEq, t: Type) { norm_binop(app); - if (fold) return reduceTo(graph.boolConst(Values.equal(xval, yval))); - var and = x.facts & y.facts; - if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(graph.trueConst()); // 0 == 0 == true + if (m.fold) return reduceTo(ssa.graph.boolConst(Values.equal(m.xval, m.yval))); + var and = m.x.facts & m.y.facts; + if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(ssa.graph.trueConst()); // 0 == 0 == true // XXX: look through type casts subsumes for OverloadedEq } def visitTypeCast(opcode: Opcode.TypeCast, f: Type, t: Type) { @@ -949,17 +693,17 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (CLOptions.PRINT_WRAP_EXTRA.get()) { FunctionWrapping.printWrap() .put3("SsaOptimizer TypeCast from %q to %q cast %s", ft.render, tt.render, cast.name) - .put2(" xconst %z # inputs %d", xconst, app.inputs.length).outln(); + .put2(" xconst %z # inputs %d", m.xconst, app.inputs.length).outln(); } if (app.inputs.length == 0) { // special cast of casting void -> X if (cast == TypeCast.THROW) return reduceTo(addThrow(app.source, V3Exception.TypeCheck)); // fold TypeCast return; } - var xval = unop(app); - if (xconst && cast != TypeCast.UNKNOWN_CAST) { + var xval = m.unop(app); + if (m.xconst && cast != TypeCast.UNKNOWN_CAST) { var r = Eval.doCast(cast, ft, tt, xval); - if (r.0) return reduceTo(graph.valConst(tt, r.1)); + if (r.0) return reduceTo(ssa.graph.valConst(tt, r.1)); else return reduceTo(addThrow(app.source, V3Exception.TypeCheck)); } @@ -967,25 +711,25 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { match (cast) { TRUE, SUBSUME => { if (context.compiler.NormConfig.WrapFuncTypeSubsume && Function_TypeCon.?(tt.typeCon)) return; - return reduceTo(x); // fold TypeCast + return reduceTo(m.x); // fold TypeCast } THROW => { return reduceTo(addThrow(app.source, V3Exception.TypeCheck)); // fold TypeCast } THROW_IF_NOT_NULL => { // TypeCast -> ConditionalThrow(x != null) - var cmp = addNot(addEqual(ft, x, graph.nullConst(ft))); + var cmp = addNot(addEqual(ft, m.x, ssa.graph.nullConst(ft))); addConditionalThrow(app.source, V3Exception.TypeCheck, cmp); - return reduceTo(graph.nullConst(tt)); + return reduceTo(ssa.graph.nullConst(tt)); } INT_VIEW_I => { return reduceTo(replaceOp(app, V3Op.newIntViewI(ft, tt))); } ENUM_TO_SET => { - return reduceTo(addEnumToSet(EnumSetType.!(tt), x)); + return reduceTo(addEnumToSet(EnumSetType.!(tt), m.x)); } _ => { - if (x.facts.V_NON_ZERO) { + if (m.x.facts.V_NON_ZERO) { app.facts |= Fact.O_NO_NULL_CHECK | Fact.V_NON_ZERO; } } @@ -995,110 +739,110 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var query = TypeSystem.newTypeQuery(ft, tt); if (app.inputs.length == 0) { // special cast of querying void -> X - if (query == TypeQuery.TRUE) return reduceTo(graph.trueConst()); - if (query == TypeQuery.FALSE) return reduceTo(graph.falseConst()); + if (query == TypeQuery.TRUE) return reduceTo(ssa.graph.trueConst()); + if (query == TypeQuery.FALSE) return reduceTo(ssa.graph.falseConst()); return; } - var xval = unop(app); - if (xconst && query != TypeQuery.UNKNOWN_QUERY) { - return reduceTo(graph.boolConst(Eval.doQuery(query, ft, tt, xval))); + var xval = m.unop(app); + if (m.xconst && query != TypeQuery.UNKNOWN_QUERY) { + return reduceTo(ssa.graph.boolConst(Eval.doQuery(query, ft, tt, xval))); } - if (query == TypeQuery.TRUE) return reduceTo(graph.trueConst()); - if (query == TypeQuery.FALSE) return reduceTo(graph.falseConst()); - if (query == TypeQuery.TRUE_IF_NOT_NULL) return reduceTo(addNotEqual(ft, x, graph.nullConst(ft))); - if (x.facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; + if (query == TypeQuery.TRUE) return reduceTo(ssa.graph.trueConst()); + if (query == TypeQuery.FALSE) return reduceTo(ssa.graph.falseConst()); + if (query == TypeQuery.TRUE_IF_NOT_NULL) return reduceTo(addNotEqual(ft, m.x, ssa.graph.nullConst(ft))); + if (m.x.facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; } def visitTypeSubsume(opcode: Opcode.TypeSubsume, ft: Type, tt: Type) { - var xval = unop(app); - if (xconst) return reduceTo(graph.valConst(tt, xval)); - if (ft == tt) return reduceTo(x); // fold TypeSubsume(x) - if (tt == x.getType()) return reduceTo(x); // fold TypeSubsume(x: T) + var xval = m.unop(app); + if (m.xconst) return reduceTo(ssa.graph.valConst(tt, xval)); + if (ft == tt) return reduceTo(m.x); // fold TypeSubsume(x) + if (tt == m.x.getType()) return reduceTo(m.x); // fold TypeSubsume(x: T) match (tt.typeCon.kind) { CLASS, VARIANT => { - if (ft == Null.TYPE) return reduceTo(graph.nullConst(tt)); - if (TypeSystem.isSubtype(ft, tt)) return reduceTo(x); // remove redundant subsume of class + if (ft == Null.TYPE) return reduceTo(ssa.graph.nullConst(tt)); + if (TypeSystem.isSubtype(ft, tt)) return reduceTo(m.x); // remove redundant subsume of class } ENUM_SET => { - return reduceTo(addEnumToSet(EnumSetType.!(tt), x)); + return reduceTo(addEnumToSet(EnumSetType.!(tt), m.x)); } _ => ; // XXX: int subsume and tuples } - if (x.facts.V_NON_ZERO) { + if (m.x.facts.V_NON_ZERO) { app.facts |= Fact.O_NO_NULL_CHECK | Fact.V_NON_ZERO; } } def visitCallMethod(opcode: Opcode.CallMethod, spec: IrSpec) { app.facts |= opcode.method.facts & Fact.O_PURE; if (app.inputs.length > 0 && app.input0().facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; - state.kill(); + if (state != null) state.kill(); } def visitCallClassMethod(opcode: Opcode.CallClassMethod, spec: IrSpec) { app.facts |= opcode.method.facts & Fact.O_PURE; var target = nullcheck(app); - state.kill(); - if (SsaThrow.?(target) || state.end) return reduceTo(target); + if (state != null) state.kill(); + if (SsaThrow.?(target) || end()) return reduceTo(target); var x = app.input0(); - if (x == null) Terminal.put2("@%d:CallClassMethod(0) == null, end=%z\n", app.uid, state.end); + if (x == null) Terminal.put2("@%d:CallClassMethod(0) == null, end=%z\n", app.uid, end()); if (app.inputs.length > 0 && app.input0().facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; } def visitCallClassVirtual(opcode: Opcode.CallClassVirtual, meth: IrSpec) { var receiver = app.input0(); receiver = nullcheck(app); - if (state.end) return reduceTo(receiver); + if (end()) return reduceTo(receiver); if (app.input0().facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; var dv = devirtualize(meth, receiver); - state.kill(); + if (state != null) state.kill(); if (dv != null) return reduceTo(replaceOp(app, V3Op.newCallClassMethod(dv))); // CallClassVirtual[m](K) => CallClassMethod[m](k) } def visitCallVariantVirtual(opcode: Opcode.CallVariantVirtual, meth: IrSpec) { var receiver = app.input0(); if (app.input0().facts.V_NON_ZERO) app.facts |= Fact.O_NO_NULL_CHECK; var dv = devirtualize(meth, receiver); - state.kill(); + if (state != null) state.kill(); if (dv != null) return reduceTo(replaceOp(app, V3Op.newCallMethod(dv))); // CallVariantVirtual[m](K) => CallMethod[m](k) } // XXX: optimize CallVariantSelector - def visitCallVariantSelector(opcode: Opcode.CallVariantSelector, spec: IrSpec) { state.kill(); } + def visitCallVariantSelector(opcode: Opcode.CallVariantSelector, spec: IrSpec) { if (state != null) state.kill(); } def visitCallClosure(opcode: Opcode.CallClosure, ft: FuncType) { var target = nullcheck(app); - state.kill(); - if (SsaThrow.?(target) || state.end) return reduceTo(target); - var xval = unop(app), xop: Operator; + if (state != null) state.kill(); + if (SsaThrow.?(target) || end()) return reduceTo(target); + var xval = m.unop(app), xop: Operator; var i_recvr: SsaInstr, newOp: Operator, newFacts: Fact.set; - if (xconst) { + if (m.xconst) { var d = Closure.!(xval); if (d == null) return reduceTo(addThrow(app.source, V3Exception.NullCheck)); // CallClosure(K) => CallMethod[K.m] - i_recvr = graph.valConst(d.memberRef.getClosureReceiver(), d.val); + i_recvr = ssa.graph.valConst(d.memberRef.getClosureReceiver(), d.val); newOp = V3Op.newCallMethod(d.memberRef); newFacts = Fact.O_NO_NULL_CHECK; - } else if (SsaApplyOp.?(x)) match ((xop = SsaApplyOp.!(x).op).opcode) { + } else if (SsaApplyOp.?(m.x)) match ((xop = SsaApplyOp.!(m.x).op).opcode) { // XXX: look through TypeCast / TypeSubsume of functions ClassGetMethod(method) => { // CallClosure(GetClassMethod[m]) => CallMethod[m] var spec = V3Op.extractIrSpec(xop, method); newOp = V3Op.newCallMethod(spec); - i_recvr = x.input0(); + i_recvr = m.x.input0(); newFacts = app.facts & Fact.O_NO_NULL_CHECK; } ClassGetVirtual(method) => { // CallClosure(GetClassVirtual[m]) => CallClassVirtual[m] var spec = V3Op.extractIrSpec(xop, method); newOp = V3Op.newCallClassVirtual(spec); - i_recvr = x.input0(); + i_recvr = m.x.input0(); newFacts = app.facts & Fact.O_NO_NULL_CHECK; } CreateClosure(method) => { // CallClosure(CreateClosure[m](p), ...) => CallMethod[m](p, ...); var spec = V3Op.extractIrSpec(xop, method); newOp = V3Op.newCallMethod(spec); - i_recvr = if (V3.isComponent(spec.receiver), graph.nullReceiver(), x.input0()); + i_recvr = if (V3.isComponent(spec.receiver), ssa.graph.nullReceiver(), m.x.input0()); if (V3.isComponent(spec.receiver)) newFacts |= Fact.O_NO_NULL_CHECK; - else newFacts |= x.facts & Fact.O_NO_NULL_CHECK; + else newFacts |= m.x.facts & Fact.O_NO_NULL_CHECK; } _ => { - if (!app.facts.O_PURE) state.kill(); + if (!app.facts.O_PURE && state != null) state.kill(); } } if (newOp != null) return reduceTo(replaceWithDirectCall(app, newOp, i_recvr, app.inputs[1 ...]).setFact(newFacts)); @@ -1106,9 +850,9 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { def visitCallFunction(opcode: Opcode.CallFunction, ft: FuncType) { var target = nullcheck(app); if (SsaThrow.?(target)) return reduceTo(target); - state.kill(); - var xval = unop(app); - if (xconst && FuncVal.?(xval)) { + if (state != null) state.kill(); + var xval = m.unop(app); + if (m.xconst && FuncVal.?(xval)) { var d = FuncVal.!(xval); if (d == null) return reduceTo(addThrow(app.source, V3Exception.NullCheck)); // CallFunction(#K) => CallMethod @@ -1119,22 +863,22 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } def visitCreateClosure(opcode: Opcode.CreateClosure, spec: IrSpec) { if (op.isPolymorphic()) return; - var xval = unop(app); - if (!fold) return; + var xval = m.unop(app); + if (!m.fold) return; // CreateClosure(#K) => #K - return reduceTo(graph.valConst(spec.getBoundType(), Closure.new(xval, spec))); + return reduceTo(ssa.graph.valConst(spec.getBoundType(), Closure.new(xval, spec))); } - def visitRefLayoutSetField(opcode: Opcode.RefLayoutSetField, rt: RefType, ft: Type) { if (optimize_loads) state.killArrayElems(); } - def visitRefLayoutSetRepeatedField(opcode: Opcode.RefLayoutSetRepeatedField, rt: RefType, ft: Type) { if (optimize_loads) state.killArrayElems(); } - def visitByteArraySetField(opcode: Opcode.ByteArraySetField, ft: Type, startType: Type) { if (optimize_loads) state.killArrayElems(); } - def visitSystemCall(opcode: Opcode.SystemCall) { state.kill(); } + def visitRefLayoutSetField(opcode: Opcode.RefLayoutSetField, rt: RefType, ft: Type) { if (optimize_loads && state != null) state.killArrayElems(); } + def visitRefLayoutSetRepeatedField(opcode: Opcode.RefLayoutSetRepeatedField, rt: RefType, ft: Type) { if (optimize_loads && state != null) state.killArrayElems(); } + def visitByteArraySetField(opcode: Opcode.ByteArraySetField, ft: Type, startType: Type) { if (optimize_loads && state != null) state.killArrayElems(); } + def visitSystemCall(opcode: Opcode.SystemCall) { if (state != null) state.kill(); } def visitPtrAdd(opcode: Opcode.PtrAdd, pt: Type, it: IntType) { - binop(app); - if (yzero) return reduceTo(x); - if (!fold) return; - if (!Addr.?(xval)) return; - var a = Addr.!(xval); - match (yval) { + m.binop(app); + if (m.yzero) return reduceTo(m.x); + if (!m.fold) return; + if (!Addr.?(m.xval)) return; + var a = Addr.!(m.xval); + match (m.yval) { yi: Box => { // TODO(addr64) var na: Addr; if (a != null) { @@ -1143,28 +887,30 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { na = Addr.new(MachProgram.!(context.prog.tprog).dataRegion, null, yi.val); na.absolute = yi.val; } - if (na != null) return reduceTo(graph.valConst(app.getType(), na)); + if (na != null) return reduceTo(ssa.graph.valConst(app.getType(), na)); } } } def visitPtrCmpSwp(opcode: Opcode.PtrCmpSwp, pt: PointerType, vt: Type) { nullcheck(app); - state.kill(); + if (state != null) state.kill(); } // XXX: PtrAdd, PtrSub, PtrLt def visitPtrLoad(opcode: Opcode.PtrLoad, pt: Type, vt: Type) { var ptr = nullcheck(app); // XXX: use Fact.O_FOLDABLE to indicate a load of an immutable value - if (optimize_loads) { + if (optimize_loads && state != null) { return reduceTo(state.loadPtr(ptr, vt, app)); } } def visitPtrStore(opcode: Opcode.PtrStore, pt: Type, vt: Type) { var ptr = nullcheck(app); - state.killArrayElems(); - state.killPointedAt(); + if (state != null) { + state.killArrayElems(); + state.killPointedAt(); + } // XXX: use a fact to indicate an initializing store that cannot kill previous loads - if (optimize_loads) { + if (optimize_loads && state != null) { state.storePtr(ptr, vt, app, app.input1()); } } @@ -1175,19 +921,19 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (target == null) return reduceTo(addThrow(app.source, V3Exception.NullCheck)); // XXX: CallAddress(#K) => CallMethod } - state.kill(); + if (state != null) state.kill(); } - def visitCallKernel(opcode: Opcode.CallKernel, typeArgs: Array) { state.kill(); } - def visitDefaultOp(op: Operator) { if (!app.facts.O_PURE) state.kill(); } + def visitCallKernel(opcode: Opcode.CallKernel, typeArgs: Array) { if (state != null) state.kill(); } + def visitDefaultOp(op: Operator) { if (!app.facts.O_PURE && state != null) state.kill(); } def reduceEq() { def ABOVE_BELOW_ZERO = Fact.V_NON_ZERO | Fact.V_NON_NEGATIVE | Fact.V_BELOW_ZERO; norm_binop(app); - if (fold) return reduceTo(graph.boolConst(Values.equal(xval, yval))); - if (x == y) return reduceTo(graph.trueConst()); + if (m.fold) return reduceTo(ssa.graph.boolConst(Values.equal(m.xval, m.yval))); + if (m.x == m.y) return reduceTo(ssa.graph.trueConst()); // XXX: look through type casts and subsumes for RefEq - var and = x.facts & y.facts, or = x.facts | y.facts; - if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(graph.trueConst()); // 0 == 0 == true - if (ABOVE_BELOW_ZERO == (or & ABOVE_BELOW_ZERO)) return reduceTo(graph.falseConst()); // neg == pos == false + var and = m.x.facts & m.y.facts, or = m.x.facts | m.y.facts; + if (Facts.NONE != (and & Fact.V_ZERO)) return reduceTo(ssa.graph.trueConst()); // 0 == 0 == true + if (ABOVE_BELOW_ZERO == (or & ABOVE_BELOW_ZERO)) return reduceTo(ssa.graph.falseConst()); // neg == pos == false } def tryEval(apply: SsaApplyOp) -> SsaInstr { for (i in apply.inputs) if (!SsaConst.?(i.dest)) return null; // only evaluate if all inputs are constants @@ -1208,18 +954,18 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } return addThrow(apply.source, ex.error); } - val: Val => return graph.valConst(apply.getType(), val); -//TODO null => return graph.valConst(apply.getType(), null); + val: Val => return ssa.graph.valConst(apply.getType(), val); +//TODO null => return ssa.graph.valConst(apply.getType(), null); _ => return null; } } def norm_binop(i: SsaApplyOp) -> Val { if (i.inputs.length != 2) norm2(i); - return binop(i); + return m.binop(i); } def norm_intbinop(i: SsaApplyOp) -> int { if (i.inputs.length != 2) norm2(i); - return intbinop(i); + return m.intbinop(i); } def norm2(i: SsaApplyOp) { var t = i.input0(); @@ -1231,15 +977,15 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { i.setInputs([x, y]); } def fold4b(s: (int, int) -> bool, u: (u32, u32) -> bool, sl: (long, long) -> bool, ul: (u64, u64) -> bool) -> SsaInstr { - if (!wide) return graph.boolConst(if(inttype.signed, s(xint, yint), u(u32.view(xint), u32.view(yint)))); - else return graph.boolConst(if(inttype.signed, sl(xlong, ylong), ul(u64.view(xlong), u64.view(ylong)))); + if (!m.wide) return ssa.graph.boolConst(if(m.inttype.signed, s(m.xint, m.yint), u(u32.view(m.xint), u32.view(m.yint)))); + else return ssa.graph.boolConst(if(m.inttype.signed, sl(m.xlong, m.ylong), ul(u64.view(m.xlong), u64.view(m.ylong)))); } def fold4i(s: (int, int) -> int, u: (u32, u32) -> u32, sl: (long, long) -> long, ul: (u64, u64) -> u64) -> SsaInstr { - if (!wide) return if(inttype.signed, i_const(s(xint, yint)), u_const(u(u32.view(xint), u32.view(yint)))); - else return if(inttype.signed, l_const(sl(xlong, ylong)), lu_const(ul(u64.view(xlong), u64.view(ylong)))); + if (!m.wide) return if(m.inttype.signed, i_const(s(m.xint, m.yint)), u_const(u(u32.view(m.xint), u32.view(m.yint)))); + else return if(m.inttype.signed, l_const(sl(m.xlong, m.ylong)), lu_const(ul(u64.view(m.xlong), u64.view(m.ylong)))); } def fold2i(s: (int, int) -> int, sl: (long, long) -> long) -> SsaInstr { - return if(wide, l_const(sl(xlong, ylong)), i_const(s(xint, yint))); + return if(m.wide, l_const(sl(m.xlong, m.ylong)), i_const(s(m.xint, m.yint))); } def replaceWithDirectCall(i_old: SsaApplyOp, newOp: Operator, i_recvr: SsaInstr, ai_old: Range) -> SsaInstr { @@ -1252,7 +998,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { match (numParams) { 0 => ; // nothing to do 1 => { - if (ai_old.length == 0) ai_new[i++] = graph.nullConst(paramTypes[i]); + if (ai_old.length == 0) ai_new[i++] = ssa.graph.nullConst(paramTypes[i]); else if (ai_old.length == 1) ai_new[i++] = ai_old[0].dest; else { var tt = TupleType.!(paramTypes[i]); @@ -1295,16 +1041,16 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } def i_const(v: int) -> SsaInstr { - return graph.intConst(Eval.doIntTrunc32(inttype, v)); + return ssa.graph.intConst(Eval.doIntTrunc32(m.inttype, v)); } def u_const(v: u32) -> SsaInstr { - return graph.intConst(Eval.doIntTrunc32(inttype, int.view(v))); + return ssa.graph.intConst(Eval.doIntTrunc32(m.inttype, int.view(v))); } def l_const(v: long) -> SsaInstr { - return graph.valConst(inttype, Long.box(Eval.doIntTrunc64(inttype, v))); + return ssa.graph.valConst(m.inttype, Long.box(Eval.doIntTrunc64(m.inttype, v))); } def lu_const(v: u64) -> SsaInstr { - return graph.valConst(inttype, Long.box(Eval.doIntTrunc64(inttype, long.view(v)))); + return ssa.graph.valConst(m.inttype, Long.box(Eval.doIntTrunc64(m.inttype, long.view(v)))); } def shiftcheck(i: SsaApplyOp, y: SsaInstr) -> SsaInstr { var bound = posIntBound(y); @@ -1317,34 +1063,34 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var receiver = nullcheck(i); if (!optimize_bounds) return i; // XXX: introduce partial bounds checks when length is a known constant. - if (state.end) return receiver; - if (findPriorBoundsCheck(i, receiver, i.input1())) return receiver; + if (end()) return receiver; + if (state != null && findPriorBoundsCheck(i, receiver, i.input1())) return receiver; // Initialize the match state for {x} and {y}. - var yval = binop(i); - if (yconst && V3.unboxI32(yval) < 0) { + var yval = m.binop(i); + if (m.yconst && V3.unboxI32(yval) < 0) { // will always throw out of bounds, but might need a null check first. if (!i.facts.O_NO_NULL_CHECK) addExplicitNullCheck(i.source, receiver); return addThrow(i.source, V3Exception.BoundsCheck); } // Compute the constant length of the array if possible. var length = -1; - if (xconst) { - match (xval) { + if (m.xconst) { + match (m.xval) { x: Record => length = x.values.length; x: Address => length = x.val.values.length; _ => return receiver; } - } else if (x.optag() == Opcode.ArrayAlloc.tag) { - var l = x.inputs[0].dest; + } else if (m.x.optag() == Opcode.ArrayAlloc.tag) { + var l = m.x.inputs[0].dest; if (!SsaConst.?(l)) return receiver; length = l.unbox(); - } else if (x.optag() == Opcode.ArrayInit.tag) { - length = x.inputs.length; + } else if (m.x.optag() == Opcode.ArrayInit.tag) { + length = m.x.inputs.length; } else { return receiver; } - if (yconst) { + if (m.yconst) { // Array length and index are constants. Static bounds check. var index = V3.unboxI32(yval); if (index < length) { @@ -1354,7 +1100,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { return addThrow(i.source, V3Exception.BoundsCheck); } // Array length is a constant, try range analysis of the index. - var bound = posIntBound(y); + var bound = posIntBound(m.y); if (bound >= 0 && bound < length) i.facts |= Facts.O_SAFE_BOUNDS; return receiver; } @@ -1381,7 +1127,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { } def addExplicitNullCheck(source: Source, receiver: SsaInstr) -> SsaInstr { var t = receiver.getType(); - var check = add(source, V3Op.newEqual(t), [receiver, graph.nullConst(t)]); + var check = add(source, V3Op.newEqual(t), [receiver, ssa.graph.nullConst(t)]); return add(source, V3Op.newConditionalThrow(V3Exception.NullCheck), [check]); } def posIntBound(x: SsaInstr) -> int { @@ -1442,7 +1188,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { return receiver; } if (receiver.facts.V_ZERO) return addThrow(i.source, V3Exception.NullCheck); - if (!optimize_nullchecks) return receiver; + if (!optimize_nullchecks || state == null) return receiver; for (l = state.nonnull; l != null; l = l.tail) { if (l.head == receiver) { i.facts |= Fact.O_NO_NULL_CHECK; @@ -1488,10 +1234,9 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { // Editing helpers //====================================================================== def addThrow(source: Source, exception: string) -> SsaInstr { - if (state.end) return null; - state.end = true; - var cfopt = SsaCfOptimizer.new(context); - return cfopt.insertThrow(prev.next, source, exception, null); + if (end()) return null; + ssa.addThrow(source, exception); + return ssa.graph.nop(); } def addConditionalThrow(source: Source, exception: string, cond: SsaInstr) -> SsaInstr { return add(source, V3Op.newConditionalThrow(exception), [cond]); @@ -1509,46 +1254,42 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { def convert(i: SsaConst, t: Type) -> SsaInstr { if (i.vtype == t) return i; var r = Eval.doCast0(i.vtype, t, i.val); - return graph.valConst(t, r.1); + return ssa.graph.valConst(t, r.1); } def addEqual(t: Type, x: SsaInstr, y: SsaInstr) -> SsaInstr { - return if(!state.end, addBinop(V3Op.newEqual(t), x, y)); + return if(!end(), addBinop(V3Op.newEqual(t), x, y)); } def addNotEqual(t: Type, x: SsaInstr, y: SsaInstr) -> SsaInstr { - return if(!state.end, addNot(addBinop(V3Op.newEqual(t), x, y))); + return if(!end(), addNot(addBinop(V3Op.newEqual(t), x, y))); } def addNot(x: SsaInstr) -> SsaInstr { - return if(!state.end, add(null, V3Op.opBoolNot, [x])); + return if(!end(), add(null, V3Op.opBoolNot, [x])); } def addEnumToSet(tc: EnumSetType, x: SsaInstr) -> SsaInstr { // XXX: reduce duplication with V3EnumSet_TypeCon - var one = graph.valConst(tc.repType, tc.repType.box(1)); + var one = ssa.graph.valConst(tc.repType, tc.repType.box(1)); var i = add(null, tc.repType.opShl(), [one, x]); i.facts |= Fact.O_NO_SHIFT_CHECK; return i; } + // Inserts a new instruction, which is not optimized again. def add(source: Source, op: Operator, args: Array) -> SsaInstr { - if (state.end) return null; + if (end()) return null; var i = SsaApplyOp.new(source, op, args); - i.insertBefore(prev.next); i.facts |= Opcodes.facts(op.opcode); - prev = i; + ssa.append(i); return i; } def killUnused(i_old: SsaInstr) -> SsaInstr { - if (i_old.useList != null) i_old.replace(graph.nullConst(i_old.getType())); + if (i_old.useList != null) i_old.replace(ssa.graph.nullConst(i_old.getType())); i_old.kill(); i_old.remove(); - return graph.nop(); + return ssa.graph.nop(); } -} - -// Performs control flow optimizations on a completed SsaGraph. -class SsaCfOptimizer(context: SsaContext) { - def mark = ++context.graph.markGen; - def queue = Vector.new(); - - def optimize() { + //====================================================================== + // Control flow optimizations on a completed graph + //====================================================================== + def optControlFlow() { // visit all the blocks of the graph // 1. reduce phis with identical inputs // 2. merge straight-line blocks (may introduce critical edges) @@ -1556,6 +1297,8 @@ class SsaCfOptimizer(context: SsaContext) { // 4. XXX iteratively reduce/fold code // 5. collapse if-chains into switches, esp if(T.?(e)) ... // 6. XXX remove useless branches + mark = ++context.graph.markGen; + queue.resize(0); queueBlock(context.graph.startBlock); for (i < queue.length) optBlock(queue[i]); } @@ -1779,45 +1522,16 @@ class SsaCfOptimizer(context: SsaContext) { c.cmp.kill(); c.cmp.remove(); } - - // Replace the first if with a switch. - block.append(sw); - } - def killBlock(block: SsaBlock) { - if (block.facts.O_KILLED) return; - // Recursively kill this block and its successor blocks - block.facts |= Fact.O_KILLED; - var succs = block.succs(); - var i = block.next; - while (i != null) { - // Kill all instructions in the block - var n = i.next; - i.next = null; - i.prev = null; - if (SsaInstr.?(i)) { - var j = SsaInstr.!(i); - j.kill(); - j.facts |= Fact.O_KILLED; - } - i = n; - } - killSuccs(succs); + + // Replace the first if with a switch. + block.append(sw); } + // Kills the blocks reachable only through {edges}, after removing {edges}. def killSuccs(edges: Array) { - // kill the outgoing control flow edges - var succs = Vector.new(); - for (s in edges) { - var d = s.dest; - if (d != null) { - Ssa.removeEdge(s, true); - succs.put(d); - } - } - // kill any dead successor blocks. - for (i < succs.length) { - var d = succs[i]; - if (!d.hasPred()) killBlock(d); - } + Ssa.killSuccs(edges); + } + def killBlock(block: SsaBlock) { + Ssa.killBlock(block); } def optEdge(edge: SsaCfEdge) -> bool { var src = edge.src, dest = edge.dest; @@ -1954,23 +1668,257 @@ class SsaCfOptimizer(context: SsaContext) { if (!dest.hasPred()) killBlock(dest); } } - def insertThrow(next: SsaLink, source: Source, exception: string, vt: Type) -> SsaInstr { - var t = SsaThrow.new(source, exception); - t.insertBefore(next); - // kill the rest of the instructions in the block - for (p = t.next; !SsaBlock.?(p); ()) { - var n = p.next; - p.remove(); - if (SsaEnd.?(p)) { - var end = SsaEnd.!(p); - killSuccs(end.succs); - Ssa.killInstr(end); - break; +} + +def ARRAY_LENGTH_FIELD = IrField.new(V3.stringType, Int.TYPE).setFlag(IrFlag.F_VALUE); + +// Matches various patterns of SSA instructions, recording the state of the match +// in the exposed fields. +class SsaInstrMatcher { + var x: SsaInstr; // First input + var y: SsaInstr; // Second input + var xconst: bool; // true if {x} is a constant + var yconst: bool; // true if {y} is a constant + var fold: bool; // true if all inputs are constants + var wide: bool; // true if this is a long int operation + var xval: Val; // if {x} is a constant, then its value + var yval: Val; // if {y} is a constant, then its value + var xint: int; // if {x} is an int constant, then its value + var yint: int; // if {y} is an int constant, then its value + var xlong: long; // if {x} is a long constant, then its value + var ylong: long; // if {y} is a long constant, then its value + var yzero: bool; // true if {y} is zero + var inttype: IntType; // if this is an integer operation, the result type + + // Resets this matcher + def clear() { + x = y = null; + xval = yval = null; + inttype = null; + xconst = yconst = yzero = fold = wide = false; + xint = yint = 0; + xlong = ylong = 0; + } + // Initializes the match state for a binary operation and returns {yval}. + def binop(i: SsaApplyOp) -> Val { + x = i.input0(); + y = i.input1(); + yzero = y.facts.V_ZERO; + fold = false; + if (xconst = SsaConst.?(x)) { + xval = SsaConst.!(x).val; + if (yconst = SsaConst.?(y)) { + fold = true; + return yval = SsaConst.!(y).val; + } else if (i.facts.O_COMMUTATIVE) { + i.inputs[0].update(y); + i.inputs[1].update(x); + xconst = false; + yconst = true; + var t = x; + x = y; + y = t; + yval = xval; + xval = null; + yzero = y.facts.V_ZERO; + return yval; + } else { + return yval = null; + } + } + return yval = if(yconst = SsaConst.?(y), SsaConst.!(y).val); + } + // Initializes the match state for an integer binary operation and returns {yint}. + def intbinop(i: SsaApplyOp) -> int { + binop(i); + inttype = IntType.!(i.op.sig.paramTypes[0]); + if (inttype.width <= 32) { + wide = false; + if (xconst) xint = V3.unboxI32(xval); + if (yconst) { + yint = V3.unboxI32(yval); + yzero = yint == 0; + } + } else { + wide = true; + if (xconst) xlong = Long.unboxSU(xval, inttype.signed); + if (yconst) { + ylong = Long.unboxSU(yval, inttype.signed); + yzero = ylong == 0; + } + } + return yint; + } + // Initializes the match state for an integer binary operation and returns {yint}. + def boolbinop(i: SsaApplyOp) -> int { + binop(i); + inttype = null; + if (xconst) xint = if(Bool.unbox(xval), 1); + if (yconst) { + yint = if(Bool.unbox(yval), 1); + yzero = yint == 0; + } + return yint; + } + // Initializes the match state for a unary operation and returns {xval}. + def unop(i: SsaApplyOp) -> Val { + x = i.input0(); + return xval = if(fold = xconst = SsaConst.?(x), SsaConst.!(x).val); + } + // Initializes the match state for an n-ary operation and returns {xval}. + def n_op(i: SsaApplyOp) -> Val { + fold = true; + var max = i.inputs.length; + for (j < max) { + if (!SsaConst.?(i.inputs[j].dest)) return (fold = false, null).last; + } + if (max >= 1) { + x = i.input0(); + xconst = true; + xval = SsaConst.!(x).val; + } + if (max >= 2) { + y = i.input1(); + yconst = true; + yval = SsaConst.!(y).val; + } + return xval; + } + def yis(val: int) -> bool { + return yconst && if(wide, ylong == val, yint == val); + } + def yrange(min: int, max: int) -> bool { + if (Debug.PARANOID && !yconst) return V3.fail("not const"); + if (wide) return min <= ylong && ylong <= max; + else return min <= yint && yint <= max; + } + def yisPowerOf2() -> bool { + return yconst && if(wide, (ylong & (ylong - 1)) == 0, (yint & (yint - 1)) == 0); + } + def ylog2() -> int { + if (Debug.PARANOID && !yconst) return V3.fail("not const"); + return if(wide, Longs.log(u64.view(ylong)), Ints.log(u32.view(yint))); + } +} + +// Flow-sensitive state for local optimizations. +class SsaBlockState { + var inbounds: List<(SsaInstr, SsaInstr)>; + var nonnull: List; + var inits: List; + def pure_loads = Vector<(SsaInstr, IrMember, SsaInstr)>.new(); + def impure_loads = Vector<(SsaInstr, IrMember, SsaInstr)>.new(); + def ptr_loads = Vector<(SsaInstr, Type, SsaInstr)>.new(); + + def clear() -> this { + nonnull = null; + inits = null; + inbounds = null; + pure_loads.resize(0); + impure_loads.resize(0); + ptr_loads.resize(0); + } + def kill() -> this { + impure_loads.resize(0); + ptr_loads.resize(0); + } + def load(obj: SsaInstr, m: IrMember, apply: SsaApplyOp) -> SsaInstr { +//TODO:dce if (apply.useList == null && apply.facts.O_NO_NULL_CHECK) { +// apply.replace(null); +// return context.graph.zeroConst(); // XXX: recursively delete dead code +// } + var val = find(obj, m); + if (val != null) return val; + if (m.isConst() || IrMethod.?(m) && !m.flags.F_POINTED_AT) pure_loads.put(obj, m, apply); + else impure_loads.put(obj, m, apply); + return apply; + } + def loadPtr(ptr: SsaInstr, loadType: Type, apply: SsaApplyOp) -> SsaInstr { + var prev = find0(ptr_loads, ptr, loadType); + if (prev != null) return prev; + ptr_loads.put(ptr, loadType, apply); + return apply; + } + def store(obj: SsaInstr, field: IrField, apply: SsaApplyOp, val: SsaInstr) -> SsaInstr { + var prev = find(obj, field); + if (prev == val) { // redundant store of previous value to field + apply.remove(); + apply.kill(); + return null; + } + killField(field); + impure_loads.put(obj, field, val); + return apply; + } + def storePtr(ptr: SsaInstr, storeType: Type, apply: SsaApplyOp, val: SsaInstr) -> SsaInstr { + var prev = find0(ptr_loads, ptr, storeType); + if (prev == val) { // redundant store of previous value to field + apply.remove(); + apply.kill(); + return null; + } + ptr_loads.resize(0); // XXX: improve pointer alias analysis, distinguish initializing stores + if (!isNarrowingStore(storeType, val)) ptr_loads.put(ptr, storeType, val); // XXX: cache narrowing stores properly + return apply; + } + def isNarrowingStore(storeType: Type, val: SsaInstr) -> bool { + match (storeType) { + x: PrimType => match (val.getType()) { + y: PrimType => return x.width != y.width; + } + } + return false; + } + def init(obj: SsaInstr, f: IrField, val: SsaInstr) { + if (f.isConst() && !f.flags.F_POINTED_AT) pure_loads.put(obj, f, val); + else impure_loads.put(obj, f, val); + } + def copy() -> SsaBlockState { + var that = SsaBlockState.new(); + that.inbounds = this.inbounds; + that.nonnull = this.nonnull; + that.inits = this.inits; + that.pure_loads.putv(this.pure_loads); + that.impure_loads.putv(this.impure_loads); + that.ptr_loads.putv(this.ptr_loads); + return that; + } + def find(obj: SsaInstr, m: IrMember) -> SsaInstr { + var r = find0(pure_loads, obj, m); + if (r == null) r = find0(impure_loads, obj, m); + return r; + } + def find0(v: Vector<(SsaInstr, T, SsaInstr)>, obj: SsaInstr, m: T) -> SsaInstr { + for (i < v.length) { + var t = v[i]; + if (t.0 == obj && t.1 == m) return t.2; + } + return null; + } + def killField(f: IrField) { + var v = impure_loads, i = 0; + for (j < v.length) { + var t = v[i]; + if (t.1 != f) { + if (i != j) v[i] = t; + i++; + } + } + v.resize(i); + } + def killPointedAt() { + var v = impure_loads, i = 0; + for (j < v.length) { + var t = v[i]; + if (!t.1.flags.F_POINTED_AT) { + if (i != j) v[i] = t; + i++; } - if (SsaInstr.?(p)) Ssa.killInstr(SsaInstr.!(p)); - p = n; } - return if(vt != null, context.graph.valConst(vt, Values.BOTTOM), context.graph.nop()); + v.resize(i); + } + def killArrayElems() { + ptr_loads.resize(0); // assume pointer loads could alias array elements. } } class SwitchCmp(val: int, cmp: SsaInstr, end: SsaIf, tSucc: SsaCfEdge, fSucc: SsaCfEdge) { } diff --git a/aeneas/src/ssa/SsaRebuilder.v3 b/aeneas/src/ssa/SsaRebuilder.v3 index 663b7e0a5..0eb519ce5 100644 --- a/aeneas/src/ssa/SsaRebuilder.v3 +++ b/aeneas/src/ssa/SsaRebuilder.v3 @@ -9,6 +9,7 @@ class SsaRebuilder(context: SsaContext) extends SsaInstrVisitor { var blockStartMap: PartialMap; var blockEndMap: PartialMap; var ssa: SsaBuilder; + var opt: SsaOptimizer; // if non-null, optimizes instructions as they are added var edgeMapMap: PartialMap>; var blocks: int; // var trace: bool = false; @@ -20,10 +21,12 @@ class SsaRebuilder(context: SsaContext) extends SsaInstrVisitor { def genSingleBlock(oldStart: SsaBlock, newStart: SsaBlock) { this.ssa = SsaBuilder.new(context, newGraph, newStart); + ssa.opt = opt; genBlock(oldStart, newStart); } def genMultiBlock(oldStart: SsaBlock, newStart: SsaBlock) { this.ssa = SsaBuilder.new(context, newGraph, newStart); + ssa.opt = opt; blocks = 0; blockStartMap = Ssa.newBlockMap(); blockStartMap[oldStart] = newStart; diff --git a/aeneas/src/ssa/VstSsaGen.v3 b/aeneas/src/ssa/VstSsaGen.v3 index 96541884a..ca7531fb3 100644 --- a/aeneas/src/ssa/VstSsaGen.v3 +++ b/aeneas/src/ssa/VstSsaGen.v3 @@ -46,6 +46,7 @@ class VstSsaGen extends VstVisitor { var recordDirectCalls: bool; var directCallTargets: List; var directCallBlocks: List; + var opt: SsaOptimizer; // if non-null, optimizes instructions as they are added new(context, opBuilder) { var params = Array.new(func.numParams); @@ -1294,6 +1295,7 @@ class VstSsaEnv extends SsaBuilder { var loopEnd: VstSsaMerge; new(size: int, gen, parent, block: SsaBlock) super(gen.context, gen.graph, block) { + opt = gen.opt; if (parent == null) varMap = Array.new(size); else varMap = parent.varMap; } diff --git a/aeneas/src/wasm/WasmCodeGen.v3 b/aeneas/src/wasm/WasmCodeGen.v3 index 50166bce3..27274991b 100644 --- a/aeneas/src/wasm/WasmCodeGen.v3 +++ b/aeneas/src/wasm/WasmCodeGen.v3 @@ -967,11 +967,15 @@ class WasmCodeGen extends SsaMachGen { if (SsaConst.?(a)) return (SsaConst.!(a).val, null); var add = cover(Opcode.PtrAdd.tag, a); if (add != null) { - var r = add.input1(); - if (SsaConst.?(r)) return (SsaConst.!(r).val, add.input0()); + match (add.input1()) { + x: SsaConst => if (!isNegativeInt(x.val)) return (x.val, add.input0()); + } } return (null, a); } + def isNegativeInt(val: Val) -> bool { + return Box.?(val) && Box.!(val).val < 0; + } def emitShift(i: SsaApplyOp, op32: WasmOp, op64: WasmOp) { if (PrimType.!(i.op.typeArgs[0]).width <= 32) { emit3(op32.opcode, dfn(i), use(i.input0()), use(i.input1())); @@ -1193,7 +1197,20 @@ class WasmCodeGen extends SsaMachGen { } def asmls(logAlign: int, a: Array, offsetOperand: int) { w.putb(logAlign); // flags = alignment - emitInt(a[offsetOperand]); // offset + emitOffset(a[offsetOperand]); + } + def emitOffset(o: Operand) { + var val = Operand.Immediate.!(o).val; + match (val) { + x: Addr => emitOffsetAddr(x); + x: Record => emitOffsetAddr(mach.addrOfRecord(x)); + x: Box => w.put_uleb32(u32.view(x.val)); + _ => emitInt(o); + } + } + def emitOffsetAddr(addr: Addr) { + w.recordPatch(PatchKind.ABS_ULEB32, addr, w.pos); + w.skipN(5); } def emitEntryStub(test: bool) { def b = w.putb; diff --git a/aeneas/test/SsaCfOptimizerTest.v3 b/aeneas/test/SsaCfOptimizerTest.v3 index d85ce0f18..42f8a04c1 100644 --- a/aeneas/test/SsaCfOptimizerTest.v3 +++ b/aeneas/test/SsaCfOptimizerTest.v3 @@ -14,10 +14,10 @@ def X_ = void( def H: SsaGraphBuildingHelper; -def newOptimizer(g: SsaGraph) -> SsaCfOptimizer { +def newOptimizer(g: SsaGraph) -> SsaOptimizer { var context = SsaContext.new(null, null); context.graph = g; - return SsaCfOptimizer.new(context); + return SsaOptimizer.new(context); } def INT32_ADD = binop(Int.TYPE.opAdd(), _, _); @@ -167,7 +167,7 @@ def testDiamondDiamond(t: SsaInstrTester) { d2.merge.append(ret); var cf = newOptimizer(g); - cf.optimize(); + cf.optControlFlow(); // TODO: clean up intermediate gotos and assert t.assertBlock(d2.merge, [m, ret]); } diff --git a/aeneas/test/SsaInstrReducerTest.v3 b/aeneas/test/SsaInstrReducerTest.v3 index ee9ca1415..ed037480d 100644 --- a/aeneas/test/SsaInstrReducerTest.v3 +++ b/aeneas/test/SsaInstrReducerTest.v3 @@ -199,9 +199,13 @@ private class SsaInstrReducerTester(t: Tester) { p.printGraph("test", StringBuilder.puts(_, "TestGraph"), graph, null); p.flush(); } - def newReducer(block: SsaBlock, link: SsaLink) -> SsaInstrReducer { - var opt = SsaInstrReducer.new(context); - opt.reset(graph, block, link); + def newReducer(block: SsaBlock, link: SsaLink) -> SsaOptimizer { + var opt = SsaOptimizer.new(context); + opt.reset(graph); + context.block = block; + opt.ssa.block = block; + if (link != null) opt.ssa.setPrev(link); + opt.state = SsaBlockState.new(); opt.optimize_loads = optimize_loads; opt.optimize_nullchecks = optimize_nullchecks; opt.optimize_inits = optimize_inits; @@ -1413,7 +1417,7 @@ def INT_CONVERT(ft: IntType, tt: IntType, x: SsaInstr) -> SsaInstr { def testIntBounds(T: SsaInstrReducerTester) { def K = T.graph.intConst; def P = T.graph.params[0]; - def X = SsaInstrReducer.new(T.context); + def X = SsaOptimizer.new(T.context); def bound = X.posIntBound; def STYPE = Int.getType(true, _); diff --git a/test/all.bash b/test/all.bash index 02fcc6035..636e24c45 100755 --- a/test/all.bash +++ b/test/all.bash @@ -101,7 +101,7 @@ esac if [ $# != 0 ]; then TEST_DIRS="$@" else - TEST_DIRS="unit asm/x86 asm/x86-64 asm/mips redef core regalloc cast variants enums open_types descriptor descext wasmgc fsi32 fsi64 float range layout funexpr readonly large smoke v3db pointer vmaddr darwin linux rt stacktrace gc system link lib wizeng apps bench" + TEST_DIRS="unit asm/x86 asm/x86-64 asm/mips redef core regalloc cast variants enums open_types descriptor descext wasmgc fsi32 fsi64 float range layout funexpr readonly large smoke buildopt bigmem v3db pointer vmaddr darwin linux rt stacktrace gc system link lib wizeng apps bench" fi function run_test_dirs() { diff --git a/test/bigmem/ptr_high00.v3 b/test/bigmem/ptr_high00.v3 new file mode 100644 index 000000000..898a33e37 --- /dev/null +++ b/test/bigmem/ptr_high00.v3 @@ -0,0 +1,8 @@ +//@execute 0=7; 1=9 +// Accesses through constant pointers above 2GB. +def main(i: int) -> int { + var p = Pointer.NULL + (-2147483632); + p.store(7); + (p + 4).store(9); + return if(i == 0, p.load(), (p + 4).load()); +} diff --git a/test/bigmem/ptr_high01.v3 b/test/bigmem/ptr_high01.v3 new file mode 100644 index 000000000..73c55c9b3 --- /dev/null +++ b/test/bigmem/ptr_high01.v3 @@ -0,0 +1,7 @@ +//@execute 0=33; 1=44 +def main(i: int) -> int { + var p = Pointer.NULL + int.min; + p.store(33); + (p + 1).store(44); + return if(i == 0, p.load(), (p + 1).load()); +} diff --git a/test/bigmem/ptr_high02.v3 b/test/bigmem/ptr_high02.v3 new file mode 100644 index 000000000..f4e9ed77b --- /dev/null +++ b/test/bigmem/ptr_high02.v3 @@ -0,0 +1,6 @@ +//@execute 0=true; 1=true +def main(i: int) -> bool { + var p = Pointer.NULL + (-2147483616); + p.store(0x1122334455667788uL); + return if(i == 0, p.load() == 0x1122334455667788uL, p.load() == 0x55667788u); +} diff --git a/test/bigmem/ptr_high03.v3 b/test/bigmem/ptr_high03.v3 new file mode 100644 index 000000000..29d2f406e --- /dev/null +++ b/test/bigmem/ptr_high03.v3 @@ -0,0 +1,7 @@ +//@execute 0=513; 1=1027 +def main(i: int) -> int { + var p = Pointer.NULL + (-2147483646); + p.store(513); + (p + 2).store(1027); + return if(i == 0, p.load(), (p + 2).load()); +} diff --git a/test/bigmem/ptr_high04.v3 b/test/bigmem/ptr_high04.v3 new file mode 100644 index 000000000..aacba5266 --- /dev/null +++ b/test/bigmem/ptr_high04.v3 @@ -0,0 +1,6 @@ +//@execute -2147483584=21; -2147483580=22 +def main(i: int) -> int { + (Pointer.NULL + (-2147483584)).store(21); + (Pointer.NULL + (-2147483580)).store(22); + return (Pointer.NULL + i).load(); +} diff --git a/test/bigmem/ptr_high05.v3 b/test/bigmem/ptr_high05.v3 new file mode 100644 index 000000000..6057c40d8 --- /dev/null +++ b/test/bigmem/ptr_high05.v3 @@ -0,0 +1,8 @@ +//@execute 0=12; 1=13 +def main(i: int) -> int { + var p = Pointer.NULL + (-2147483560); + var q = p + (-8); + q.store(12); + (q + 4).store(13); + return if(i == 0, (p + (-8)).load(), (p + (-4)).load()); +} diff --git a/test/bigmem/test.bash b/test/bigmem/test.bash new file mode 100755 index 000000000..35bf7f002 --- /dev/null +++ b/test/bigmem/test.bash @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +. ../common.bash bigmem + +# Wasm tests that access linear memory above 2GB, which needs the largest heap. +V3C_OPTS="$V3C_OPTS -heap-size=2147483647" + +if [ $# -gt 0 ]; then + TESTS="$@" +else + TESTS=*.v3 +fi + +# The JVM-hosted spec runner cannot allocate such a large memory. +FILTERED=$OUT/config +mkdir -p $FILTERED +for f in $CONFIG/*; do + case ${f##*/} in + test-wasm-spec@wizeng) ;; + *) ln -sf $f $FILTERED/ ;; + esac +done +CONFIG=$FILTERED + +TARGETS="" +for target in $TEST_TARGETS; do + if [ "$target" = wasm ]; then TARGETS="$TARGETS $target"; fi +done +TEST_TARGETS="$TARGETS" +execute_tests diff --git a/test/buildopt/const_range00.v3 b/test/buildopt/const_range00.v3 new file mode 100644 index 000000000..41f186b34 --- /dev/null +++ b/test/buildopt/const_range00.v3 @@ -0,0 +1,7 @@ +//@execute 0=104; 1=101 +// A constant range is not a Record. +def main(a: int) -> int { + var s = "hello world"; + var hello = s[0 ... 5]; + return if(a == 0, hello[0], hello[1]); +} diff --git a/test/buildopt/const_range01.v3 b/test/buildopt/const_range01.v3 new file mode 100644 index 000000000..92acbc64c --- /dev/null +++ b/test/buildopt/const_range01.v3 @@ -0,0 +1,6 @@ +//@execute 0=101; 1=108 +def main(a: int) -> int { + var s = "hello world"; + var r = s[1 ... 4]; + return if(a == 0, r[0], r[2]); +} diff --git a/test/buildopt/const_range02.v3 b/test/buildopt/const_range02.v3 new file mode 100644 index 000000000..dec4c1795 --- /dev/null +++ b/test/buildopt/const_range02.v3 @@ -0,0 +1,5 @@ +//@execute 0=120; 1=122 +def main(a: int) -> int { + var r: Range = "xyz"; + return if(a == 0, r[0], r[2]); +} diff --git a/test/buildopt/const_range03.v3 b/test/buildopt/const_range03.v3 new file mode 100644 index 000000000..b156743ac --- /dev/null +++ b/test/buildopt/const_range03.v3 @@ -0,0 +1,6 @@ +//@execute 0=119; 1=100 +def main(a: int) -> int { + var s = "hello world"; + var r = s[6 ...]; + return if(a == 0, r[byte.view(0)], r[byte.view(4)]); +} diff --git a/test/buildopt/const_range04.v3 b/test/buildopt/const_range04.v3 new file mode 100644 index 000000000..9a8e80798 --- /dev/null +++ b/test/buildopt/const_range04.v3 @@ -0,0 +1,6 @@ +//@execute 0=88; 1=104 +def main(a: int) -> int { + var r = "hello"[0 ... 3]; + if (a == 0) r[1] = 'X'; + return if(a == 0, r[1], r[0]); +} diff --git a/test/buildopt/const_range05.v3 b/test/buildopt/const_range05.v3 new file mode 100644 index 000000000..360043273 --- /dev/null +++ b/test/buildopt/const_range05.v3 @@ -0,0 +1,5 @@ +//@execute 0=3; 1=104 +def main(a: int) -> int { + var r = "hello"[0 ... 3]; + return if(a == 0, r.length, r[0]); +} diff --git a/test/buildopt/killed00.v3 b/test/buildopt/killed00.v3 new file mode 100644 index 000000000..10d1b6fe1 --- /dev/null +++ b/test/buildopt/killed00.v3 @@ -0,0 +1,9 @@ +//@execute 0=42; 5=47 +// A reduction that kills the added instruction. +class K { + var x: int; + new(x: int) { this.x = x; } +} +def main(a: int) -> int { + return K.new(42 + a).x; +} diff --git a/test/buildopt/killed01.v3 b/test/buildopt/killed01.v3 new file mode 100644 index 000000000..a4f7ed3ca --- /dev/null +++ b/test/buildopt/killed01.v3 @@ -0,0 +1,5 @@ +//@execute 0=77; 1=77 +def x = [33, 44]; +def main(a: int) -> int { + return x[0] + x[1L]; +} diff --git a/test/buildopt/killed02.v3 b/test/buildopt/killed02.v3 new file mode 100644 index 000000000..88804591d --- /dev/null +++ b/test/buildopt/killed02.v3 @@ -0,0 +1,13 @@ +//@execute 0=12; 3=15 +class A { + var x: int; + new(x: int) { this.x = x; } +} +class B extends A { + var y: int; + new(x: int, y: int) super(x) { this.y = y; } +} +def main(a: int) -> int { + var b = B.new(5 + a, 7); + return b.x + b.y; +} diff --git a/test/buildopt/killed03.v3 b/test/buildopt/killed03.v3 new file mode 100644 index 000000000..47b243699 --- /dev/null +++ b/test/buildopt/killed03.v3 @@ -0,0 +1,5 @@ +//@execute 0=60; 1=60 +def x = [10, 20, 30]; +def main(a: int) -> int { + return x[0L] + x[1u64] + x[i64.view(2)]; +} diff --git a/test/buildopt/killed04.v3 b/test/buildopt/killed04.v3 new file mode 100644 index 000000000..5ed1ab307 --- /dev/null +++ b/test/buildopt/killed04.v3 @@ -0,0 +1,7 @@ +//@execute 0=99; 1=100 +def x = [0, 0]; +def main(a: int) -> int { + x[0] = 99; + x[1L] = a; + return x[0] + x[1]; +} diff --git a/test/buildopt/killed05.v3 b/test/buildopt/killed05.v3 new file mode 100644 index 000000000..ff04a5f16 --- /dev/null +++ b/test/buildopt/killed05.v3 @@ -0,0 +1,5 @@ +//@execute 0=5; 1=6 +def s = "hello"; +def main(a: int) -> int { + return s[4L] - 'o' + 5 + a; +} diff --git a/test/buildopt/killed06.v3 b/test/buildopt/killed06.v3 new file mode 100644 index 000000000..16c142bfb --- /dev/null +++ b/test/buildopt/killed06.v3 @@ -0,0 +1,6 @@ +//@execute 0=33; 1=44; 2=!BoundsCheckException +def x = [33, 44]; +def main(a: int) -> int { + var t = x[0L] + x[1L]; + return x[a] + t - 77; +} diff --git a/test/buildopt/lower_throw00.v3 b/test/buildopt/lower_throw00.v3 new file mode 100644 index 000000000..2a35b0125 --- /dev/null +++ b/test/buildopt/lower_throw00.v3 @@ -0,0 +1,11 @@ +//@execute 0=!NullCheckException; 1=3 +// A null array load that folds to a throw while lowering at -O0. +type U #unboxed { + case Has(a: Array); + case Not; +} +def main(i: int) -> int { + var u: U; + if (i == 0) return U.Has.!(u).a[1]; + return 3; +} diff --git a/test/buildopt/lower_throw01.v3 b/test/buildopt/lower_throw01.v3 new file mode 100644 index 000000000..a4eb26a63 --- /dev/null +++ b/test/buildopt/lower_throw01.v3 @@ -0,0 +1,10 @@ +//@execute 0=!NullCheckException; 1=3 +type U #unboxed { + case Has(a: Array); + case Not; +} +def main(i: int) -> int { + var u: U; + if (i == 0) U.Has.!(u).a[1] = 5; + return 3; +} diff --git a/test/buildopt/lower_throw02.v3 b/test/buildopt/lower_throw02.v3 new file mode 100644 index 000000000..14a5dc4ea --- /dev/null +++ b/test/buildopt/lower_throw02.v3 @@ -0,0 +1,9 @@ +//@execute 0=!DivideByZeroException; 1=!DivideByZeroException; 2=!DivideByZeroException; 3=!DivideByZeroException; 4=5 +def main(i: int) -> int { + var z = 0, zl = 0L; + if (i == 0) return i / z; + if (i == 1) return i % z; + if (i == 2) return int.view(long.view(i) / zl); + if (i == 3) return int.view(long.view(i) % zl); + return 5; +} diff --git a/test/buildopt/lower_throw03.v3 b/test/buildopt/lower_throw03.v3 new file mode 100644 index 000000000..bd37fb927 --- /dev/null +++ b/test/buildopt/lower_throw03.v3 @@ -0,0 +1,9 @@ +//@execute 0=!NullCheckException; 1=!NullCheckException; 2=!NullCheckException; 3=2 +def x = [1, 2]; +def main(i: int) -> int { + var a: Array = null; + if (i == 0) return a.length; + if (i == 1) return a[0]; + if (i == 2) a[1] = 3; + return x.length; +} diff --git a/test/buildopt/lower_throw04.v3 b/test/buildopt/lower_throw04.v3 new file mode 100644 index 000000000..1edd5366e --- /dev/null +++ b/test/buildopt/lower_throw04.v3 @@ -0,0 +1,9 @@ +//@execute 0=!NullCheckException; 1=!NullCheckException; 2=4 +def f(x: int) -> int { return x + 3; } +def main(i: int) -> int { + var g: int -> int = null; + if (i == 0) return g(1); + var h: int -> int = f; + if (i == 1) { h = null; return h(1); } + return h(1); +} diff --git a/test/buildopt/lower_throw05.v3 b/test/buildopt/lower_throw05.v3 new file mode 100644 index 000000000..56d79e721 --- /dev/null +++ b/test/buildopt/lower_throw05.v3 @@ -0,0 +1,6 @@ +//@execute 0=!NullCheckException; 1=3 +def main(i: int) -> int { + var t: (Array, int); + if (i == 0) return t.0[0]; + return 3; +} diff --git a/test/buildopt/ptr_wrap00.v3 b/test/buildopt/ptr_wrap00.v3 new file mode 100644 index 000000000..2e86f5baf --- /dev/null +++ b/test/buildopt/ptr_wrap00.v3 @@ -0,0 +1,9 @@ +//@execute 0=!BoundsCheckException; 1=!BoundsCheckException; 2=0 +// A folded pointer with an extreme offset wraps on 32-bit targets. +layout S { +0 x: u32; =4; } +def data = Array.new(8); +def main(i: int) -> u32 { + if (i == 0) return Ref.at(data, int.max).x; + if (i == 1) return Ref.at(data, int.min).x; + return Ref.at(data, 4).x; +} diff --git a/test/buildopt/ptr_wrap01.v3 b/test/buildopt/ptr_wrap01.v3 new file mode 100644 index 000000000..18f4ee197 --- /dev/null +++ b/test/buildopt/ptr_wrap01.v3 @@ -0,0 +1,8 @@ +//@execute 0=!BoundsCheckException; 1=7 +layout S { +0 x: u32; =4; } +def data = Array.new(8); +def main(i: int) -> u32 { + if (i == 0) Ref.at(data, int.max).x = 3; + Ref.at(data, 4).x = 7; + return Ref.at(data, 4).x; +} diff --git a/test/buildopt/ptr_wrap02.v3 b/test/buildopt/ptr_wrap02.v3 new file mode 100644 index 000000000..da9ee0bb9 --- /dev/null +++ b/test/buildopt/ptr_wrap02.v3 @@ -0,0 +1,7 @@ +//@execute 0=!BoundsCheckException; 1=0 +layout S { +0 x: u32; =4; } +def data = Array.new(8); +def main(i: int) -> u32 { + if (i == 0) return Ref.at(data, -2147483000).x; + return Ref.at(data, 0).x; +} diff --git a/test/buildopt/ptr_wrap03.v3 b/test/buildopt/ptr_wrap03.v3 new file mode 100644 index 000000000..d28ebb0db --- /dev/null +++ b/test/buildopt/ptr_wrap03.v3 @@ -0,0 +1,7 @@ +//@execute 0=!BoundsCheckException; 1=0 +layout S { +0 x: u64; =8; } +def data = Array.new(16); +def main(i: int) -> u32 { + if (i == 0) return u32.view(Ref.at(data, int.max - 3).x); + return u32.view(Ref.at(data, 8).x); +} diff --git a/test/buildopt/ptr_wrap04.v3 b/test/buildopt/ptr_wrap04.v3 new file mode 100644 index 000000000..52c73e57d --- /dev/null +++ b/test/buildopt/ptr_wrap04.v3 @@ -0,0 +1,7 @@ +//@execute 0=!BoundsCheckException; 1=0 +layout S { +0 x: u32; +4 y: u32; =8; } +def data = Array.new(8); +def main(i: int) -> u32 { + if (i == 0) return Ref.at(data, int.max - 5).y; + return Ref.at(data, 0).y; +} diff --git a/test/buildopt/ptr_wrap05.v3 b/test/buildopt/ptr_wrap05.v3 new file mode 100644 index 000000000..b3b88ab1d --- /dev/null +++ b/test/buildopt/ptr_wrap05.v3 @@ -0,0 +1,8 @@ +//@execute 0=!BoundsCheckException; 1=!BoundsCheckException; 2=0 +layout S { +0 b: byte; =1; } +def data = Array.new(4); +def main(i: int) -> byte { + if (i == 0) return Ref.at(data, int.max).b; + if (i == 1) return Ref.at(data, int.min).b; + return Ref.at(data, 3).b; +} diff --git a/test/buildopt/test.bash b/test/buildopt/test.bash new file mode 100755 index 000000000..c9d75b960 --- /dev/null +++ b/test/buildopt/test.bash @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +. ../common.bash buildopt + +# Run each test with SSA builders optimizing instructions as they are added, in each +# compiler phase separately (one phase's folding can hide another's) and all together. +# At -O0, earlier passes leave more to fold during lowering, e.g. throws. +BASE_OPTS="$V3C_OPTS" + +if [ $# -gt 0 ]; then + TESTS="$@" +else + TESTS=*.v3 +fi + +for level in "" "-O0"; do + for flags in VstBuildOpt NormBuildOpt MachBuildOpt VstBuildOpt,+NormBuildOpt,+MachBuildOpt; do + V3C_OPTS="$BASE_OPTS $level -opt=+$flags" + execute_tests + done +done diff --git a/test/buildopt/throw_lower00.v3 b/test/buildopt/throw_lower00.v3 new file mode 100644 index 000000000..7f1651f32 --- /dev/null +++ b/test/buildopt/throw_lower00.v3 @@ -0,0 +1,26 @@ +//@execute 0=false; 4=false; 5=true; 6=false +// A load that folds to a throw while lowering a packed variant comparison. +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[0])) == T.IsP(P.I(is[0])); + 5 => return T.IsP(P.I(is[1])) == T.IsP(P.I(2)); + _ => return T.IsP(P.Q(mkC(0))) == T.None; + } +} diff --git a/test/buildopt/throw_lower01.v3 b/test/buildopt/throw_lower01.v3 new file mode 100644 index 000000000..0458b4037 --- /dev/null +++ b/test/buildopt/throw_lower01.v3 @@ -0,0 +1,25 @@ +//@execute 0=false; 4=false; 5=true; 6=false +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[0])) == T.IsP(P.I(is[0])); + 5 => return T.IsP(P.I(is[1])) == T.IsP(P.I(2)); + _ => return T.None == T.IsP(P.Q(mkC(0))); + } +} diff --git a/test/buildopt/throw_lower02.v3 b/test/buildopt/throw_lower02.v3 new file mode 100644 index 000000000..bc33a940d --- /dev/null +++ b/test/buildopt/throw_lower02.v3 @@ -0,0 +1,25 @@ +//@execute 0=false; 1=false; 2=false; 4=false; 5=true +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[0])) == T.IsP(P.I(is[0])); + 5 => return T.IsP(P.I(is[1])) == T.IsP(P.I(2)); + _ => return T.IsP(P.Q(mkC(a))) == T.None; + } +} diff --git a/test/buildopt/throw_lower03.v3 b/test/buildopt/throw_lower03.v3 new file mode 100644 index 000000000..6bed08d15 --- /dev/null +++ b/test/buildopt/throw_lower03.v3 @@ -0,0 +1,25 @@ +//@execute 0=true; 4=false; 5=true; 6=true +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[0])) == T.IsP(P.I(is[0])); + 5 => return T.IsP(P.I(is[1])) == T.IsP(P.I(2)); + _ => return T.IsP(P.Q(mkC(0))) != T.None; + } +} diff --git a/test/buildopt/throw_lower04.v3 b/test/buildopt/throw_lower04.v3 new file mode 100644 index 000000000..18c7a030b --- /dev/null +++ b/test/buildopt/throw_lower04.v3 @@ -0,0 +1,25 @@ +//@execute 0=false; 4=true; 5=true; 6=false +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[1])) != T.IsP(P.I(is[1])); + 5 => return T.IsP(P.I(is[1])) == T.IsP(P.I(2)); + _ => return T.IsP(P.Q(mkC(0))) == T.None; + } +} diff --git a/test/buildopt/throw_lower05.v3 b/test/buildopt/throw_lower05.v3 new file mode 100644 index 000000000..5f61b7cc3 --- /dev/null +++ b/test/buildopt/throw_lower05.v3 @@ -0,0 +1,25 @@ +//@execute 0=false; 4=false; 5=false; 6=false +type C { + case None; + case Val(x: int); +} +type P #unboxed #packed { + case S(s: string); + case Q(c: C); + case I(x: i30); +} +type T #unboxed { + case None; + case IsP(p: P); +} +def ss = ["s", "t"]; +def xs = [1, 2]; +def is: Array = [1, 2]; +def mkC(i: int) -> C { return if(i == 2, C.None, C.Val(xs[i])); } +def main(a: int) -> bool { + match (a) { + 4 => return T.IsP(P.S(ss[0])) == T.IsP(P.I(is[0])); + 5 => return T.IsP(P.I(is[0])) == T.None; + _ => return T.IsP(P.Q(mkC(0))) == T.None; + } +} diff --git a/test/buildopt/throw_variant00.v3 b/test/buildopt/throw_variant00.v3 new file mode 100644 index 000000000..b9f8ea84f --- /dev/null +++ b/test/buildopt/throw_variant00.v3 @@ -0,0 +1,13 @@ +//@execute 0=!NullCheckException; 1=1 +// A call that always throws, returning a variant. +type H { + case One(a: int); +} +class A { + def m() -> H { return H.One(1); } +} +def a = A.new(); +def main(i: int) -> int { + if (i == 0) return A.m(null).tag + 1; + return A.m(a).tag + 1; +} diff --git a/test/buildopt/throw_variant01.v3 b/test/buildopt/throw_variant01.v3 new file mode 100644 index 000000000..268492c3d --- /dev/null +++ b/test/buildopt/throw_variant01.v3 @@ -0,0 +1,16 @@ +//@execute 0=!NullCheckException; 1=2 +type H { + case One(a: int); + case Two; +} +class A { + def m() -> H { return H.One(1); } +} +class B extends A { + def m() -> H { return H.Two; } +} +def b = B.new(); +def main(i: int) -> int { + if (i == 0) return A.m(null).tag + 1; + return A.m(b).tag + 1; +} diff --git a/test/buildopt/throw_variant02.v3 b/test/buildopt/throw_variant02.v3 new file mode 100644 index 000000000..bda0c2c45 --- /dev/null +++ b/test/buildopt/throw_variant02.v3 @@ -0,0 +1,13 @@ +//@execute 0=!NullCheckException; 1=1 +type H { + case One(a: int); +} +class A { + var h = H.One(5); +} +def a = A.new(); +def main(i: int) -> int { + var o: A = null; + if (i == 0) return o.h.tag + 1; + return a.h.tag + 1; +} diff --git a/test/buildopt/throw_variant03.v3 b/test/buildopt/throw_variant03.v3 new file mode 100644 index 000000000..f604c7ed7 --- /dev/null +++ b/test/buildopt/throw_variant03.v3 @@ -0,0 +1,24 @@ +//@execute 0=!NullCheckException; 1=11 +type H { + case One(a: int); + case Two(b: int); +} +class A { + def m() -> H { return H.Two(11); } +} +def a = A.new(); +def get(h: H) -> int { + match (h) { + One(x) => return x; + Two(y) => return y; + } +} +def main(i: int) -> int { + if (i == 0) { + match (A.m(null)) { + One(x) => return x; + Two(y) => return y; + } + } + return get(A.m(a)); +} diff --git a/test/buildopt/throw_variant04.v3 b/test/buildopt/throw_variant04.v3 new file mode 100644 index 000000000..5c4a5172d --- /dev/null +++ b/test/buildopt/throw_variant04.v3 @@ -0,0 +1,13 @@ +//@execute 0=!NullCheckException; 1=1 +type H #unboxed { + case One(a: int); + case Two; +} +class A { + def m() -> H { return H.Two; } +} +def a = A.new(); +def main(i: int) -> int { + if (i == 0) return A.m(null).tag; + return A.m(a).tag; +} diff --git a/test/buildopt/throw_variant05.v3 b/test/buildopt/throw_variant05.v3 new file mode 100644 index 000000000..d5ad0a688 --- /dev/null +++ b/test/buildopt/throw_variant05.v3 @@ -0,0 +1,13 @@ +//@execute 0=!NullCheckException; 1=7 +type H { + case One(a: int); +} +class A { + def m() -> (int, H) { return (6, H.One(1)); } +} +def a = A.new(); +def main(i: int) -> int { + if (i == 0) return A.m(null).1.tag; + var t = A.m(a); + return t.0 + t.1.tag + 1; +}