Skip to content
Closed
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6fde7bd
Move Interpreter to the location of runtime tests
mabbay Mar 13, 2026
cf20247
Remove methods from jdk.incubator.code.internal.ArithmeticAndConvOpIm…
mabbay Mar 14, 2026
c71e110
Merge branch 'code-reflection' into mv-interpreter
mabbay Mar 14, 2026
943fa35
Document the issue with interning constant expression of type String
mabbay Mar 23, 2026
204fcae
Model if a variable is declared final
mabbay Mar 24, 2026
e72bdb3
Enhance the check of whether a VarOp models a constant variable
mabbay Mar 24, 2026
3cfd1d8
Merge branch 'code-reflection' into intern-string-constant-expr
mabbay Mar 24, 2026
b21de78
Revert "Enhance the check of whether a VarOp models a constant variable"
mabbay Mar 25, 2026
188e19e
Revert "Model if a variable is declared final"
mabbay Mar 25, 2026
dfd6325
Broaden the JLS notion of constant variable to include effectively fi…
mabbay Mar 25, 2026
dd21836
Embed the expected value in the reflect method name
mabbay Mar 25, 2026
50d5b82
Implement constant folding
mabbay Mar 26, 2026
d6b839f
Encapsulate the code for evaluation in a class
mabbay Mar 27, 2026
5796b78
Add a map to class Evaluator for caching the result of evaluation
mabbay Mar 27, 2026
bb86b12
Add test cases
mabbay Mar 30, 2026
def145e
create class ConstantFolder
mabbay Mar 30, 2026
66f5243
Delete TestConstantFolding.java as it contains the same cases as Test…
mabbay Mar 30, 2026
f7b488b
Use ConstantFolder transformer in BytecodeGenerator and Interpreter
mabbay Mar 30, 2026
5465171
Forget to commit ConstantFolder class
mabbay Mar 31, 2026
6cdb7b5
Remove constant folding from Interpreter because it causes problems
mabbay Apr 2, 2026
e73916c
Make RemoveUnusedConstantTransformer internal
mabbay Apr 2, 2026
6d2ac0b
Apply suggestions
mabbay Apr 14, 2026
fb04923
Merge branch 'code-reflection' into intern-string-constant-expr
mabbay Apr 14, 2026
60dd65c
Merge branch 'code-reflection' into intern-string-constant-expr
mabbay Apr 23, 2026
feb5d6d
Remove empty line
mabbay Apr 23, 2026
4e74ebb
Derive expected value from the case method
mabbay Apr 23, 2026
247c773
Add comment
mabbay Apr 23, 2026
e149a14
Drop comment
mabbay Apr 28, 2026
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
Expand Up @@ -25,6 +25,8 @@

package jdk.incubator.code;

import jdk.incubator.code.dialect.core.CoreOp;
Comment thread
mabbay marked this conversation as resolved.
Outdated

import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import jdk.incubator.code.dialect.core.VarType;
import jdk.incubator.code.extern.DialectFactory;
import jdk.incubator.code.internal.OpBuilder;
import jdk.incubator.code.internal.RemoveUnusedConstantTransformer;
import jdk.incubator.code.runtime.ReflectableLambdaMetafactory;

import static java.lang.constant.ConstantDescs.*;
Expand Down Expand Up @@ -174,8 +175,10 @@ private static <O extends Op & Op.Invokable> byte[] generateClassData(MethodHand
BitSet reflectableLambda = new BitSet();
CodeTransformer lowering = LoweringTransform.getInstance(lookup);
for (var e : ops.sequencedEntrySet()) {
Op transformed = e.getValue().transform(CodeContext.create(), ConstantFolder.getInstance(lookup));
transformed = transformed.transform(CodeContext.create(), RemoveUnusedConstantTransformer.getInstance());
O lowered = NormalizeBlocksTransformer.transform(
(O)e.getValue().transform(CodeContext.create(), lowering));
(O)transformed.transform(CodeContext.create(), lowering));
generateMethod(lookup, clName, e.getKey(), lowered, clb, ops, lambdaSink, reflectableLambda);
}
var modelsToBuild = new LinkedHashMap<String, FuncOp>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package jdk.incubator.code.dialect.java;

import jdk.incubator.code.Block;
import jdk.incubator.code.CodeTransformer;
import jdk.incubator.code.Op;

import java.lang.invoke.MethodHandles;
import java.util.Optional;

import static jdk.incubator.code.dialect.core.CoreOp.constant;

public class ConstantFolder implements CodeTransformer {
Comment thread
mabbay marked this conversation as resolved.
Outdated
private final JavaOp.JavaExpression.Evaluator evaluator;

private ConstantFolder(JavaOp.JavaExpression.Evaluator evaluator) {
this.evaluator = evaluator;
}

public static ConstantFolder getInstance(MethodHandles.Lookup l) {
Comment thread
mabbay marked this conversation as resolved.
Outdated
return new ConstantFolder(new JavaOp.JavaExpression.Evaluator(l));
}

@Override
public Block.Builder acceptOp(Block.Builder b, Op op) {
Optional<Object> v = evaluator.evaluate(op.result());
if (v.isPresent()) {
Op.Result c = b.op(constant(op.resultType(), v.get()));
b.context().mapValue(op.result(), c);
} else {
b.op(op);
}
return b;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,7 @@ public sealed interface JavaExpression permits
*}
*/
static Optional<Object> evaluate(MethodHandles.Lookup l, Value v) {
try {
Object o = eval(l, v);
return Optional.of(o);
} catch (NonConstantExpression e) {
return Optional.empty();
}
return new Evaluator(l).evaluate(v);
}

/**
Expand Down Expand Up @@ -180,151 +175,177 @@ static Optional<Object> evaluate(MethodHandles.Lookup l, Value v) {
* @jls 15.29 Constant Expressions
*/
static <T extends Op & JavaExpression> Optional<Object> evaluate(MethodHandles.Lookup l, T op) {
try {
Object v = eval(l, op);
return Optional.of(v);
} catch (NonConstantExpression e) {
return Optional.empty();
}
return new Evaluator(l).evaluate(op);
}

private static Object eval(MethodHandles.Lookup l, Op op) {
return switch (op) {
case CoreOp.ConstantOp cop when isConstant(cop) -> {
Object v = cop.value();
yield v instanceof String s ? s.intern() : v;
class Evaluator {
Comment thread
mabbay marked this conversation as resolved.
Outdated
private final MethodHandles.Lookup l;
private final Map<Value, Object> m = new HashMap<>();

Evaluator(MethodHandles.Lookup l) {
this.l = l;
}

<T extends Op & JavaExpression> Optional<Object> evaluate(T op) {
try {
Object v = this.eval(op);
return Optional.of(v);
} catch (NonConstantExpression e) {
return Optional.empty();
}
case CoreOp.VarAccessOp.VarLoadOp varLoadOp when isConstant(varLoadOp.varOp()) ->
eval(l, varLoadOp.varOp().initOperand());
case JavaOp.ConvOp _ -> {
// we expect cast to primitive type
var v = eval(l, op.operands().getFirst());
yield ArithmeticAndConvOpImpls.evaluate(op, List.of(v));
}

Optional<Object> evaluate(Value v) {
try {
Object o = this.eval(v);
return Optional.of(o);
} catch (NonConstantExpression e) {
return Optional.empty();
}
case CastOp castOp -> {
// we expect cast to String
Value operand = castOp.operands().getFirst();
if (!castOp.resultType().equals(J_L_STRING) || !operand.type().equals(J_L_STRING)) {
throw new NonConstantExpression();
}

private Object eval(Op op) {
if (m.containsKey(op.result())) {
return m.get(op.result());
}
Object r = switch (op) {
case ConstantOp cop when isConstant(cop) -> {
Object v = cop.value();
yield v instanceof String s ? s.intern() : v;
}
Object v = eval(l, operand);
if (!(v instanceof String s)) {
throw new NonConstantExpression();
case VarAccessOp.VarLoadOp varLoadOp when varLoadOp.operands().getFirst() instanceof Result &&
Comment thread
mabbay marked this conversation as resolved.
isConstant(varLoadOp.varOp()) -> eval(varLoadOp.varOp().initOperand());
case ConvOp _ -> {
// we expect cast to primitive type
var v = eval(op.operands().getFirst());
yield ArithmeticAndConvOpImpls.evaluate(op, List.of(v));
}
yield s;
}
case ConcatOp concatOp -> {
Object first = eval(l, concatOp.operands().getFirst());
Object second = eval(l, concatOp.operands().getLast());
yield (first.toString() + second).intern();
}
case JavaOp.FieldAccessOp.FieldLoadOp fieldLoadOp -> {
Field field;
VarHandle vh;
try {
field = fieldLoadOp.fieldReference().resolveToField(l);
vh = fieldLoadOp.fieldReference().resolveToHandle(l);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException(e);
case CastOp castOp -> {
// we expect cast to String
Value operand = castOp.operands().getFirst();
if (!castOp.resultType().equals(J_L_STRING) || !operand.type().equals(J_L_STRING)) {
throw new NonConstantExpression();
}
Object v = eval(operand);
if (!(v instanceof String s)) {
throw new NonConstantExpression();
}
yield s;
}
// Requirement: the field must be a constant variable.
// Current checks:
// 1) The field is declared final.
// 2) The field type is a primitive or String.
// Missing check:
// 3) Verify the field is initialized and the initializer is a constant expression.
if ((field.getModifiers() & Modifier.FINAL) == 0 ||
!isConstantType(fieldLoadOp.fieldReference().type())) {
throw new NonConstantExpression();
case ConcatOp concatOp -> {
Object first = eval(concatOp.operands().getFirst());
Object second = eval(concatOp.operands().getLast());
yield (first.toString() + second).intern();
}
Object v;
if ((field.getModifiers() & Modifier.STATIC) != 0) {
// @@@ why using field.get fails ?
v = vh.get();
} else {
// we can't get the value of an instance field from the model
// we need the value of the receiver
throw new NonConstantExpression();
case FieldAccessOp.FieldLoadOp fieldLoadOp -> {
Field field;
VarHandle vh;
try {
field = fieldLoadOp.fieldReference().resolveToField(l);
vh = fieldLoadOp.fieldReference().resolveToHandle(l);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException(e);
}
// Requirement: the field must be a constant variable.
// Current checks:
// 1) The field is declared final.
// 2) The field type is a primitive or String.
// Missing check:
// 3) Verify the field is initialized and the initializer is a constant expression.
if ((field.getModifiers() & Modifier.FINAL) == 0 ||
!isConstantType(fieldLoadOp.fieldReference().type())) {
throw new NonConstantExpression();
}
Object v;
if ((field.getModifiers() & Modifier.STATIC) != 0) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can't you check for STATIC in the above -- e.g. when you also check for FINAL ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The first checks are for constant variables (as per JLS). The second check is due to the limitation we have in the model, as we can't get the value of an instance field.

// @@@ why using field.get fails ?
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

did you try field::get(null) ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, but it throws IllegalAccessException

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We discussed this offline -- this failure is due to the fact that Field::get uses caller sensitive methods -- so access checks are relative to the place where Field::get is called -- in this case jdk.incubator.code.dialect.java. When using VarHandle however, access checks are defined in terms of a user-provided lookup object -- which makes this work more reliably. I'd suggest dropping the comment and maybe say that we need to use VH here.

v = vh.get();
} else {
// we can't get the value of an instance field from the model
// we need the value of the receiver
throw new NonConstantExpression();
}
yield v instanceof String s ? s.intern() : v;
}
yield v instanceof String s ? s.intern() : v;
}
case ArithmeticOperation _ -> {
List<Object> values = op.operands().stream().map(v -> eval(l, v)).toList();
yield ArithmeticAndConvOpImpls.evaluate(op, values);
}
case JavaOp.ConditionalExpressionOp _ -> {
boolean p = evalBoolean(l, op.bodies().get(0));
Object t = eval(l, op.bodies().get(1));
Object f = eval(l, op.bodies().get(2));
yield p ? t : f;
}
case JavaOp.ConditionalAndOp _ -> {
boolean left = evalBoolean(l, op.bodies().get(0));
boolean right = evalBoolean(l, op.bodies().get(1));
yield left && right;
}
case JavaOp.ConditionalOrOp _ -> {
boolean left = evalBoolean(l, op.bodies().get(0));
boolean right = evalBoolean(l, op.bodies().get(1));
yield left || right;
}
default -> throw new NonConstantExpression();
};
}

private static Object eval(MethodHandles.Lookup l, Value v) {
if (v.declaringElement() instanceof JavaExpression e) {
return eval(l, (Op & JavaExpression) e);
case ArithmeticOperation _ -> {
List<Object> values = op.operands().stream().map(this::eval).toList();
yield ArithmeticAndConvOpImpls.evaluate(op, values);
}
case ConditionalExpressionOp _ -> {
boolean p = evalBoolean(op.bodies().get(0));
Object t = eval(op.bodies().get(1)); // I need to have a version that accept operand evaluator, for every eval* method
Comment thread
mabbay marked this conversation as resolved.
Outdated
Object f = eval(op.bodies().get(2));
yield p ? t : f;
}
case ConditionalAndOp _ -> {
boolean left = evalBoolean(op.bodies().get(0));
boolean right = evalBoolean(op.bodies().get(1));
yield left && right;
}
case ConditionalOrOp _ -> {
boolean left = evalBoolean(op.bodies().get(0));
boolean right = evalBoolean(op.bodies().get(1));
yield left || right;
}
default -> throw new NonConstantExpression();
};
m.put(op.result(), r);
return r;
}
throw new NonConstantExpression();
}

private static Object eval(MethodHandles.Lookup l, Body body) throws NonConstantExpression {
if (body.blocks().size() != 1 ||
!(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
yop.yieldValue() == null ||
!isConstantType(yop.yieldValue().type())) {
private Object eval(Value v) {
if (v.declaringElement() instanceof JavaExpression e) {
return eval((Op & JavaExpression) e);
}
throw new NonConstantExpression();
}
return eval(l, yop.yieldValue());
}

private static boolean evalBoolean(MethodHandles.Lookup l, Body body) throws NonConstantExpression {
Object eval = eval(l, body);
if (!(eval instanceof Boolean b)) {
throw new NonConstantExpression();
private Object eval(Body body) throws NonConstantExpression {
if (body.blocks().size() != 1 ||
!(body.entryBlock().terminatingOp() instanceof CoreOp.YieldOp yop) ||
yop.yieldValue() == null ||
!isConstantType(yop.yieldValue().type())) {
throw new NonConstantExpression();
}
return eval(yop.yieldValue());
}

return b;
}
private boolean evalBoolean(Body body) throws NonConstantExpression {
Object eval = eval(body);
if (!(eval instanceof Boolean b)) {
throw new NonConstantExpression();
}
return b;
}

private static boolean isConstant(CoreOp.ConstantOp op) {
return isConstantType(op.resultType()) && isConstantValue(op.value());
}
private static boolean isConstant(CoreOp.ConstantOp op) {
return isConstantType(op.resultType()) && isConstantValue(op.value());
}

private static boolean isConstant(VarOp op) {
// Requirement: the local variable must be a constant variable.
// Current checks:
// 1) The variable is initialized, and the initializer is a constant expression.
// 2) The variable type is a primitive or String.
// Missing check:
// 3) Ensure the variable is declared final
return isConstantType(op.varValueType()) &&
!op.isUninitialized() &&
// @@@ Add to VarOp
op.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
}
private static boolean isConstant(VarOp op) {
// Requirement: the local variable must be a constant variable.
// Current checks:
// 1) The variable is initialized, and the initializer is a constant expression.
// 2) The variable type is a primitive or String.
// Missing check:
// 3) Ensure the variable is declared final
return isConstantType(op.varValueType()) &&
!op.isUninitialized() &&
// @@@ Add to VarOp
op.result().uses().stream().noneMatch(u -> u.op() instanceof CoreOp.VarAccessOp.VarStoreOp);
}

private static boolean isConstantValue(Object o) {
return switch (o) {
case String _ -> true;
case Boolean _, Byte _, Short _, Character _, Integer _, Long _, Float _, Double _ -> true;
case null, default -> false;
};
}
private static boolean isConstantValue(Object o) {
return switch (o) {
case String _ -> true;
case Boolean _, Byte _, Short _, Character _, Integer _, Long _, Float _, Double _ -> true;
case null, default -> false;
};
}

private static boolean isConstantType(TypeElement e) {
return (e instanceof PrimitiveType && !VOID.equals(e)) || J_L_STRING.equals(e);
private static boolean isConstantType(TypeElement e) {
return (e instanceof PrimitiveType && !VOID.equals(e)) || J_L_STRING.equals(e);
}
}
}

Expand Down
Loading