Skip to content

Commit

Permalink
[opt] Inline constructor calls and remove dead allocations (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
titzer authored Feb 28, 2025
1 parent 6c0f39b commit 1952c7d
Show file tree
Hide file tree
Showing 12 changed files with 92 additions and 25 deletions.
2 changes: 1 addition & 1 deletion aeneas/src/main/Version.v3
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@

// Updated by VCS scripts. DO NOT EDIT.
component Version {
def version: string = "III-9.1807";
def version: string = "III-9.1808";
var buildData: string;
}
1 change: 1 addition & 0 deletions aeneas/src/ssa/SsaBuilder.v3
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ class SsaBuilder {
var facts: Fact.set = Fact.V_NON_ZERO;
if (V3.isVariant(m.receiver)) facts |= Fact.O_PURE;
if (m.member == null) return add(V3Op.newEmptyClassAlloc(m.receiver), Ssa.NO_INSTRS, facts);
recordDirectCall(m);
return add(V3Op.newClassAlloc(m), x, facts);
}
// (Component|Class|Variant)GetField[f](x)
Expand Down
53 changes: 30 additions & 23 deletions aeneas/src/ssa/SsaInliner.v3
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ class SsaInliner extends SsaRebuilder {
}
}
if (callerBlock == null) return; // TODO: aborting inlining, graph is probably invalid
}

def inline() {
var target = inlinee.asMethod().ssa;
if (target == context.graph) return; // cannot inline recursive call
// Split {callerBlock} to include only instructions before the call.
// ==== Before ===========================================
// block ... prev <-> call <-> contInstrs ... contEnd
Expand All @@ -43,17 +47,30 @@ class SsaInliner extends SsaRebuilder {
// ==== After ===========================================
// block ... prev call contInstrs ... contEnd
// ^-----------^
}

def inline() {
var target = inlinee.asMethod().ssa;
if (target == context.graph) return; // cannot inline recursive call
instrMap.reset(target);
newGraph = context.graph;
curBlock = SsaBuilder.new(context, newGraph, callerBlock);

if (V3.isComponent(inlinee.receiver)) mapComponentParams(target.params);
else mapParams(target.params);
if (call.op.opcode.tag == Opcode.ClassAlloc.tag) {
// constructor call; insert an allocation with no method
var op = V3Op.newEmptyClassAlloc(call.op.typeArgs[0]);
var receiver = curBlock.add(op, [], Fact.V_NON_ZERO);
instrMap[target.params[0]] = receiver; // don't transfer non-null facts
mapNonReceiverParams(target.params[1 ...], call.inputs);
} else if (V3.isComponent(inlinee.receiver)) {
// component call; use null receiver
map1(target.params[0], newGraph.nullConst(inlinee.receiver));
mapNonReceiverParams(target.params[1 ...], call.inputs[1 ...]);
} else {
// class/variant method call; insert null check if necessary
var receiver = call.input0();
if (V3Op.needsNullCheck(call, receiver)) {
curBlock.opNullCheck(inlinee.receiver, receiver);
}
instrMap[target.params[0]] = receiver; // XXX: transfer flow-sensitive non-nullness
mapNonReceiverParams(target.params[1 ...], call.inputs[1 ...]);
}
// determine whether a simple or a complex inline is necessary
var targetStart = target.startBlock;
if (targetStart.succs().length == 0) {
Expand All @@ -65,21 +82,7 @@ class SsaInliner extends SsaRebuilder {
}
call.kill();
}
def mapComponentParams(params: Array<SsaParam>) {
// map parameter 0 to the null constant
map1(params[0], newGraph.nullConst(inlinee.receiver));
mapNonReceiverParams(Arrays.range(params, 1, params.length), Arrays.range(call.inputs, 1, call.inputs.length));
}
def mapParams(params: Array<SsaParam>) {
// map parameter 0 to argument 0
var receiver = call.input0();
if (V3Op.needsNullCheck(call, receiver)) {
curBlock.opNullCheck(inlinee.receiver, receiver);
}
instrMap[params[0]] = receiver; // don't transfer non-null facts
mapNonReceiverParams(Arrays.range(params, 1, params.length), Arrays.range(call.inputs, 1, call.inputs.length));
}
def mapNonReceiverParams(params: Array<SsaParam>, args: Array<SsaDfEdge>) {
def mapNonReceiverParams(params: Range<SsaParam>, args: Range<SsaDfEdge>) {
// map non-receiver params, expanding or contracting parameters to deal with tuples.
if (params.length == 0) return; // nothing to do
if (params.length == args.length) {
Expand All @@ -92,12 +95,12 @@ class SsaInliner extends SsaRebuilder {
// collapse tuple arguments into one parameter
var p = params[0];
var vt = inlinee.instantiateType(p.vtype);
var tupleArg = curBlock.opTupleCreate(vt, Arrays.map(args, SsaDfEdge.getDest));
var tupleArg = curBlock.opTupleCreate(vt, Arrays.map(Ranges.dup(args), SsaDfEdge.getDest)); // XXX
inlinedInstrs++;
map1(p, tupleArg);
} else if (args.length == 1) {
// expand tuple argument into multiple parameters
var vt = Tuple.TYPECON.create(Lists.fromArray(Arrays.map(params, SsaParam.vtype)));
var vt = Tuple.TYPECON.create(Lists.fromArray(Arrays.map(Ranges.dup(params), SsaParam.vtype))); // XXX
vt = inlinee.instantiateType(vt);
var nargs = Array<SsaInstr>.new(params.length);
var a = args[0].dest;
Expand Down Expand Up @@ -249,6 +252,10 @@ class SsaEarlyInliner(context: SsaContext, compilation: Compilation, gen: VstSsa
match (apply.op.opcode) {
CallMethod(method) => inlinee = V3Op.extractIrSpec(apply.op, method);
CallClassMethod(method) => inlinee = V3Op.extractIrSpec(apply.op, method);
ClassAlloc(method) => {
if (method == null) continue;
inlinee = V3Op.extractIrSpec(apply.op, method);
}
_ => continue;
}
if (!shouldInline(inlinee)) {
Expand Down
12 changes: 11 additions & 1 deletion aeneas/src/ssa/SsaOptimizer.v3
Original file line number Diff line number Diff line change
Expand Up @@ -741,12 +741,21 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher {
ArrayAlloc => {
var len = i.input0();
if (len.facts.V_NON_NEGATIVE) i.facts |= Fact.O_NO_NEGATIVE_CHECK;
if (remove_pure_ops && i.useList == null) { // unused array allocation
if (!i.facts.O_NO_NEGATIVE_CHECK) {
var op = IntType.!(len.getType()).opLt();
var check = addBinop(op, len, graph.zeroConst());
return addConditionalThrow(i.source, V3Exception.LengthCheck, check);
}
return killUnused(i);
}
state.init(i, ARRAY_LENGTH_FIELD, i.input0());
}
ArrayInit => {
if (i.op.isPolymorphic()) return i;
n_op(i);
if (true || !fold) return i;
if (remove_pure_ops && i.useList == null) return killUnused(i); // unused empty array init
if (true || !fold) return i; // TODO: can fold array init if identity not observed
var arrayType = i.op.typeArgs[0];
var record = context.prog.newRecord(arrayType, i.inputs.length);
for (j < i.inputs.length) record.values[j] = SsaConst.!(i.inputs[j].dest).val;
Expand Down Expand Up @@ -785,6 +794,7 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher {
}
ClassAlloc(method) => {
if (method != null) state.kill();
if (remove_pure_ops && method == null && i.useList == null) return killUnused(i); // unused empty alloc
if (i.op.isPolymorphic()) return i;
if (!i.facts.O_PURE) return i;
n_op(i);
Expand Down
11 changes: 11 additions & 0 deletions test/core/inline_cons04.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//@execute 0=3; -333=-996
class Point2(x: int, y: int) { }
class Point3 extends Point2 {
def z: int;
new(x: int, y: int, z) super(x, y) { }
}

def main(a: int) -> int {
var p = Point3.new(a, a + 1, a + 2);
return p.x + p.y + p.z;
}
7 changes: 7 additions & 0 deletions test/core/inline_cons05.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//@execute 0=1; -333=-665
class Point2(x: int, y: int) { }

def main(a: int) -> int {
var p = Point2.new(a, a + 1);
return p.x + p.y;
}
5 changes: 5 additions & 0 deletions test/core/opt_dead_alloc00.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@execute =42
def main() -> int {
var x: Array<byte> = [];
return 42;
}
5 changes: 5 additions & 0 deletions test/core/opt_dead_alloc01.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@execute 0=42
def main(a: int) -> int {
var x = [a];
return 42;
}
6 changes: 6 additions & 0 deletions test/core/opt_dead_alloc02.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//@execute 0=42
class C { }
def main(a: int) -> int {
var x = C.new();
return 42;
}
5 changes: 5 additions & 0 deletions test/core/opt_dead_alloc03.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@execute =42
def main() -> int {
var x = Array<byte>.new(3);
return 42;
}
5 changes: 5 additions & 0 deletions test/core/opt_dead_alloc04.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@execute 0=42; 5=42; -1=!LengthCheckException
def main(a: int) -> int {
var x = Array<byte>.new(a);
return 42;
}
5 changes: 5 additions & 0 deletions test/core/opt_dead_alloc05.v3
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//@execute 0=42; 3=42
def main(a: u31) -> int {
var x = Array<byte>.new(a);
return 42;
}

0 comments on commit 1952c7d

Please sign in to comment.