From 76f40809051c3f1d826e7e86719e276c95fabc42 Mon Sep 17 00:00:00 2001 From: "Ben L. Titzer" Date: Sun, 23 Jun 2024 09:57:10 -0400 Subject: [PATCH] Replace TupleVal with BoxVal --- aeneas/src/core/Eval.v3 | 32 +++---- aeneas/src/core/Program.v3 | 15 ++-- aeneas/src/core/Value.v3 | 35 ++++++++ aeneas/src/ic/Ic.v3 | 6 +- aeneas/src/ir/Ir.v3 | 9 +- aeneas/src/ir/Normalization.v3 | 10 +-- aeneas/src/ir/Reachability.v3 | 4 +- aeneas/src/mach/MachProgram.v3 | 4 +- aeneas/src/main/Version.v3 | 2 +- aeneas/src/ssa/SsaInterpreter.v3 | 6 +- aeneas/src/ssa/SsaOptimizer.v3 | 4 +- aeneas/src/types/Tuple.v3 | 12 --- aeneas/src/v3/V3.v3 | 2 +- aeneas/test/SsaInstrReducerTest.v3 | 4 +- aeneas/test/Utils.v3 | 2 +- aeneas/test/ValueTest.v3 | 133 +++++++++++++++++++++++++++++ 16 files changed, 221 insertions(+), 59 deletions(-) create mode 100644 aeneas/test/ValueTest.v3 diff --git a/aeneas/src/core/Eval.v3 b/aeneas/src/core/Eval.v3 index 7ef0c1272..0236c5296 100644 --- a/aeneas/src/core/Eval.v3 +++ b/aeneas/src/core/Eval.v3 @@ -21,8 +21,8 @@ component Eval { // recursively check nested types of tuples var values: Array; if (val == null) values = Array.new(Lists.length(ft.nested)); - else if (!TupleVal.?(val)) return throw; - else values = TupleVal.!(val).values; + else if (!BoxVal.?(val)) return throw; + else values = BoxVal.!(val).values; var a = ft.nested, b = tt.nested, i = 0, rv = Array.new(values.length); while (a != null) { @@ -33,7 +33,7 @@ component Eval { rv[i] = r.1; a = a.tail; b = b.tail; i++; } - if (b == null) return (true, TupleVal.new(rv)); + if (b == null) return (true, BoxVal.new(null, rv)); return throw; } CLASS_CAST, VARIANT_CAST => { @@ -110,8 +110,8 @@ component Eval { TUPLE_QUERY => { var values: Array; if (val == null) values = Array.new(Lists.length(ft.nested)); - else if (!TupleVal.?(val)) return false; - else values = TupleVal.!(val).values; + else if (!BoxVal.?(val)) return false; + else values = BoxVal.!(val).values; var a = ft.nested, b = tt.nested, i = 0; while (a != null) { @@ -486,12 +486,12 @@ def evalOp(op: Operator, args: Arguments) -> Result { TupleCreate(length) => { var vals = Array.new(length); for (i < vals.length) vals[i] = args.vals[i]; - return TupleVal.new(vals); + return BoxVal.new(null, vals); } TupleGetElem(index) => { var tuple = args.vals[0]; if (tuple == Values.BOTTOM) return Values.BOTTOM; - return (TupleVal.!(tuple)).values[index]; + return (BoxVal.!(tuple)).values[index]; } //---------------------------------------------------------------------------- ArrayAlloc => { @@ -511,7 +511,7 @@ def evalOp(op: Operator, args: Arguments) -> Result { for (i < length) { var vals = Array.new(elems); for (j < elems) vals[j] = args.vals[p++]; - array.values[i] = TupleVal.new(vals); + array.values[i] = BoxVal.new(null, vals); } return array; } @@ -535,17 +535,17 @@ def evalOp(op: Operator, args: Arguments) -> Result { if (u32.view(index) >= u32.view(array.values.length)) return args.throw(V3Exception.BoundsCheck, null); var val = array.values[index]; if (val == null) return null; - return TupleVal.!(val).values[elem]; + return BoxVal.!(val).values[elem]; } ArraySetElemElem(elem) => { var array = args.r(0), index = args.i(1); if (array == null) return args.throw(V3Exception.NullCheck, null); if (u32.view(index) >= u32.view(array.values.length)) return args.throw(V3Exception.BoundsCheck, null); - var tval = TupleVal.!(array.values[index]); + var tval = BoxVal.!(array.values[index]); var val = args.vals[2]; if (tval == null) { var len = Tuple.length(V3Array.elementType(args.getTypeArg(0))); - array.values[index] = tval = TupleVal.new(Array.new(len)); + array.values[index] = tval = BoxVal.new(null, Array.new(len)); } tval.values[elem] = val; return val; @@ -629,7 +629,7 @@ def evalOp(op: Operator, args: Arguments) -> Result { var array = args.r(0), start = args.vals[1], index = args.i(2); if (array == null) return args.throw(V3Exception.NullCheck, null); // TODO: should not happen because prior bounds check if (start != null) index += ArrayRangeStart.!(start).start; - var tval = TupleVal.!(array.values[index]); + var tval = BoxVal.!(array.values[index]); if (tval == null) return null; return tval.values[elem]; } @@ -643,10 +643,10 @@ def evalOp(op: Operator, args: Arguments) -> Result { var array = args.r(0), start = args.vals[1], index = args.i(2); if (array == null) return args.throw(V3Exception.NullCheck, null); // TODO: should not happen because prior bounds check if (start != null) index += ArrayRangeStart.!(start).start; - var tval = TupleVal.!(array.values[index]); + var tval = BoxVal.!(array.values[index]); if (tval == null) { var len = Tuple.length(V3Array.elementType(args.getTypeArg(0))); - array.values[index] = tval = TupleVal.new(Array.new(len)); + array.values[index] = tval = BoxVal.new(null, Array.new(len)); } return tval.values[elem] = args.vals[3]; // TODO: in-place mutation of tuple element } @@ -1056,13 +1056,13 @@ def getRecordReceiver(args: Arguments) -> Record { // TODO: this a giant hack for adapting arguments to void calls var r = args.vals[0]; if (Record.?(r)) return Record.!(r); - if (TupleVal.?(r)) return Record.!(TupleVal.!(r).values[0]); + if (BoxVal.?(r)) return Record.!(BoxVal.!(r).values[0]); if (r != null) args.throw(V3Exception.InternalError, "expected record receiver for call"); return null; } def getReceiver(args: Arguments) -> Val { // TODO: this a giant hack for adapting arguments to void calls var r = args.vals[0]; - if (TupleVal.?(r)) return TupleVal.!(r).values[0]; + if (BoxVal.?(r)) return BoxVal.!(r).values[0]; return r; } diff --git a/aeneas/src/core/Program.v3 b/aeneas/src/core/Program.v3 index 048f399b4..f090a0f7f 100644 --- a/aeneas/src/core/Program.v3 +++ b/aeneas/src/core/Program.v3 @@ -93,12 +93,8 @@ class Program { VOID, BOOL, INT, FLOAT, ARRAY, COMPONENT, CLASS, CLOSURE, FUNCREF, ANYFUNC, ENUM, ENUM_SET, POINTER, REF, RANGE, RANGE_START => return Values.BOTTOM; VARIANT => { - if (ir.defaultRecords != null) { - var record = ir.defaultRecords[t]; - if (record != null) return record; - } else { - ir.defaultRecords = TypeUtil.newTypeMap(); - } + var prev = ir.getDefaultVal(t); + if (prev != null) return prev; var ct = ClassType.!(t); var decl = ct.classDecl; if (decl.superclass == null) { @@ -106,7 +102,7 @@ class Program { } var ic = ir.makeIrClass(t); var record = newRecord(t, ic.fields.length); - ir.defaultRecords[t] = record; + ir.defaultValues[t] = record; var typeArgs = ct.getTypeArgs(); for (i < ic.fields.length) { var ft = ic.fields[i].fieldType; @@ -116,8 +112,11 @@ class Program { return record; } TUPLE => { + var prev = ir.getDefaultVal(t); + if (prev != null) return prev; var at = Tuple.toTypeArray(t); - var tv = TupleVal.new(Array.new(at.length)); + var tv = BoxVal.new(null, Array.new(at.length)); + ir.defaultValues[t] = tv; for (i < at.length) tv.values[i] = getDefaultValue(at[i]); return tv; } diff --git a/aeneas/src/core/Value.v3 b/aeneas/src/core/Value.v3 index 3562f213f..f88aebd84 100644 --- a/aeneas/src/core/Value.v3 +++ b/aeneas/src/core/Value.v3 @@ -73,3 +73,38 @@ component Values { return if(v != null, v.hash()); } } + +def UNDEFINED_HASH = -1; +def RECURSIVE_HASH = -2; +class BoxVal(t: Type, values: Array) extends Val { + private var id = UID.next++; + private var h0: int = 0; + + def equals(other: Val) -> bool { + if (this == other) return true; + match (other) { + that: BoxVal => { + if (this.id == that.id) return true; + if (this.t != that.t) return false; + if (this.hash() != that.hash()) return false; + for (i < values.length) if(!Values.equal(this.values[i], that.values[i])) return false; + if (this.id < that.id) that.id = this.id; + else this.id = that.id; + return true; + } + null => return t == null && Values.deepEqualBottom(values); + _ => return false; + } + } + def hash() -> int { + if (h0 >= 0) return h0; + var nhash = if(t == null, values.length, t.hash); // initial new hash + if (h0 == RECURSIVE_HASH) return h0 = int.max & nhash; // cycle check + h0 = RECURSIVE_HASH; + for (v in values) { + nhash = nhash * 33 + Values.hash(v); // recursive hash + if (h0 >= 0) return h0; // recursive cycle detected + } + return h0 = int.max & nhash; + } +} diff --git a/aeneas/src/ic/Ic.v3 b/aeneas/src/ic/Ic.v3 index 17dce6748..edf380efd 100644 --- a/aeneas/src/ic/Ic.v3 +++ b/aeneas/src/ic/Ic.v3 @@ -726,13 +726,13 @@ class IcInterpreter(prog: Program, compile: IrSpec -> IcMethod) extends Argument } } else if (numParams == 1) { // collapse into tuple - regs[argRp] = TupleVal.new(Arrays.range(regs, argRp, argRp + numArgs)); + regs[argRp] = BoxVal.new(null, Arrays.range(regs, argRp, argRp + numArgs)); } else { // expand tuple var last = argRp + numArgs - 1, v = regs[last]; - if (TupleVal.?(v)) { + if (BoxVal.?(v)) { // expand tuple - var tv = TupleVal.!(v); + var tv = BoxVal.!(v); for (i = 0; last < max; i++) { regs[last++] = tv.values[i]; } diff --git a/aeneas/src/ir/Ir.v3 b/aeneas/src/ir/Ir.v3 index 0641d98ad..4c7e5c39b 100644 --- a/aeneas/src/ir/Ir.v3 +++ b/aeneas/src/ir/Ir.v3 @@ -259,7 +259,7 @@ class IrModule { def methods = Vector.new(); def init = Vector.new(); def roots = Vector.new(); - var defaultRecords: HashMap; + var defaultValues: HashMap; def addRoot(name: string, meth: IrSpec) -> int { var index = roots.length; @@ -352,4 +352,11 @@ class IrModule { def isEnum(t: Type) -> bool { return makeIrClass(t).facts.C_ENUM; } + def getDefaultVal(t: Type) -> Val { + if (defaultValues == null) { + defaultValues = TypeUtil.newTypeMap(); + return Values.BOTTOM; + } + return defaultValues[t]; + } } diff --git a/aeneas/src/ir/Normalization.v3 b/aeneas/src/ir/Normalization.v3 index 97af139e8..70ffd5d2d 100644 --- a/aeneas/src/ir/Normalization.v3 +++ b/aeneas/src/ir/Normalization.v3 @@ -401,7 +401,7 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) if (constructor == null || constructor.source != null) constructor = rc.orig.methods[0] = getClassAllocIr(vn); var r = variantInterp.invoke(Closure.new(Values.BOTTOM, IrSpec.new(vn.oldType, TypeUtil.NO_TYPES, constructor)), inputs.extract()); match (r) { - x: TupleVal => for (i < x.values.length) result[i] = x.values[i]; + x: BoxVal => for (i < x.values.length) result[i] = x.values[i]; x: Val => result[0] = x; } } else { @@ -425,7 +425,7 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) // normalize the live instances of a mixed array type def normMixedArrayRecord(rt: ArrayNorm, oldRecord: Record, newRecord: Record) { def v = oldRecord.values; - for (i < v.length) newRecord.values[i] = normAsTupleVal(v[i], rt.enorm); + for (i < v.length) newRecord.values[i] = normAsBoxVal(v[i], rt.enorm); } // normalize the live instances of a complex (i.e. size-N element) array type def normComplexArrayRecord(rt: ArrayNorm, oldRecord: Record, newRecords: Array) { @@ -452,12 +452,12 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) _ => return v; } } - def normAsTupleVal(v: Val, tn: TypeNorm) -> Val { + def normAsBoxVal(v: Val, tn: TypeNorm) -> Val { if (v == null) return v; var values = Array.new(tn.size); normValIntoArray(v, tn, values, 0); if (tn.size == 1) return values[0]; - return TupleVal.new(values); + return BoxVal.new(null, values); } def layoutVtable(rc: RaClass) { var vtable = Vector.new(); @@ -560,7 +560,7 @@ class ReachabilityNormalizer(config: NormalizerConfig, ra: ReachabilityAnalyzer) array[index] = FuncVal.new(ref); normValIntoArray(x.val, norm(ref.receiver), array, index + 1); } - x: TupleVal => { + x: BoxVal => { // tuple: recursively normalize all of the sub var tnn = TupleNorm.!(tn).nested; for (i < tnn.length) { diff --git a/aeneas/src/ir/Reachability.v3 b/aeneas/src/ir/Reachability.v3 index 783215e94..c3dfda4fa 100644 --- a/aeneas/src/ir/Reachability.v3 +++ b/aeneas/src/ir/Reachability.v3 @@ -128,7 +128,7 @@ class ReachabilityAnalyzer(compilation: Compilation) { null => ; x: Record => queue.add(analyzeRecord, x); x: Closure => queue.add(analyzeValue, x); - x: TupleVal => queue.add(analyzeValue, x); + x: BoxVal => queue.add(analyzeValue, x); x: ArrayRangeVal => queue.add(analyzeRecord, x.array); } } @@ -149,7 +149,7 @@ class ReachabilityAnalyzer(compilation: Compilation) { getMethod(null, rm); if (x.val != null) analyzeValue(x.val); } - x: TupleVal => for(e in x.values) analyzeValue(e); + x: BoxVal => for(e in x.values) analyzeValue(e); } } // analyze a record diff --git a/aeneas/src/mach/MachProgram.v3 b/aeneas/src/mach/MachProgram.v3 index 5fa9b36dd..8eca96b8e 100644 --- a/aeneas/src/mach/MachProgram.v3 +++ b/aeneas/src/mach/MachProgram.v3 @@ -353,7 +353,7 @@ class MachProgram extends TargetProgram { // mixed array var et = V3Array.elementType(r.rtype), ets = Tuple.toTypeArray(et); for (i < v.length) { - var tv = TupleVal.!(v[i]), elemBegin = start + i * rep.elemScale + rep.headerSize; + var tv = BoxVal.!(v[i]), elemBegin = start + i * rep.elemScale + rep.headerSize; for (j < ets.length) { // XXX: special case Array, Array, Array for performance encodeVal(w.at(elemBegin + rep.offsets[j]), if(tv == null, null, tv.values[j]), ets[j]); @@ -564,7 +564,7 @@ class MachProgram extends TargetProgram { x: Record => addrOfRecord(x); x: FuncVal => addrOfMethod(x.memberRef.asMethod()); x: Closure => { layoutVal(x.val); addrOfMethod(x.memberRef.asMethod()); } - x: TupleVal => for (e in x.values) layoutVal(e); + x: BoxVal => for (e in x.values) layoutVal(e); } } def encodeRegion(region: Region, w: MachDataWriter) { diff --git a/aeneas/src/main/Version.v3 b/aeneas/src/main/Version.v3 index 877c958a0..124835580 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.1741"; + def version: string = "III-7.1742"; var buildData: string; } diff --git a/aeneas/src/ssa/SsaInterpreter.v3 b/aeneas/src/ssa/SsaInterpreter.v3 index 98e6b8773..864b8dbe0 100644 --- a/aeneas/src/ssa/SsaInterpreter.v3 +++ b/aeneas/src/ssa/SsaInterpreter.v3 @@ -103,7 +103,7 @@ class SsaInterpreter(prog: Program, genSsa: (IrSpec, int) -> SsaGraph) { _ => { var vals = Array.new(x.inputs.length); for (i < x.inputs.length) vals[i] = getVal(x.inputs[i].dest); - r = TupleVal.new(vals); + r = BoxVal.new(null, vals); } } if (frame.prev == null) { @@ -293,14 +293,14 @@ class SsaInterpreter(prog: Program, genSsa: (IrSpec, int) -> SsaGraph) { // collapse into tuple var vals = Array.new(args.length); for (i < vals.length) vals[i] = args[i]; // XXX: manual array copy - setVal(params[0], TupleVal.new(vals)); + setVal(params[0], BoxVal.new(null, vals)); return; } // Deal with fewer arguments than parameters by expanding the last tuple. var last = args.length - 1; setArgs0(params[0 ... last], args[0 ... last]); match (args[last]) { - x: TupleVal => { + x: BoxVal => { // expand tuple for (j = last; j < params.length; j++) { setVal(params[j], x.values[j - last]); diff --git a/aeneas/src/ssa/SsaOptimizer.v3 b/aeneas/src/ssa/SsaOptimizer.v3 index 60a32212d..66e546a44 100644 --- a/aeneas/src/ssa/SsaOptimizer.v3 +++ b/aeneas/src/ssa/SsaOptimizer.v3 @@ -829,14 +829,14 @@ class SsaInstrReducer(context: SsaContext) extends SsaInstrMatcher { var inputs = i.inputs; var vals = Array.new(inputs.length); for (j < inputs.length) vals[j] = SsaConst.!(inputs[j].dest).val; - return graph.valConst(i.op.sig.returnType(), TupleVal.new(vals)); + return graph.valConst(i.op.sig.returnType(), BoxVal.new(null, vals)); } TupleGetElem(index) => { var xval = unop(i); if (xconst) { // fold K.N var tupleType = i.op.typeArgs[0]; - var val = TupleVal.!(xval), t = Lists.get(tupleType.nested, index); + var val = BoxVal.!(xval), t = Lists.get(tupleType.nested, index); return graph.valConst(t, if(val != null, val.values[index], null)); } if (x.optag() == Opcode.TupleCreate.tag) { diff --git a/aeneas/src/types/Tuple.v3 b/aeneas/src/types/Tuple.v3 index 334f9e04b..8f32b4950 100644 --- a/aeneas/src/types/Tuple.v3 +++ b/aeneas/src/types/Tuple.v3 @@ -62,15 +62,3 @@ class Tuple_TypeCon extends TypeCon { class TupleType extends Type { new(hash: int, typeCon: TypeCon, nested: List) super(hash, typeCon, nested) { } } -// A value representing a tuple of multiple values. -class TupleVal(values: Array) extends Val { - def equals(other: Val) -> bool { - if (other == this) return true; - if (other == null) return Values.deepEqualBottom(values); - if (!TupleVal.?(other)) return false; - return Values.deepEqual(this.values, TupleVal.!(other).values); - } - def hash() -> int { - return Arrays.hash(22, values, Values.hash); - } -} diff --git a/aeneas/src/v3/V3.v3 b/aeneas/src/v3/V3.v3 index c777e029b..f61362b89 100644 --- a/aeneas/src/v3/V3.v3 +++ b/aeneas/src/v3/V3.v3 @@ -156,7 +156,7 @@ component V3 { x: Box => buf.putd(x.val); x: Box => buf.putz(x.val); x: Record => buf.put2("#%d:%q", x.id, x.rtype.render); - x: TupleVal => { + x: BoxVal => { var vals = x.values; var list: List = null; if (vtype != null) list = vtype.nested; diff --git a/aeneas/test/SsaInstrReducerTest.v3 b/aeneas/test/SsaInstrReducerTest.v3 index fd3d0717c..904bdaf94 100644 --- a/aeneas/test/SsaInstrReducerTest.v3 +++ b/aeneas/test/SsaInstrReducerTest.v3 @@ -79,7 +79,7 @@ class SsaInstrReducerTester { def assertTK(vals: Array, i: SsaInstr) { var j = opt1(null, i); TEST.eq(true, SsaConst.?(j)); - TEST.eq(true, Values.equal(TupleVal.new(vals), SsaConst.!(j).val)); + TEST.eq(true, Values.equal(BoxVal.new(null, vals), SsaConst.!(j).val)); } def assertBK(val: bool, i: SsaInstr) { TEST.eq(val, SsaConst.!(opt1(null, i)).unbox()); @@ -212,7 +212,7 @@ class SsaInstrReducerTester { return graph.valConst(rtype, r); } def tupleConst(rtype: Type, vals: Array) -> SsaConst { - var r = TupleVal.new(vals); + var r = BoxVal.new(null, vals); return graph.valConst(rtype, r); } } diff --git a/aeneas/test/Utils.v3 b/aeneas/test/Utils.v3 index e7284d503..682e90720 100644 --- a/aeneas/test/Utils.v3 +++ b/aeneas/test/Utils.v3 @@ -203,7 +203,7 @@ class SsaInstrMatchHelper(prog: Program, TEST: UnitTest, opt1: SsaInstr -> SsaIn def assertTK(vals: Array, i: SsaInstr) { var j = opt1(i); TEST.eq(true, SsaConst.?(j)); - TEST.eq(true, Values.equal(TupleVal.new(vals), SsaConst.!(j).val)); + TEST.eq(true, Values.equal(BoxVal.new(null, vals), SsaConst.!(j).val)); } def assertBK(val: bool, i: SsaInstr) { TEST.eq(val, SsaConst.!(opt1(i)).unbox()); diff --git a/aeneas/test/ValueTest.v3 b/aeneas/test/ValueTest.v3 new file mode 100644 index 000000000..1f8d5a1ca --- /dev/null +++ b/aeneas/test/ValueTest.v3 @@ -0,0 +1,133 @@ +def TEST = UnitTest.new("Value", runAllTests); + +def runAllTests() { + testIntBox(); + testLongBox(); + testBoolBox(); + testBoxVal(); +} + +def BOTTOM = Values.BOTTOM; +def INT_ONE = Int.box(1); +def INT_TWO = Int.box(2); +def INT_MIN = Int.box(int.min); +def LONG_ONE = Long.box(1); +def LONG_MAX = Long.box(long.max); +def BOOL_TRUE = Bool.box(true); + +def assertEq(a: Val, b: Val) { + if (!Values.equal(a, b) || !Values.equal(b, a)) { + var buf = StringBuilder.new(); + buf.puts("expected "); + V3.renderResult(a, null, buf); + buf.puts(" == "); + V3.renderResult(b, null, buf); + System.error(TEST.name, buf.toString()); + } + var ha = Values.hash(a), hb = Values.hash(b); + if (ha != hb) { + var buf = StringBuilder.new(); + buf.puts("expected hash "); + V3.renderResult(a, null, buf); + buf.puts(" == "); + V3.renderResult(b, null, buf); + buf.put2(" (%d != %d)", ha, hb); + System.error(TEST.name, buf.toString()); + } +} + +def assertEqSet(a: Range) { + for (i in a) for (j in a) assertEq(i, j); +} + +def assertNeSet(a: Range) { + for (i < a.length) for (j < a.length) { + if (i == j) assertEq(a[i], a[j]); + else assertNe(a[i], a[j]); + } +} + +def assertNe(a: Val, b: Val) { + if (Values.equal(a, b) || Values.equal(b, a)) { + var buf = StringBuilder.new(); + buf.puts("expected "); + V3.renderResult(a, null, buf); + buf.puts(" != "); + V3.renderResult(b, null, buf); + System.error(TEST.name, buf.toString()); + } +} + +def testIntBox() { + def box = Int.box; + + assertEqSet([BOTTOM, Int.ZERO, box(0), box(0)]); + assertEqSet([Int.ONE, box(1), box(1)]); + assertEqSet([Int.TWO, box(2), box(2)]); + assertEqSet([Int.FOUR, box(4), box(4)]); + assertEqSet([Int.MINUS_1, box(-1), box(-1)]); + + assertNeSet([Int.ZERO, Int.ONE, Int.TWO, Int.FOUR, Int.MINUS_1]); + + for (i in [99, -87, 89734, 1239, int.min, int.max]) { + assertEqSet([box(i), box(i)]); + } +} + +def testLongBox() { + def box: long -> Val = Long.box; + + assertEqSet([BOTTOM, box(0), box(0)]); + + var longs: Array = [97, -88, 89733, 12398, long.min, long.max]; + for (i in longs) assertEqSet([box(i), box(i)]); + + var vals = Arrays.map(longs, box); + assertNeSet(vals); +} + +def testBoolBox() { + var t = Bool.TRUE, f = Bool.FALSE; + def box = Bool.box; + assertEq(t, t); + assertEq(f, f); + + assertEq(box(true), t); + assertEq(box(false), f); + + assertNe(t, f); + + assertNe(t, box(false)); + assertNe(f, box(true)); + + assertEqSet([BOTTOM, Bool.FALSE, box(false), box(false)]); +} + +def box1(v: Val) -> Val { + return BoxVal.new(null, [v]); +} + +def box2(a: Val) -> Val { + return BoxVal.new(null, [a, a]); +} + +def testBoxVal() { + var box = BoxVal.new(null, _); + + assertEqSet([box([]), box([])]); + + var vals: Array = [BOTTOM, INT_ONE, INT_MIN, BOOL_TRUE, LONG_MAX]; + for (v in vals) { + assertEqSet([box1(v), box1(v)]); + assertEqSet([box2(v), box2(v)]); + } + + assertNeSet(Arrays.map(vals, box1)); + assertNeSet(Arrays.map(vals, box2)); + + var zeroes = [BOTTOM, Int.ZERO, Int.box(0), Long.box(0), Bool.FALSE, Bool.box(false)]; + for (v in zeroes) { + assertEqSet([null, box1(v), box2(v)]); + } + // TODO: more complex tests with records, closures, etc +}