Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bytecode locals compaction #224

Closed
Closed
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
50975b3
Added Op operand values declaration dominance verification to TestSma…
asotona Aug 29, 2024
17bb538
fixed lifting of variables without dominant initial store
asotona Aug 29, 2024
014ee75
Roundtrip stabilization - deferred redundant var initializations with…
asotona Aug 30, 2024
191bb20
TestSmallCorpus actual numbers
asotona Aug 30, 2024
59c2cec
restored roundtrip stability - fixed LocalsTypeMapper
asotona Aug 30, 2024
ae7f0cc
minor corrections
asotona Aug 30, 2024
60e3d36
minor corrections
asotona Aug 30, 2024
58dbaf5
applied the suggested changes
asotona Sep 2, 2024
6a730b0
minor adjustments
asotona Sep 2, 2024
eb958c2
implemented LocalsCompactor
asotona Sep 4, 2024
c473ea4
Implementation of LocalsCompactor
asotona Sep 5, 2024
ca591d8
LocalsCompactor plugged into BytecodeGenerator
asotona Sep 5, 2024
b36c96e
Lifting compacted variables fix - work in progress
asotona Sep 5, 2024
3c415e4
Lifting compacted variables fix - work in progress
asotona Sep 6, 2024
917796e
Lifting compacted variables fix - work in progress
asotona Sep 9, 2024
788bb83
Added deferCache to BytecodeGenerator
asotona Sep 9, 2024
82c891f
BytecodeLift accepts Lookup for LocalsTypeMapper to test class assign…
asotona Sep 9, 2024
1da3d5d
BytecodeLift fix
asotona Sep 9, 2024
118aeed
Enabled slots compaction for all generated bytecode
asotona Sep 9, 2024
11c8786
Merge remote-tracking branch 'babylon/code-reflection' into bytecode-…
asotona Sep 9, 2024
582101b
fixed -0 iinc index :)
asotona Sep 10, 2024
8ea5bb2
TestSmallCorpus fixes
asotona Sep 10, 2024
a487c5a
TestSmallCorpus fixes
asotona Sep 10, 2024
73ff9a2
LocalsCompactor improvements
asotona Sep 10, 2024
670811c
disabled TestSmallCorpus
asotona Sep 10, 2024
0df993c
typo
asotona Sep 10, 2024
6b83a1b
LocalsCompactor improvements
asotona Sep 10, 2024
d4c0b59
LocalsCompactor re-mapping also double slots
asotona Sep 10, 2024
6f71c8e
added comments
asotona Sep 10, 2024
cadb366
added comments
asotona Sep 10, 2024
015b1c7
Removed oboslete lookup argument.
asotona Sep 11, 2024
0989c96
minor cleanup
asotona Sep 11, 2024
32938a9
fixed typo
asotona Sep 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@
import java.lang.reflect.code.op.CoreOp.*;

import java.lang.classfile.ClassBuilder;
import java.lang.classfile.ClassModel;
import java.lang.classfile.Opcode;
import java.lang.classfile.TypeKind;
import java.lang.classfile.attribute.ConstantValueAttribute;
@@ -129,7 +130,9 @@ public static <O extends Op & Op.Invokable> MethodHandle generate(MethodHandles.
* @return the class file bytes
*/
public static byte[] generateClassData(MethodHandles.Lookup lookup, FuncOp fop) {
return generateClassData(lookup, fop.funcName(), fop);
ClassModel generatedModel = ClassFile.of().parse(generateClassData(lookup, fop.funcName(), fop));
// Compact locals of the generated bytecode
return ClassFile.of().transform(generatedModel, LocalsCompactor.INSTANCE);
}

/**
@@ -204,6 +207,7 @@ private record ExceptionRegionWithBlocks(ExceptionRegionEnter ere, BitSet blocks
private final Map<Block.Parameter, Value> singlePredecessorsValues;
private final List<LambdaOp> lambdaSink;
private final BitSet quotable;
private final Map<Op, Boolean> deferCache;
private Value oprOnStack;

private BytecodeGenerator(MethodHandles.Lookup lookup,
@@ -230,6 +234,7 @@ private BytecodeGenerator(MethodHandles.Lookup lookup,
this.singlePredecessorsValues = new HashMap<>();
this.lambdaSink = lambdaSink;
this.quotable = quotable;
this.deferCache = new HashMap<>();
}

private void setExceptionRegionStack(Block.Reference target, BitSet activeRegionStack) {
@@ -347,13 +352,18 @@ private void processOperands(List<Value> operands) {
}

// Some of the operations can be deferred
private static boolean canDefer(Op op) {
return switch (op) {
case ConstantOp cop -> canDefer(cop);
case VarOp vop -> canDefer(vop);
case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
default -> false;
};
private boolean canDefer(Op op) {
Boolean can = deferCache.get(op);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use computeIfAbsent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately computeIfAbsent throws ConcurrentModificationException in this case. This method is not usable when the computation causes more additions to the map.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah of course, its recursive.

if (can == null) {
can = switch (op) {
case ConstantOp cop -> canDefer(cop);
case VarOp vop -> canDefer(vop);
case VarAccessOp.VarLoadOp vlop -> canDefer(vlop);
default -> false;
};
deferCache.put(op, can);
}
return can;
}

// Constant can be deferred, except for loading of a class constant, which may throw an exception
@@ -413,7 +423,7 @@ private static boolean isDominatedBy(Op.Result n, Set<Op.Result> doms) {
}

// Var load can be deferred when not used as immediate operand
private static boolean canDefer(VarAccessOp.VarLoadOp op) {
private boolean canDefer(VarAccessOp.VarLoadOp op) {
return !isNextUse(op.result());
}

@@ -445,7 +455,7 @@ case ConditionalBranchOp op when getConditionForCondBrOp(op) instanceof BinaryTe
}

// Determines if the operation result is immediatelly used by the next operation and so can stay on stack
private static boolean isNextUse(Value opr) {
private boolean isNextUse(Value opr) {
Op nextOp = switch (opr) {
case Block.Parameter p -> p.declaringBlock().firstOp();
case Op.Result r -> r.declaringBlock().nextOp(r.op());
Original file line number Diff line number Diff line change
@@ -73,10 +73,13 @@
import java.util.stream.Stream;

import static java.lang.classfile.attribute.StackMapFrameInfo.SimpleVerificationTypeInfo.*;
import java.lang.invoke.MethodHandles;
import java.util.BitSet;

public final class BytecodeLift {

public static boolean DUMP = false; // @@@ only for debugging purpose

private record ExceptionRegion(Label startLabel, Label endLabel, Label handlerLabel) {}
private record ExceptionRegionEntry(Op.Result enter, Block.Builder startBlock, ExceptionRegion region) {}

@@ -134,7 +137,7 @@ private BytecodeLift(Block.Builder entryBlock, ClassModel classModel, CodeModel
initLocalValues.add(null);
}
});
this.codeTracker = new LocalsTypeMapper(classModel.thisClass().asSymbol(), initLocalTypes, codeModel.exceptionHandlers(), smta, elements);
this.codeTracker = new LocalsTypeMapper(classModel.thisClass().asSymbol(), initLocalTypes, codeModel.exceptionHandlers(), smta, elements, codeAttribtue);
this.blockMap = smta.map(sma ->
sma.entries().stream().collect(Collectors.toUnmodifiableMap(
StackMapFrameInfo::target,
@@ -423,35 +426,13 @@ private void liftBody() {
endOfFlow();
}
case LoadInstruction inst -> {
LocalsTypeMapper.Variable var = codeTracker.getVarOf(i);
if (var.isSingleValue) {
assert var.value != null;
stack.push(var.value);
} else {
assert var.value instanceof Op.Result r && r.op() instanceof CoreOp.VarOp;
stack.push(op(CoreOp.varLoad(var.value)));
}
stack.push(load(i));
}
case StoreInstruction inst -> {
LocalsTypeMapper.Variable var = codeTracker.getVarOf(i);
if (var.isSingleValue) {
assert var.value == null;
var.value = stack.pop();
} else {
if (var.value == null) {
var.value = op(CoreOp.var("slot#" + inst.slot(), var.type(), stack.pop()));
} else {
assert var.value instanceof Op.Result r && r.op() instanceof CoreOp.VarOp;
op(CoreOp.varStore(var.value, stack.pop()));
}
}
store(i, inst.slot(), stack.pop());
}
case IncrementInstruction inst -> {
LocalsTypeMapper.Variable var = codeTracker.getVarOf(i);
assert !var.isSingleValue && var.value instanceof Op.Result r && r.op() instanceof CoreOp.VarOp;
op(CoreOp.varStore(var.value, op(CoreOp.add(
op(CoreOp.varLoad(var.value)),
liftConstant(inst.constant())))));
store(i, inst.slot(), op(CoreOp.add(load(-i - 1), liftConstant(inst.constant()))));
}
case ConstantInstruction inst -> {
stack.push(liftConstant(inst.constantValue()));
@@ -835,6 +816,32 @@ yield op(CoreOp._new(
}
}

private Value load(int i) {
LocalsTypeMapper.Variable var = codeTracker.getVarOf(i);
if (var.isSingleValue) {
assert var.value != null;
return var.value;
} else {
assert var.value instanceof Op.Result r && r.op() instanceof CoreOp.VarOp;
return op(CoreOp.varLoad(var.value));
}
}

private void store(int i, int slot, Value value) {
LocalsTypeMapper.Variable var = codeTracker.getVarOf(i);
if (var.isSingleValue) {
assert var.value == null;
var.value = value;
} else {
if (var.value == null) {
var.value = op(CoreOp.var("slot#" + slot, var.type(), value));
} else {
assert var.value instanceof Op.Result r && r.op() instanceof CoreOp.VarOp;
op(CoreOp.varStore(var.value, value));
}
}
}

private Op.Result lookup() {
return constantCache.computeIfAbsent(LOOKUP, _ -> op(CoreOp.invoke(LOOKUP)));
}
Loading