Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
2d0219f
Add open enums and enum methods with tests
eliotmoss Apr 1, 2026
e89f1b8
Restructuring to eliminate VstEnum in favor of VstClass and to proces…
eliotmoss Apr 7, 2026
156212f
Fix -O3 CI failure
eliotmoss Apr 7, 2026
6984a02
Fix was not quite right
eliotmoss Apr 8, 2026
ec83c26
Need to set facts to suppress null check on enum method calls, to avo…
eliotmoss Apr 8, 2026
33a7f5d
Seems some null checks on enums were still getting through
eliotmoss Apr 8, 2026
aecbd77
Remove a dead field and code related to it. Add O3 tests to my CI Ma…
eliotmoss Apr 8, 2026
1404c4f
Strategy A Step 1: Remove per-case enum method overrides
eliotmoss Apr 10, 2026
c17bb9d
Strategy A Step 2: Enum subtype method virtual dispatch
eliotmoss Apr 16, 2026
46a19f2
Add edge-case tests for _ case + method; fix null mtable crash
eliotmoss Apr 16, 2026
1963918
Fix multi-method selective override dispatch
eliotmoss Apr 17, 2026
25dd610
Fix JVM backend for enum virtual dispatch
eliotmoss Apr 17, 2026
3348619
Add enum method closure tests with subtype overrides
eliotmoss Apr 17, 2026
7ebd2f0
JVM enum closure boxing: box tag to Integer for Oop receiver slot
eliotmoss Apr 17, 2026
a366267
Revert JVM boxing experiments; fix inline closure JVM path
eliotmoss Apr 17, 2026
2b13898
JVM: always box enum tag for user-defined enum methods
eliotmoss Apr 17, 2026
23a7a68
Add wasm-gc i31ref ops for future enum closure boxing
eliotmoss Apr 18, 2026
4af63c4
Wasm-gc enum closure boxing: box tag to i31ref for Oop receiver slot
eliotmoss Apr 18, 2026
50d3b4c
Add edge-case enum closure tests; fix constant Oop emission on JVM/wa…
eliotmoss Apr 19, 2026
5cb1e81
Fix constant enum Closure values in optimizer and JVM heap emission
eliotmoss Apr 19, 2026
fd0fa58
Fix JVM NPE when comparing null function values in enum params
eliotmoss Apr 19, 2026
fd4fbba
Fix JVM null function comparison and enum closure constant folding
eliotmoss Apr 19, 2026
c66e112
Add tests for enum subtype methods with extra fields
eliotmoss Apr 19, 2026
3ddb2af
Fix JVM closure adapter for non-overridden enum methods with call(f)
eliotmoss Apr 19, 2026
54ae150
Fix constant-folded enum closure tag-0 boxing with OopInt Val wrapper
eliotmoss Apr 19, 2026
dd819c7
Fix Eval.doOp TypeSubsume for Oop boxing with -wfts flag
eliotmoss Apr 19, 2026
8ebb16d
Fix -wfts enum closure failures with BoxEnumClosureReceiver config flag
eliotmoss Apr 19, 2026
5f6e397
Add EnumClosureBoxing benchmark for enum closure boxing overhead
eliotmoss Apr 20, 2026
ebb4ff0
Unify enum/variant dispatch in SsaNormalizer
eliotmoss Apr 20, 2026
d4dca29
Unify enum/variant mtable entry filling in Normalization
eliotmoss Apr 20, 2026
a3e1f82
Enable IrClass parent inheritance for enum subtypes
eliotmoss Apr 20, 2026
55f81e8
Remove markEnumSubtypeOverrides post-pass
eliotmoss Apr 20, 2026
fe8e981
Remove resolveEnumMethodImpl; use shared resolveMethodImpl
eliotmoss Apr 20, 2026
45e9b3e
Add shared hierarchical-type helpers; reduce EnumType.? usage
eliotmoss Apr 20, 2026
94a93c1
Replace EnumType.? with V3 helpers across IR and optimizer
eliotmoss Apr 20, 2026
e75935d
Replace EnumType.? with V3 helpers across SSA, backends, evaluator
eliotmoss Apr 20, 2026
8d0613f
Fix RaClass field copy and numberVariant DFS for enum subtypes
eliotmoss Apr 20, 2026
ef53144
Fix EnumType/ClassType match ordering and restore resolveEnumMethodImpl
eliotmoss Apr 21, 2026
e010cd7
Broaden RefLayout field accessors in Eval.v3 to handle enum types
eliotmoss Apr 21, 2026
63f5d12
Merge EnumType into ClassType; eliminate separate enum type class
eliotmoss Apr 21, 2026
f9ea704
Use RC_ENUM flag instead of V3.isEnum() in normalization pipeline
eliotmoss Apr 21, 2026
48ac667
Revert RC_ENUM usage where it conflicts with variant-enums
eliotmoss Apr 21, 2026
a0fae9d
Fix enum virtual dispatch fallback when mtable is null
eliotmoss Apr 21, 2026
0c626a5
Give enums VariantNorm; add isFlattened() to decouple from isUnboxed()
eliotmoss Apr 21, 2026
eb76ec9
Eliminate isFlattened(); unify enum/variant dispatch guards; add 15 t…
eliotmoss Apr 21, 2026
da309bc
Strategy B: synthetic VstClass/IrClass/RaClass per enum case
eliotmoss Apr 21, 2026
5b2be12
Fix three enum dispatch bugs; add per-case method execution tests
eliotmoss Apr 22, 2026
2ff4b13
Skip NullCheck on enum receivers during early inlining
eliotmoss Apr 22, 2026
4553cb1
Remove dead enum mtable code; fix _ case liveness and mtable slots; u…
eliotmoss Apr 22, 2026
da2bed3
Add EnumScalability benchmark for Strategy A vs B comparison
eliotmoss Apr 22, 2026
28a0f4b
Elide RaClasses for enum cases without per-case overrides
eliotmoss Apr 22, 2026
62ecac0
Queue-based per-case enum liveness analysis
eliotmoss Apr 22, 2026
1fc2711
Add EnumLiveness and EnumOverrides benchmarks
eliotmoss Apr 22, 2026
8eccaf6
Add compact mtable optimization for enum virtual dispatch
eliotmoss Apr 23, 2026
57bee32
Skip M_ABSTRACT methods in JvmV3EnumGen.buildMethod
eliotmoss Apr 27, 2026
33bd168
Wire up EnumOverrides/EnumLiveness benchmarks for wasm and wasm-gc
eliotmoss Apr 27, 2026
a1eb815
Simplify wasm bench runner: drop fragile Symbol/unreachable catches
eliotmoss Apr 27, 2026
f9980be
Fix lookupEnumExprMember to pass receiver (not expr.expr) for VstField
eliotmoss 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
111 changes: 94 additions & 17 deletions aeneas/src/core/Eval.v3
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ component Eval {
return b == null;
}
CLASS_QUERY, VARIANT_QUERY => {
if (V3.isEnum(tt)) {
// enum subtype query: check integer tag is in [tagLo, tagHi]
var decl = V3.getDecl(tt);
var tagVal = Int.unbox(val);
return tagVal >= decl.tagLo && tagVal <= decl.tagHi;
}
if (val == null) return false;
if (tt.open()) return false;
return Record.?(val) && TypeSystem.isSubtype(Record.!(val).rtype, tt);
Expand Down Expand Up @@ -810,19 +816,34 @@ def evalOp(op: Operator, args: Arguments) -> Result {
return if(object != null, object.values[field.index]);
}
VariantGetMethod(method) => {
var ta = args.getTypeArgs();
if (V3.isEnum(ta[0])) {
var spec = lookupEnumVirtual(args, method);
return Closure.new(args.vals[0], spec);
}
var object = args.r(0);
var spec = args.getClosedIrSpec(method);
if (spec == null) return args.notFoldable(null);
return Closure.new(object, spec);
}
VariantGetVirtual(method) => {
var ta = args.getTypeArgs();
if (V3.isEnum(ta[0])) {
var spec = lookupEnumVirtual(args, method);
return Closure.new(args.vals[0], spec);
}
var object = getRecordReceiver(args);
var spec = args.getClosedIrSpec(method);
if (spec == null) return args.notFoldable(null);
spec = lookupVariantVirtual(args, object, spec);
return Closure.new(object, spec);
}
VariantGetSelector(selector) => {
var ta = args.getTypeArgs();
if (V3.isEnum(ta[0])) {
var spec = lookupEnumVirtual(args, selector);
return FuncVal.new(spec);
}
var object = getRecordReceiver(args);
var spec = args.getClosedIrSpec(selector);
if (spec == null) return args.notFoldable(null);
Expand All @@ -831,8 +852,9 @@ def evalOp(op: Operator, args: Arguments) -> Result {
}
//----------------------------------------------------------------------------
NullCheck => {
var object = args.r(0);
if (object == null) return args.throw(V3Exception.NullCheck, null);
var val = args.vals[0];
if (val == null) return args.throw(V3Exception.NullCheck, null);
if (!Record.?(val)) return Values.BOTTOM; // non-ref values (e.g. enums) are never null
return Values.BOTTOM;
}
BoundsCheck => {
Expand Down Expand Up @@ -868,6 +890,7 @@ def evalOp(op: Operator, args: Arguments) -> Result {
if (ft == null) return args.notFoldable(null);
if (tt == null) return args.notFoldable(null);
var val = args.vals[0];
if (tt == Oop.TYPE || ft == Oop.TYPE) return val;
var r = Eval.doCast0(ft, tt, val);
if (r.0) return r.1;
return args.throw(V3Exception.InternalError, "subsume should never fail");
Expand Down Expand Up @@ -914,13 +937,23 @@ def evalOp(op: Operator, args: Arguments) -> Result {
return args.tailCall(spec, object, 1, args.vals.length);
}
CallVariantVirtual(method) => {
var ta = args.getTypeArgs();
if (V3.isEnum(ta[0])) {
var spec = lookupEnumVirtual(args, method);
return args.tailCall(spec, args.vals[0], 1, args.vals.length);
}
var object = getRecordReceiver(args);
var spec = args.getClosedIrSpec(method);
if (spec == null) return args.notFoldable(null);
spec = lookupVariantVirtual(args, object, spec);
return args.tailCall(spec, object, 1, args.vals.length);
}
CallVariantSelector(selector) => {
var ta = args.getTypeArgs();
if (V3.isEnum(ta[0])) {
var spec = lookupEnumVirtual(args, selector);
return args.tailCall(spec, args.vals[0], 1, args.vals.length);
}
var object = getRecordReceiver(args);
var spec = args.getClosedIrSpec(selector);
if (spec == null) return args.notFoldable(null);
Expand Down Expand Up @@ -959,11 +992,11 @@ def evalOp(op: Operator, args: Arguments) -> Result {
}
RefLayoutGetField(offset, order) => {
var ref = args.ref(0);
return doRefLayoutGetField(args, args.getPrimTypeArg(1), ref, offset, order);
return doRefLayoutGetField(args, args.getTypeArg(1), ref, offset, order);
}
RefLayoutSetField(offset, order) => {
var ref = args.ref(0);
return doRefLayoutSetField(args, args.getPrimTypeArg(1), ref, offset, order, args.vals[1]);
return doRefLayoutSetField(args, args.getTypeArg(1), ref, offset, order, args.vals[1]);
}
RefLayoutAtRepeatedField(offset, scale, max) => {
var ref = args.ref(0);
Expand All @@ -977,25 +1010,25 @@ def evalOp(op: Operator, args: Arguments) -> Result {
var ref = args.ref(0);
var index = args.i(1);
if (u32.view(index) >= u32.view(max)) return args.throw(V3Exception.BoundsCheck, null);
return doRefLayoutGetField(args, args.getPrimTypeArg(1), ref, offset + scale * index, order);
return doRefLayoutGetField(args, args.getTypeArg(1), ref, offset + scale * index, order);
}
RefLayoutSetRepeatedField(offset, scale, max, order) => {
var ref = args.ref(0);
var index = args.i(1);
if (u32.view(index) >= u32.view(max)) return args.throw(V3Exception.BoundsCheck, null);
return doRefLayoutSetField(args, args.getPrimTypeArg(1), ref, offset + scale * index, order, args.vals[2]);
return doRefLayoutSetField(args, args.getTypeArg(1), ref, offset + scale * index, order, args.vals[2]);
}
ByteArrayGetField(offset, order) => {
var array = args.r(0);
var i_offset = if(ArrayRangeStart.?(args.vals[1]), ArrayRangeStart.!(args.vals[1]).start, args.i(1));
// XXX: Refactor so no intermediate ByteArrayOffset object needed
return doRefLayoutGetField(args, args.getPrimTypeArg(0), ByteArrayOffset.new(array, offset), i_offset, order);
return doRefLayoutGetField(args, args.getTypeArg(0), ByteArrayOffset.new(array, offset), i_offset, order);
}
ByteArraySetField(offset, order) => {
var array = args.r(0);
var i_offset = if(ArrayRangeStart.?(args.vals[1]), ArrayRangeStart.!(args.vals[1]).start, args.i(1));
// XXX: Refactor so no intermediate ByteArrayOffset object needed
return doRefLayoutSetField(args, args.getPrimTypeArg(0), ByteArrayOffset.new(array, offset), i_offset, order, args.vals[2]);
return doRefLayoutSetField(args, args.getTypeArg(0), ByteArrayOffset.new(array, offset), i_offset, order, args.vals[2]);
}

//----------------------------------------------------------------------------
Expand Down Expand Up @@ -1047,9 +1080,9 @@ def evalOp(op: Operator, args: Arguments) -> Result {
return args.unimplemented();
}

def doRefLayoutGetField(args: Arguments, fieldType: PrimType, ref: ByteArrayOffset, offset: int, order: ByteOrder) -> Result {
def doRefLayoutGetField(args: Arguments, fieldType: Type, ref: ByteArrayOffset, offset: int, order: ByteOrder) -> Result {
if (ref == null || ref.array == null) return args.throw(V3Exception.NullCheck, null);
var v = ref.read(order, offset, fieldType.packedByteSize);
var v = ref.read(order, offset, if(PrimType.?(fieldType), PrimType.!(fieldType).packedByteSize, ClassType.!(fieldType).byteSize));
match (fieldType) {
x: BoolType => {
return Bool.box((v & 1) != 0);
Expand All @@ -1064,18 +1097,20 @@ def doRefLayoutGetField(args: Arguments, fieldType: PrimType, ref: ByteArrayOffs
I64, U64 => return Long.box(long.view(v));
}
}
x: EnumType => {
if (v >= x.enumDecl.cases.length) v = 0; // out-of-bounds tag => 0
return Int.box(int.view(v)); // note: no long enum values
}
x: FloatType => {
if (x.is64) return Float64Val.new(v);
else return Float32Val.new(u32.view(v));
}
_ => return args.throw("EvalException", Strings.format1("invalid RefLayoutField type %q", fieldType.render));
_ => {
if (V3.isEnum(fieldType)) {
if (v >= ClassType.!(fieldType).classDecl.cases.length) v = 0;
return Int.box(int.view(v));
}
return args.throw("EvalException", Strings.format1("invalid RefLayoutField type %q", fieldType.render));
}
}
}
def doRefLayoutSetField(args: Arguments, fieldType: PrimType, ref: ByteArrayOffset, offset: int, order: ByteOrder, val: Val) -> Result {
def doRefLayoutSetField(args: Arguments, fieldType: Type, ref: ByteArrayOffset, offset: int, order: ByteOrder, val: Val) -> Result {
if (ref == null || ref.array == null) return args.throw(V3Exception.NullCheck, null);
var bits: u64 = 0, signed = false;
match (val) {
Expand All @@ -1085,7 +1120,8 @@ def doRefLayoutSetField(args: Arguments, fieldType: PrimType, ref: ByteArrayOffs
v: Float32Val => bits = v.bits;
v: Float64Val => bits = v.bits;
}
ref.write(order, offset, fieldType.packedByteSize, bits);
var size = if(PrimType.?(fieldType), PrimType.!(fieldType).packedByteSize, ClassType.!(fieldType).byteSize);
ref.write(order, offset, size, bits);
return Values.BOTTOM;
}

Expand Down Expand Up @@ -1147,6 +1183,47 @@ def lookupClassVirtual(args: Arguments, object: Record, spec: IrSpec) -> IrSpec
args.throw(V3Exception.NullCheck, null);
return null;
}
def lookupEnumVirtual(args: Arguments, member: IrMember) -> IrSpec {
var ta = args.getTypeArgs();
var enumType = ClassType.!(ta[0]);
var tag = Int.unbox(args.vals[0]);
var targetDecl = findEnumDeclForTag(enumType.classDecl, tag);
var ir = args.getProgram().ir;
var m: IrMethod;
// Check synthetic case decl first (Strategy B per-case overrides).
if (targetDecl.cases != null) {
for (c in targetDecl.cases) {
if (c.decl != null && c.decl.isSynthetic && c.enumTag == tag) {
var caseIc = ir.getIrClass(c.decl.typeCon.create0());
if (caseIc != null && member.index < caseIc.methods.length) {
var candidate = caseIc.methods[member.index];
if (candidate != null) { m = candidate; break; }
}
}
}
}
if (m == null) {
var vc = targetDecl;
while (vc != null) {
var ic = ir.makeIrClass(vc.getDeclaredType());
if (ic != null && member.index < ic.methods.length) {
var candidate = ic.methods[member.index];
if (candidate != null) { m = candidate; break; }
}
vc = vc.parentEnum;
}
}
if (m == null) m = IrMethod.!(member);
return IrSpec.new(ta[0], ta, m);
}
def findEnumDeclForTag(decl: VstClass, tag: int) -> VstClass {
for (l = decl.subtypeOrder; l != null; l = l.tail) {
if (l.head.tagLo <= tag && tag <= l.head.tagHi) {
return findEnumDeclForTag(l.head, tag);
}
}
return decl;
}
def lookupVariantVirtual(args: Arguments, object: Record, spec: IrSpec) -> IrSpec {
if (object != null) return args.getProgram().ir.resolveMethodImpl(object.rtype, spec);
return args.getProgram().ir.resolveVariantDefaultMethodImpl(spec);
Expand Down
6 changes: 6 additions & 0 deletions aeneas/src/core/Operator.v3
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,12 @@ component V3Op {
var paramTypes = Arrays.prepend(ftype, Function.getParamTypeArray(ftype));
return newOp0(Opcode.CallFunction, [ftype], paramTypes, Function.getReturnType(ftype));
}
def newCallFunctionDirect(ftype: Type) -> Operator {
ftype = Function.funcRefType(ftype);
if (ftype.typeCon.kind != Kind.FUNCREF) return V3.fail("only function types allowed");
var paramTypes = Arrays.prepend(ftype, Function.getParamTypeArray(ftype));
return newOp0(Opcode.CallFunction, [ftype], paramTypes, Function.getReturnType(ftype));
}
def newCreateClosure(methodRef: IrSpec, closure: Type) -> Operator {
var typeArgs = methodRef.typeArgs;
return newOp0(Opcode.CreateClosure(methodRef.asMethod()), typeArgs, [closure], methodRef.getBoundType());
Expand Down
9 changes: 9 additions & 0 deletions aeneas/src/core/Value.v3
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ class Val extends Result {
def equals(val: Val) -> bool { return this == val; }
def hash() -> int { return 0; }
}
// An OopInt wraps an integer that has been boxed to Oop (e.g. an enum tag).
// Distinct from null so that emitters can produce Integer.valueOf(v) on JVM
// or ref.i31(v) on wasm-gc, rather than a null reference.
class OopInt(v: int) extends Val {
def equals(other: Val) -> bool {
return OopInt.?(other) && OopInt.!(other).v == v;
}
def hash() -> int { return v; }
}
// An Exception represents an exceptional result (i.e. not a value), for example a
// safety violation such as !NullCheckException or !DivideByZeroException.
class Exception(error: string, msg: string, trace: List<Source>) extends Result { }
Expand Down
9 changes: 4 additions & 5 deletions aeneas/src/debug/Dwarf.v3
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,7 @@ class DwarfInfoSection(abbrev: DwarfAbbrevSection) {
encoding = if (V3.isSigned(x), DW.DW_ATE_signed, DW.DW_ATE_unsigned);
emitBaseType(t, byteSize, encoding);
}
x: ClassType => emitClassType(x);
x: EnumType => emitEnumType(x);
x: ClassType => if (x.classDecl.isEnum()) emitEnumType(x); else emitClassType(x);
x: ArrayType => emitArrayType(x);
_ => emitUnspecifiedType(t);
}
Expand Down Expand Up @@ -353,20 +352,20 @@ class DwarfInfoSection(abbrev: DwarfAbbrevSection) {
_ => ;
}
}
def emitEnumType(t: EnumType) {
def emitEnumType(t: ClassType) {
w.put_uleb32(abbrev.getAbbrev(DwarfAbbrevTag.EnumurationType));
buf.reset();
t.render(buf);
buf.send(w.putr);
w.putb(0);
w.putb(t.byteSize);
for (c in t.enumDecl.cases) {
for (c in t.classDecl.cases) {
w.put_uleb32(abbrev.getAbbrev(DwarfAbbrevTag.Enumurator));
buf.reset();
c.render(buf);
buf.send(w.putr);
w.putb(0);
w.put_uleb32(u32.!(c.tag));
w.put_uleb32(u32.!(c.localTag));
}
w.putb(0);
}
Expand Down
1 change: 1 addition & 0 deletions aeneas/src/ir/FunctionWrappers.v3
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,7 @@ class FunctionWrappers {
for (l = rc.subtypes; l != null; l = l.tail) {
var rcThis = l.head;
var impl = rn.resolveMethodImpl(rcThis, rm);
if (impl == null) continue;
var orig = impl.orig;
var origType = if(impl.spec == null, impl.orig.getMethodType(), impl.spec.getMethodType());
var origSig = FuncType.!(origType).sig();
Expand Down
17 changes: 11 additions & 6 deletions aeneas/src/ir/Ir.v3
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class IrSelector extends IrMember {
// indexed by class ID, where class IDs are assigned during Reachability analysis.
class IrMtable(root: IrMethod, rootId: int, table: Array<IrMethod>) extends IrItem {
var record: Record;
var compactRecord: Record;

def render(buf: StringBuilder) -> StringBuilder {
buf.puts("[");
Expand Down Expand Up @@ -353,8 +354,10 @@ class IrModule {
}
def resolveMethodImpl(classType: Type, irSpec: IrSpec) -> IrSpec {
var ic = makeIrClass(classType);
var m = ic.methods[irSpec.member.index];
if (m == irSpec.member) return irSpec;
var idx = irSpec.member.index;
if (idx >= ic.methods.length) return irSpec;
var m = ic.methods[idx];
if (m == null || m == irSpec.member) return irSpec;
while (ic.inherits(m)) {
classType = V3.getSuperType(classType);
ic = ic.parent;
Expand Down Expand Up @@ -388,14 +391,16 @@ class IrModule {
if (ctype == null) return null;
var ic = classMap[ctype];
if (ic == null) {
if (V3.isClass(ctype)) {
if (V3.isEnum(ctype)) {
var decl = V3.getDecl(ctype);
var sc = if(decl.parentEnum != null, makeIrClass(decl.parentEnum.getDeclaredType()));
ic = newIrClass(ctype, sc, decl);
classMap[ctype] = ic;
} else if (V3.isClass(ctype)) {
ic = newIrClassWithSuper(ctype, V3.classDecl(ctype));
} else if (V3.isComponent(ctype)) {
ic = newIrClass(ctype, null, V3.componentDecl(ctype));
classMap[ctype] = ic;
} else if (EnumType.?(ctype)) {
ic = newIrClass(ctype, null, EnumType.!(ctype).enumDecl);
classMap[ctype] = ic;
}
}
return ic;
Expand Down
Loading
Loading