From 96cec50eac4fc77c776abadae2ba9e8cca69397c Mon Sep 17 00:00:00 2001 From: "Ben L. Titzer" Date: Fri, 22 Nov 2024 14:27:27 -0500 Subject: [PATCH] [optimization] Combine load elimination with SsaInstrReducer for better results --- aeneas/src/ir/SsaNormalizer.v3 | 7 +- aeneas/src/mach/MachLowering.v3 | 4 +- aeneas/src/main/Compiler.v3 | 7 +- aeneas/src/main/Version.v3 | 2 +- aeneas/src/ssa/SsaBuilder.v3 | 7 +- aeneas/src/ssa/SsaOptimizer.v3 | 243 +++++++++++------------------ aeneas/src/ssa/SsaPrinter.v3 | 2 +- aeneas/test/SsaInstrReducerTest.v3 | 1 + test/core/opt_load13.v3 | 27 ++++ test/core/opt_load14.v3 | 8 + 10 files changed, 131 insertions(+), 177 deletions(-) create mode 100644 test/core/opt_load13.v3 create mode 100644 test/core/opt_load14.v3 diff --git a/aeneas/src/ir/SsaNormalizer.v3 b/aeneas/src/ir/SsaNormalizer.v3 index 2cbf6fe78..67de1bce7 100644 --- a/aeneas/src/ir/SsaNormalizer.v3 +++ b/aeneas/src/ir/SsaNormalizer.v3 @@ -26,14 +26,9 @@ class SsaRaNormalizer extends SsaRebuilder { //TODO SsaGraphVerifier.new(context).verify(); if (context.compiler.NormOptimize) { var opt = SsaOptimizer.new(context); + opt.iopt.optimize_loads = context.compiler.LoadOptimize; opt.optGraph(); context.printSsa("Norm Optimized"); - if (context.compiler.LoadOptimize) { - // Perform load elimination. - if (SsaLoadOptimizer.new(context).optimize()) { - context.printSsa("Load Optimized"); - } - } //TODO SsaGraphVerifier.new(context).verify(); } } diff --git a/aeneas/src/mach/MachLowering.v3 b/aeneas/src/mach/MachLowering.v3 index b7055a2b1..53863fd08 100644 --- a/aeneas/src/mach/MachLowering.v3 +++ b/aeneas/src/mach/MachLowering.v3 @@ -374,8 +374,8 @@ class MachLowering(mach: MachProgram, compiler: Compiler, config: MachLoweringCo } } def doBlock(block: SsaBlock) { - curBlock.clear(); - context.block = curBlock.block = block; + context.block = block; + curBlock.set(block); var i = block.next; while (true) { if (i == null) break; diff --git a/aeneas/src/main/Compiler.v3 b/aeneas/src/main/Compiler.v3 index 268fa6c3b..3d5171508 100644 --- a/aeneas/src/main/Compiler.v3 +++ b/aeneas/src/main/Compiler.v3 @@ -341,15 +341,10 @@ class Compilation(compiler: Compiler, prog: Program) { SsaCfOptimizer.new(context).optimize(); context.printSsa("Control Optimized"); } - if (compiler.LoadOptimize) { - // Perform load elimination. - if (SsaLoadOptimizer.new(context).optimize()) { - context.printSsa("Load Optimized"); - } - } if (compiler.PostpassOptimize) { // Run the post-pass optimizer. var opt = SsaOptimizer.new(context); + opt.iopt.optimize_loads = compiler.LoadOptimize; opt.optGraph(); context.printSsa("Postpass Optimized"); } diff --git a/aeneas/src/main/Version.v3 b/aeneas/src/main/Version.v3 index fb302a501..26ce8469e 100644 --- a/aeneas/src/main/Version.v3 +++ b/aeneas/src/main/Version.v3 @@ -3,6 +3,6 @@ // Updated by VCS scripts. DO NOT EDIT. component Version { - def version: string = "III-7.1770"; + def version: string = "III-7.1772"; var buildData: string; } diff --git a/aeneas/src/ssa/SsaBuilder.v3 b/aeneas/src/ssa/SsaBuilder.v3 index f39e07720..003bb3caa 100644 --- a/aeneas/src/ssa/SsaBuilder.v3 +++ b/aeneas/src/ssa/SsaBuilder.v3 @@ -13,16 +13,16 @@ def checkInputs(inputs: Array) { for (i in inputs) if (i == null) return V3.fail("null input"); } -def NEW_OPTIMIZER = false; def N = Facts.NONE; // Support class for constructing SSA instruction-by-instruction. -class SsaBuilder extends SsaBlockState { +class SsaBuilder { def context: SsaContext; def graph: SsaGraph; var block: SsaBlock; var pt: SsaLink; var opt: SsaOptimizer; var source: Source; + var end: bool; new(context, graph, block) { } @@ -37,7 +37,6 @@ class SsaBuilder extends SsaBlockState { var i = SsaApplyOp.new(source, op, args); i.setFact(Opcodes.facts(op.opcode)); append(i); - if (NEW_OPTIMIZER && opt != null) return opt.reduceApply(this, i); return i; } def addApplyF(op: Operator, args: Array, facts: Fact.set) -> SsaInstr { @@ -50,7 +49,6 @@ class SsaBuilder extends SsaBlockState { var i = SsaApplyOp.new(source, op, args); i.setFact(Opcodes.facts(op.opcode) | facts); append(i); - if (NEW_OPTIMIZER && opt != null) return opt.reduceApply(this, i); return i; } def addApplyVst(source: Source, op: Operator, vst: VstOperator, args: Array) -> SsaInstr { @@ -376,7 +374,6 @@ class SsaBuilder extends SsaBlockState { end = true; var i = SsaIf.new(cond, tblock, fblock); append(i); - if (NEW_OPTIMIZER && opt != null) opt.reduceIf(this, i); } def addSelect(t: Type, cond: SsaInstr, tval: SsaInstr, fval: SsaInstr) -> SsaInstr { if (Debug.PARANOID) { checkInputs([cond, tval, fval]); } diff --git a/aeneas/src/ssa/SsaOptimizer.v3 b/aeneas/src/ssa/SsaOptimizer.v3 index e30a7ac8d..d989d39ae 100644 --- a/aeneas/src/ssa/SsaOptimizer.v3 +++ b/aeneas/src/ssa/SsaOptimizer.v3 @@ -248,29 +248,93 @@ class SsaInstrMatcher { // Flow-sensitive state for local optimizations. class SsaBlockState { var end: bool; - var fields: List<(SsaInstr, IrField, SsaInstr)>; 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 clear() -> this { - fields = null; nonnull = null; inits = null; inbounds = null; end = false; + pure_loads.resize(0); + impure_loads.resize(0); + } + def kill() -> this { + impure_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.facts.F_POINTED_AT) pure_loads.put(obj, m, apply); + else impure_loads.put(obj, m, apply); + return apply; } - def addField(receiver: SsaInstr, field: IrField, value: SsaInstr) { - fields = List.new((receiver, field, value), fields); + 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 init(obj: SsaInstr, f: IrField, val: SsaInstr) { + if (f.isConst() && !f.facts.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.fields = this.fields; 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); 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, IrMember, SsaInstr)>, obj: SsaInstr, m: IrMember) -> 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.facts.F_POINTED_AT) { + if (i != j) v[i] = t; + i++; + } + } + v.resize(i); + } } // Optimizes SSA instructions one at a time, performing such optimizations @@ -669,7 +733,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { ArrayAlloc => { var len = i.input0(); if (len.facts.V_NON_NEGATIVE) i.facts |= Fact.O_NO_NEGATIVE_CHECK; - state.addField(i, ARRAY_LENGTH_FIELD, i.input0()); + state.init(i, ARRAY_LENGTH_FIELD, i.input0()); } ArrayInit => { if (i.op.isPolymorphic()) return i; @@ -705,10 +769,10 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { // ArrayGetLength(ArrayAlloc(x)) => x return receiver.input0(); } - var len = lookupField(i, receiver, ARRAY_LENGTH_FIELD); - if (len != i) return len; + if (optimize_loads) return state.load(receiver, ARRAY_LENGTH_FIELD, i); } - ClassAlloc => { + ClassAlloc(method) => { + if (method != null) state.kill(); if (i.op.isPolymorphic()) return i; if (!i.facts.O_PURE) return i; n_op(i); @@ -733,17 +797,17 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { i.setFactIf(Fact.O_NO_NULL_CHECK, Fact.O_PURE); i.facts |= Fact.F_VALUE; if (remove_pure_ops && i.facts.O_PURE && i.useList == null) return killUnused(i); - return lookupField(i, receiver, field); } + if (optimize_loads) return state.load(receiver, field, i); } ClassInitField(field) => { i.facts |= Fact.O_NO_NULL_CHECK; - if (field.isConst()) state.addField(i.input0(), field, i.input1()); // forward stores + if (optimize_loads) state.init(i.input0(), field, i.input1()); // forward stores } ClassSetField(field) => { var receiver = nullcheck(i); if (state.end) return receiver; - if (field.isConst()) state.addField(receiver, field, i.input1()); // forward stores + if (optimize_loads) state.store(receiver, field, i, i.input1()); // forward stores } ClassGetMethod(method) => { var meth = V3Op.extractIrSpec(i.op, method); @@ -818,10 +882,10 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { return if(lit.exactType != field.fieldType, convert(const, field.fieldType), const); } } - if (field.isConst()) return lookupField(i, null, field); + if (optimize_loads) return state.load(null, field, i); } ComponentSetField(field) => { - if (field.isConst()) state.addField(null, field, i.input1()); // forward stores + if (optimize_loads) state.store(null, field, i, i.input1()); // forward stores } TupleCreate(length) => { if (i.op.isPolymorphic()) return i; @@ -850,10 +914,12 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { CallMethod(method) => { i.facts |= method.facts & Fact.O_PURE; if (i.inputs.length > 0 && i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; + state.kill(); } CallClassMethod(method) => { i.facts |= method.facts & Fact.O_PURE; var target = nullcheck(i); + state.kill(); if (SsaThrow.?(target)) return target; if (i.inputs.length > 0 && i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; } @@ -863,16 +929,19 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { if (state.end) return receiver; if (i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; var dv = devirtualize(meth, receiver); + state.kill(); if (dv != null) return replaceOp(i, V3Op.newCallClassMethod(dv)); // CallClassVirtual[m](K) => CallClassMethod[m](k) } CallVariantVirtual(method) => { var receiver = i.input0(), meth = V3Op.extractIrSpec(i.op, method); if (i.input0().facts.V_NON_ZERO) i.facts |= Fact.O_NO_NULL_CHECK; var dv = devirtualize(meth, receiver); + state.kill(); if (dv != null) return replaceOp(i, V3Op.newCallMethod(dv)); // CallVariantVirtual[m](K) => CallMethod[m](k) } CallClosure => { var target = nullcheck(i); + state.kill(); if (SsaThrow.?(target)) return target; var xval = unop(i); if (xconst) { @@ -920,6 +989,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { CallFunction => { var target = nullcheck(i); if (SsaThrow.?(target)) return target; + state.kill(); var xval = unop(i); if (xconst) { var d = FuncVal.!(xval); @@ -929,6 +999,9 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { return add(i.source, V3Op.newCallMethod(d.memberRef), inputs).setFact(Fact.O_NO_NULL_CHECK); } } + CallAddress => { + state.kill(); + } CreateClosure(method) => { if (i.op.isPolymorphic()) return i; var xval = unop(i); @@ -1157,15 +1230,6 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { state.nonnull = List.new(receiver, state.nonnull); return receiver; } - def lookupField(load: SsaInstr, receiver: SsaInstr, field: IrField) -> SsaInstr { - if (!optimize_loads) return load; - for (l = state.fields; l != null; l = l.tail) { - var h = l.head; - if (h.0 == receiver && h.1 == field) return h.2; - } - state.addField(receiver, field, load); - return load; - } def devirtualize(m: IrSpec, x: SsaInstr) -> IrSpec { if (!m.member.facts.M_OVERRIDDEN) { return m; // CHA devirtualization @@ -1903,136 +1967,3 @@ type BoundOp { } } } - -// Performs load/store optimizations on a completed SsaGraph. -class SsaLoadOptimizer(context: SsaContext) { - var pure = SsaLoadedFields.new(); - var impure = SsaLoadedFields.new(); - var any = false; - - def optimize() -> bool { - // visit all the blocks of the graph, removing redundant loads. - for (block in context.graph.bfBlocks(null)) optBlock(block); - return any; - } - def optBlock(block: SsaBlock) { - pure.length = 0; - impure.length = 0; - var i = block.next; - for (i = block.next; SsaInstr.?(i); ()) { - var next = i.next; - match (i) { - instr: SsaApplyOp => { - var repl = optInstr(instr); - if (repl != null) { - any = true; - instr.replace(repl); - instr.remove(); - } - } - } - i = next; - } - while (SsaApplyOp.?(i)) { - var instr = SsaApplyOp.!(i); - var next = instr.next; - i = next; - } - } - def optInstr(apply: SsaApplyOp) -> SsaInstr { - match (apply.op.opcode) { - ClassAlloc(method) => { - if (method != null) impure.length = 0; - } - // XXX: ClassGetMethod, ClassGetVirtual for monomorphic methods. - ClassGetField(field) => return load(apply.input0(), field, apply); - VariantGetField(field) => return load(apply.input0(), field, apply); - ComponentGetField(field) => return load(null, field, apply); - ClassInitField(field) => { - var obj = apply.input0(); - var map = if(field.isConst(), pure, impure); - map.add(obj, field, apply.input1()); - } - ClassSetField(field) => return store(apply.input0(), field, apply, apply.input1()); - ComponentSetField(field) => return store(null, field, apply, apply.input1()); - PtrStore => { - pure.killPointedAt(); - impure.killPointedAt(); - } - Init, - CallMethod, - CallClassMethod, - CallClassVirtual, - CallClassSelector, - CallVariantVirtual, - CallVariantSelector, - CallClosure, - CallFunction, - SystemCall => impure.length = 0; - _ => ; - } - return null; - } - def load(obj: SsaInstr, m: IrMember, apply: SsaApplyOp) -> SsaInstr { - 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)) pure.add(obj, m, apply); - else impure.add(obj, m, apply); - return null; - } - 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(); - } else { - impure.kill(field); - impure.add(obj, field, val); - } - return null; - } - def find(o: SsaInstr, f: IrMember) -> SsaInstr { - var v = pure.find(o, f); - return if(v == null, impure.find(o, f), v); - } -} -// Analysis data for tracking the loaded fields. -class SsaLoadedFields { - var array = Array<(SsaInstr, IrMember, SsaInstr)>.new(4); - var length = 0; - def add(obj: SsaInstr, f: IrMember, val: SsaInstr) { - if (length == array.length) array = Arrays.grow(array, array.length * 2); - array[length++] = (obj, f, val); - } - def find(obj: SsaInstr, f: IrMember) -> SsaInstr { - for (i < length) { - var t = array[i]; - if (t.0 == obj && t.1 == f) return t.2; - } - return null; - } - def kill(f: IrField) { - var i = 0; - for (j < length) { - var t = array[j]; - if (t.1 != f) { - if (i != j) array[i] = t; - i++; - } - } - length = i; - } - def killPointedAt() { - var i = 0; - for (j < length) { - var t = array[j]; - if ((!t.1.facts.F_POINTED_AT) && i != j) - array[i++] = t; - } - length = i; - } -} diff --git a/aeneas/src/ssa/SsaPrinter.v3 b/aeneas/src/ssa/SsaPrinter.v3 index 509754f90..334b734c1 100644 --- a/aeneas/src/ssa/SsaPrinter.v3 +++ b/aeneas/src/ssa/SsaPrinter.v3 @@ -166,7 +166,7 @@ class SsaPrinter { buf.puts(")"); } } - def printInstrLn(i: SsaInstr) { + def printInstrLn(i: SsaInstr) -> this { indent(2); printInstr(i, true, true, true, true); ln(); diff --git a/aeneas/test/SsaInstrReducerTest.v3 b/aeneas/test/SsaInstrReducerTest.v3 index 1ead48012..a46cf0c73 100644 --- a/aeneas/test/SsaInstrReducerTest.v3 +++ b/aeneas/test/SsaInstrReducerTest.v3 @@ -153,6 +153,7 @@ private class SsaInstrReducerTester(t: Tester) { } def failBlock(block: SsaBlock, expected: Array) { printExpectedBlock(block, expected); + Terminal.put("--- got ---\n"); printBlock(block, expected.length * 2 + 1); t.fail("block"); } diff --git a/test/core/opt_load13.v3 b/test/core/opt_load13.v3 new file mode 100644 index 000000000..b1910bc29 --- /dev/null +++ b/test/core/opt_load13.v3 @@ -0,0 +1,27 @@ +//@execute 0=6; 1=8; 2=6; 3=9 +var x = 5, y = 7; +def main(a: int) -> int { + x = 6; + y = 8; + match(a) { + 0 => { + var t = x; + return x; + } + 1 => { + var t = x; + return y; + } + 2 => { + var t = x; + y = 9; + return x; + } + 3 => { + var t = x; + x = 9; + return x; + } + } + return x + y; +} diff --git a/test/core/opt_load14.v3 b/test/core/opt_load14.v3 new file mode 100644 index 000000000..c434b634f --- /dev/null +++ b/test/core/opt_load14.v3 @@ -0,0 +1,8 @@ +//@execute 0=55; -55=0; 45=100 +class A { } +var x: int; +def main(a: int) -> int { + x = a; + var obj = A.new(); + return x + 55; +}