diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java index 59f5d2746..8053f0434 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/CompiletimeFunctionRunner.java @@ -26,6 +26,7 @@ import de.peeeq.wurstscript.jassinterpreter.TestFailException; import de.peeeq.wurstscript.jassinterpreter.TestSuccessException; import de.peeeq.wurstscript.parser.WPos; +import de.peeeq.wurstscript.translation.imtranslation.ClassManagementVars; import de.peeeq.wurstscript.translation.imtranslation.*; import de.peeeq.wurstscript.types.TypesHelper; import de.peeeq.wurstscript.utils.Pair; @@ -51,6 +52,7 @@ public class CompiletimeFunctionRunner { private final ImTranslator translator; private boolean injectObjects; private final Deque delayedActions = new ArrayDeque<>(); + private final Map> compiletimeObjects = new LinkedHashMap<>(); public ILInterpreter getInterpreter() { return interpreter; @@ -110,6 +112,7 @@ public void run() { interpreter.writebackGlobalState(isInjectObjects()); } runDelayedActions(); + emitCompiletimeObjectAllocs(); partitionCompiletimeStateInitFunction(); @@ -272,10 +275,10 @@ public ImVar initFor(ILconstObject obj) { ImVar res = JassIm.ImVar(obj.getTrace(), obj.getType(), obj.getType() + "_compiletime", false); imProg.getGlobals().add(res); - ImAlloc alloc = JassIm.ImAlloc(obj.getTrace(), obj.getType()); - addCompiletimeStateInitAlloc(alloc.getTrace(), res, alloc); globalState.setVal(res, obj); + registerCompiletimeObject(obj, res); + Element trace = obj.getTrace(); @@ -370,6 +373,67 @@ private ImExpr constantToExpr(Element trace, ILconst value) { } + private void registerCompiletimeObject(ILconstObject obj, ImVar targetVar) { + ClassManagementVars mVars = translator.getClassManagementVarsFor(obj.getType().getClassDef()); + compiletimeObjects.computeIfAbsent(mVars, k -> new ArrayList<>()) + .add(new CompiletimeObjectInit(obj, targetVar)); + } + + private void emitCompiletimeObjectAllocs() { + if (compiletimeObjects.isEmpty()) { + return; + } + + List objectInits = new ArrayList<>(); + + for (Map.Entry> entry : compiletimeObjects.entrySet()) { + List objs = entry.getValue(); + if (objs.isEmpty()) { + continue; + } + + objs.sort(Comparator.comparingInt(o -> o.object.getObjectId())); + + ClassManagementVars mVars = entry.getKey(); + int currentMax = 0; + int finalMax = globalState.getMaxAllocatedId(objs.get(0).object.getImClass()); + + for (CompiletimeObjectInit init : objs) { + int desiredId = init.object.getObjectId(); + int targetMax = desiredId - 1; + if (targetMax > currentMax) { + objectInits.add(JassIm.ImSet(init.object.getTrace(), + JassIm.ImVarAccess(mVars.maxIndex), + JassIm.ImIntVal(targetMax))); + currentMax = targetMax; + } + + ImAlloc alloc = JassIm.ImAlloc(init.object.getTrace(), init.object.getType()); + ImSet assign = JassIm.ImSet(init.object.getTrace(), JassIm.ImVarAccess(init.targetVar), alloc); + objectInits.add(assign); + imProg.getGlobalInits().put(init.targetVar, Collections.singletonList(assign)); + currentMax = desiredId; + } + + if (finalMax > currentMax) { + objectInits.add(JassIm.ImSet(objs.get(0).object.getTrace(), + JassIm.ImVarAccess(mVars.maxIndex), JassIm.ImIntVal(finalMax))); + } + } + + getCompiletimeStateInitFunction().getBody().addAll(0, objectInits); + } + + private static class CompiletimeObjectInit { + private final ILconstObject object; + private final ImVar targetVar; + + private CompiletimeObjectInit(ILconstObject object, ImVar targetVar) { + this.object = object; + this.targetVar = targetVar; + } + } + private ImFunction compiletimeStateInitFunction = null; private ImFunction getCompiletimeStateInitFunction() { diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java index 0099e46c3..4565df047 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/EvaluateExpr.java @@ -195,7 +195,11 @@ public static ILconst eval(ImVarArrayAccess e, ProgramState globalState, LocalSt public static @Nullable ILconst eval(ImMethodCall mc, ProgramState globalState, LocalState localState) { - ILconstObject receiver = globalState.toObject(mc.getReceiver().evaluate(globalState, localState)); + ImType receiverType = globalState.resolveType(mc.getReceiver().attrTyp()); + ImClassType receiverClassType = receiverType instanceof ImClassType + ? (ImClassType) receiverType + : mc.getMethod().getMethodClass(); + ILconstObject receiver = globalState.toObject(mc.getReceiver().evaluate(globalState, localState), receiverClassType); globalState.assertAllocated(receiver, mc.attrTrace()); mark(mc, globalState); @@ -231,7 +235,19 @@ public static ILconst eval(ImVarArrayAccess e, ProgramState globalState, LocalSt public static ILconst eval(ImMemberAccess ma, ProgramState globalState, LocalState localState) { - ILconstObject receiver = globalState.toObject(ma.getReceiver().evaluate(globalState, localState)); + ImType receiverType = globalState.resolveType(ma.getReceiver().attrTyp()); + ImClassType receiverClassType = receiverType instanceof ImClassType ? (ImClassType) receiverType : null; + if (receiverClassType == null) { + de.peeeq.wurstscript.jassIm.Element parent = ma.getVar().getParent(); + while (parent != null && !(parent instanceof ImClass)) { + parent = parent.getParent(); + } + if (parent instanceof ImClass) { + receiverClassType = JassIm.ImClassType((ImClass) parent, JassIm.ImTypeArguments()); + } + } + + ILconstObject receiver = globalState.toObject(ma.getReceiver().evaluate(globalState, localState), receiverClassType); if (receiver == null) { throw new InterpreterException(ma.getTrace(), "Null pointer dereference: " + ImPrinter.asString(ma.getReceiver())); } @@ -254,14 +270,14 @@ public static ILconst eval(ImAlloc e, ProgramState globalState, LocalState local public static ILconst eval(ImDealloc imDealloc, ProgramState globalState, LocalState localState) { - ILconstObject obj = globalState.toObject(imDealloc.getObj().evaluate(globalState, localState)); + ILconstObject obj = globalState.toObject(imDealloc.getObj().evaluate(globalState, localState), imDealloc.getClazz()); globalState.deallocate(obj, imDealloc.getClazz().getClassDef(), imDealloc.attrTrace()); return ILconstNull.instance(); } public static ILconst eval(ImInstanceof e, ProgramState globalState, LocalState localState) { - ILconstObject obj = globalState.toObject(e.getObj().evaluate(globalState, localState)); + ILconstObject obj = globalState.toObject(e.getObj().evaluate(globalState, localState), e.getClazz()); return ILconstBool.instance(globalState.isInstanceOf(obj, e.getClazz().getClassDef(), e.attrTrace())); } @@ -272,7 +288,7 @@ public static ILconst eval(ImTypeIdOfClass e, public static ILconst eval(ImTypeIdOfObj e, ProgramState globalState, LocalState localState) { - ILconstObject obj = globalState.toObject(e.getObj().evaluate(globalState, localState)); + ILconstObject obj = globalState.toObject(e.getObj().evaluate(globalState, localState), e.getClazz()); return new ILconstInt(globalState.getTypeId(obj, e.attrTrace())); } @@ -376,13 +392,14 @@ public ILconst get() { public static ILaddress evaluateLvalue(ImMemberAccess va, ProgramState globalState, LocalState localState) { ImVar v = va.getVar(); + ImType receiverType = globalState.resolveType(va.getReceiver().attrTyp()); + ImClassType receiverClassType = receiverType instanceof ImClassType ? (ImClassType) receiverType : null; ILconst receiverVal = va.getReceiver().evaluate(globalState, localState); - ILconstObject receiver = globalState.toObject(receiverVal); - if (receiver == null && receiverVal instanceof ILconstInt && va.getReceiver().attrTyp() instanceof ImClassType) { + ILconstObject receiver = globalState.toObject(receiverVal, receiverClassType); + if (receiver == null && receiverVal instanceof ILconstInt && receiverClassType != null) { int objectId = ((ILconstInt) receiverVal).getVal(); if (objectId != 0) { - receiver = globalState.ensureObject((ImClassType) va.getReceiver().attrTyp(), - objectId, va.attrTrace()); + receiver = globalState.ensureObject(receiverClassType, objectId, va.attrTrace()); } } if (receiver == null) { @@ -443,7 +460,9 @@ public static ILconst eval(ImTypeVarDispatch e, ProgramState globalState, LocalS public static ILconst eval(ImCast imCast, ProgramState globalState, LocalState localState) { ILconst res = imCast.getExpr().evaluate(globalState, localState); - if (TypesHelper.isIntType(imCast.getToType())) { + ImType targetType = globalState.resolveType(imCast.getToType()); + + if (TypesHelper.isIntType(targetType)) { if (res instanceof ILconstObject) { return ILconstInt.create(((ILconstObject) res).getObjectId()); } @@ -454,10 +473,10 @@ public static ILconst eval(ImCast imCast, ProgramState globalState, LocalState l } } if (res instanceof ILconstInt) { - if (imCast.getToType() instanceof ImClassType) { - return globalState.getObjectByIndex(((ILconstInt) res).getVal()); + if (targetType instanceof ImClassType) { + return globalState.getObjectByIndex(((ILconstInt) res).getVal(), (ImClassType) targetType); } - if (imCast.getToType() instanceof IlConstHandle) { + if (targetType instanceof IlConstHandle) { return globalState.getHandleByIndex(((ILconstInt) res).getVal()); } } diff --git a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java index 8a0d45672..0cf625b43 100644 --- a/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java +++ b/de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/interpreter/ProgramState.java @@ -5,6 +5,7 @@ import de.peeeq.wurstscript.WLogger; import de.peeeq.wurstscript.ast.Element; import de.peeeq.wurstscript.attributes.CompileError; +import de.peeeq.datastructures.Partitions; import de.peeeq.wurstscript.gui.WurstGui; import de.peeeq.wurstscript.intermediatelang.*; import de.peeeq.wurstscript.jassIm.*; @@ -27,8 +28,8 @@ public class ProgramState extends State { private PrintStream outStream = System.err; private final List nativeProviders = Lists.newArrayList(); private ImProg prog; - private int objectIdCounter; - private final Int2ObjectOpenHashMap indexToObject = new Int2ObjectOpenHashMap<>(); + private final Map classKeyLookup = new HashMap<>(); + private final Map objectIdSpaces = new HashMap<>(); private final Int2ObjectOpenHashMap handleMap = new Int2ObjectOpenHashMap<>(); private final Deque stackFrames = new ArrayDeque<>(); private final Deque lastStatements = new ArrayDeque<>(); @@ -87,9 +88,24 @@ public ProgramState(WurstGui gui, ImProg prog, boolean isCompiletime) { this.prog = prog; this.isCompiletime = isCompiletime; + buildClassKeyLookup(); identifyGenericStaticGlobals(); } + private void buildClassKeyLookup() { + Partitions partitions = new Partitions<>(); + for (ImClass clazz : prog.getClasses()) { + partitions.add(clazz); + for (ImClassType superClass : clazz.getSuperClasses()) { + partitions.union(clazz, superClass.getClassDef()); + } + } + + for (ImClass clazz : prog.getClasses()) { + classKeyLookup.put(clazz, partitions.getRep(clazz)); + } + } + private void identifyGenericStaticGlobals() { Map classMap = new HashMap<>(); for (ImClass c : prog.getClasses()) { @@ -167,6 +183,9 @@ public Iterable getNativeProviders() { public ProgramState setProg(ImProg p) { prog = p; + classKeyLookup.clear(); + objectIdSpaces.clear(); + buildClassKeyLookup(); return this; } @@ -175,21 +194,24 @@ public ImProg getProg() { } public ILconstObject allocate(ImClassType clazz, Element trace) { - objectIdCounter++; - ILconstObject res = new ILconstObject(clazz, objectIdCounter, trace); - indexToObject.put(objectIdCounter, res); - WLogger.trace("alloc objId=" + objectIdCounter + " type=" + clazz + " trace=" + trace); + Object key = classKey(clazz.getClassDef()); + ObjectIdSpace idSpace = getIdSpace(key); + int objId = !idSpace.freeObjectIds.isEmpty() ? idSpace.freeObjectIds.pop() : ++idSpace.objectIdCounter; + ILconstObject res = new ILconstObject(clazz, objId, trace); + idSpace.indexToObject.put(objId, res); + WLogger.trace("alloc objId=" + objId + " type=" + clazz + " trace=" + trace); return res; } protected Object classKey(ImClass clazz) { - return clazz; + return classKeyLookup.getOrDefault(clazz, clazz); } public void deallocate(ILconstObject obj, ImClass clazz, Element trace) { assertAllocated(obj, trace); obj.destroy(); - // TODO recycle ids + ObjectIdSpace idSpace = getIdSpace(classKey(clazz)); + idSpace.freeObjectIds.push(obj.getObjectId()); } public void assertAllocated(ILconstObject obj, Element trace) { @@ -214,16 +236,6 @@ public int getTypeId(ILconstObject obj, Element trace) { return obj.getImClass().attrTypeId(); } - private ImClass findClazz(Object key) { - for (ImClass c : prog.getClasses()) { - if (classKey(c).equals(key)) { - return c; - } - } - throw new Error("no class found for key " + key); - } - - public void pushStackframeWithTypes(ImFunction f, @Nullable ILconstObject receiver, ILconst[] args, WPos trace, Map typeSubstitutions) { @@ -504,8 +516,30 @@ public void compilationError(String errorMessage) { } } - public ILconst getObjectByIndex(int val) { - return indexToObject.get(val); + public ILconstObject getObjectByIndex(int val) { + return getObjectByIndex(val, null); + } + + public ILconstObject getObjectByIndex(int val, @Nullable ImClassType expectedType) { + if (expectedType != null) { + ObjectIdSpace idSpace = objectIdSpaces.get(classKey(expectedType.getClassDef())); + if (idSpace == null) { + return null; + } + return idSpace.indexToObject.get(val); + } + + ILconstObject found = null; + for (ObjectIdSpace idSpace : objectIdSpaces.values()) { + ILconstObject candidate = idSpace.indexToObject.get(val); + if (candidate != null) { + if (found != null && candidate != found) { + throw new InterpreterException(this, "Ambiguous object id " + val + " in multiple id spaces."); + } + found = candidate; + } + } + return found; } public Map getHandleMap() { @@ -517,20 +551,31 @@ public ILconst getHandleByIndex(int val) { } public ILconstObject ensureObject(ImClassType clazz, int objectId, Element trace) { - ILconstObject existing = indexToObject.get(objectId); + ObjectIdSpace idSpace = getIdSpace(classKey(clazz.getClassDef())); + ILconstObject existing = idSpace.indexToObject.get(objectId); if (existing != null) { return existing; } ILconstObject res = new ILconstObject(clazz, objectId, trace); - indexToObject.put(objectId, res); + idSpace.indexToObject.put(objectId, res); return res; } public ILconstObject toObject(ILconst val) { + return toObject(val, null); + } + + public ILconstObject toObject(ILconst val, @Nullable ImType expectedType) { + ImType resolved = expectedType == null ? null : resolveType(expectedType); + ImClassType expectedClass = resolved instanceof ImClassType ? (ImClassType) resolved : null; + if (val instanceof ILconstObject) { return (ILconstObject) val; } else if (val instanceof ILconstInt) { - return indexToObject.get(((ILconstInt) val).getVal()); + int objectId = ((ILconstInt) val).getVal(); + return expectedClass != null + ? getObjectByIndex(objectId, expectedClass) + : getObjectByIndex(objectId); } throw new InterpreterException(this, "Value " + val + " (" + val.getClass().getSimpleName() + ") cannot be cast to object."); } @@ -685,7 +730,25 @@ protected ILconstArray getArray(ImVar v) { public Collection getAllObjects() { - return indexToObject.values(); + List values = new ArrayList<>(); + for (ObjectIdSpace idSpace : objectIdSpaces.values()) { + values.addAll(idSpace.indexToObject.values()); + } + return values; + } + + private ObjectIdSpace getIdSpace(Object key) { + return objectIdSpaces.computeIfAbsent(key, k -> new ObjectIdSpace()); + } + + public int getMaxAllocatedId(ImClass clazz) { + return getIdSpace(classKey(clazz)).objectIdCounter; + } + + private static class ObjectIdSpace { + private int objectIdCounter; + private final Deque freeObjectIds = new ArrayDeque<>(); + private final Int2ObjectOpenHashMap indexToObject = new Int2ObjectOpenHashMap<>(); } public Map snapshotResolvedTypeSubstitutions() { diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/interpreter/ProgramStateRecycleTest.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/interpreter/ProgramStateRecycleTest.java new file mode 100644 index 000000000..672a3864d --- /dev/null +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/interpreter/ProgramStateRecycleTest.java @@ -0,0 +1,104 @@ +package tests.wurstscript.interpreter; + +import de.peeeq.wurstscript.ast.Ast; +import de.peeeq.wurstscript.ast.Element; +import de.peeeq.wurstscript.gui.WurstGuiLogger; +import de.peeeq.wurstscript.intermediatelang.ILconstObject; +import de.peeeq.wurstscript.intermediatelang.interpreter.ProgramState; +import de.peeeq.wurstscript.jassIm.ImClass; +import de.peeeq.wurstscript.jassIm.ImClassType; +import de.peeeq.wurstscript.jassIm.ImProg; +import de.peeeq.wurstscript.jassIm.JassIm; +import org.testng.annotations.Test; + +import java.util.Collections; +import java.util.HashMap; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + +public class ProgramStateRecycleTest { + + @Test + public void reusesObjectIdsAfterDeallocateWithinIdSpace() { + Element trace = Ast.NoExpr(); + ImClass clazz = JassIm.ImClass(trace, "A", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.emptyList()); + ImProg prog = JassIm.ImProg(trace, JassIm.ImVars(), JassIm.ImFunctions(), JassIm.ImMethods(), JassIm.ImClasses(clazz), JassIm.ImTypeClassFuncs(), new HashMap<>()); + ProgramState state = new ProgramState(new WurstGuiLogger(), prog, true); + + ImClassType classType = JassIm.ImClassType(clazz, JassIm.ImTypeArguments()); + ILconstObject first = state.allocate(classType, trace); + state.deallocate(first, clazz, trace); + ILconstObject second = state.allocate(classType, trace); + + assertEquals(first.getObjectId(), second.getObjectId()); + assertFalse(second.isDestroyed()); + } + + @Test + public void maintainsSeparateAndSharedIdSpaces() { + Element trace = Ast.NoExpr(); + ImClass classA = JassIm.ImClass(trace, "A", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.emptyList()); + ImClass classB = JassIm.ImClass(trace, "B", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.emptyList()); + ImClassType classASuperType = JassIm.ImClassType(classA, JassIm.ImTypeArguments()); + ImClass classC = JassIm.ImClass(trace, "C", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.singletonList(classASuperType)); + + ImProg prog = JassIm.ImProg(trace, JassIm.ImVars(), JassIm.ImFunctions(), JassIm.ImMethods(), JassIm.ImClasses(classA, classB, classC), JassIm.ImTypeClassFuncs(), new HashMap<>()); + ProgramState state = new ProgramState(new WurstGuiLogger(), prog, true); + + ImClassType typeA = JassIm.ImClassType(classA, JassIm.ImTypeArguments()); + ImClassType typeB = JassIm.ImClassType(classB, JassIm.ImTypeArguments()); + ImClassType typeC = JassIm.ImClassType(classC, JassIm.ImTypeArguments()); + + ILconstObject a1 = state.allocate(typeA, trace); + ILconstObject b1 = state.allocate(typeB, trace); + + assertEquals(a1.getObjectId(), 1); + assertEquals(b1.getObjectId(), 1, "Independent classes should start their own id counters."); + + state.deallocate(a1, classA, trace); + state.deallocate(b1, classB, trace); + + ILconstObject c1 = state.allocate(typeC, trace); + assertEquals(c1.getObjectId(), a1.getObjectId(), "Subclass should share the id space with its superclass."); + + ILconstObject b2 = state.allocate(typeB, trace); + assertEquals(b2.getObjectId(), b1.getObjectId(), "Independent id space should recycle its own ids."); + } + + @Test + public void stressRecyclesAcrossMultipleIdSpaces() { + Element trace = Ast.NoExpr(); + ImClass classA = JassIm.ImClass(trace, "A", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.emptyList()); + ImClass classB = JassIm.ImClass(trace, "B", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.emptyList()); + ImClassType classASuperType = JassIm.ImClassType(classA, JassIm.ImTypeArguments()); + ImClass classC = JassIm.ImClass(trace, "C", JassIm.ImTypeVars(), JassIm.ImVars(), JassIm.ImMethods(), JassIm.ImFunctions(), Collections.singletonList(classASuperType)); + + ImProg prog = JassIm.ImProg(trace, JassIm.ImVars(), JassIm.ImFunctions(), JassIm.ImMethods(), JassIm.ImClasses(classA, classB, classC), JassIm.ImTypeClassFuncs(), new HashMap<>()); + ProgramState state = new ProgramState(new WurstGuiLogger(), prog, true); + + ImClassType typeA = JassIm.ImClassType(classA, JassIm.ImTypeArguments()); + ImClassType typeB = JassIm.ImClassType(classB, JassIm.ImTypeArguments()); + ImClassType typeC = JassIm.ImClassType(classC, JassIm.ImTypeArguments()); + + for (int i = 0; i < 250; i++) { + ILconstObject a = state.allocate(typeA, trace); + ILconstObject b = state.allocate(typeB, trace); + if (i % 5 == 0) { + ILconstObject c = state.allocate(typeC, trace); + state.deallocate(c, classC, trace); + } + state.deallocate(a, classA, trace); + state.deallocate(b, classB, trace); + } + + ILconstObject reusedA = state.allocate(typeA, trace); + state.deallocate(reusedA, classA, trace); + ILconstObject reusedC = state.allocate(typeC, trace); + ILconstObject reusedB = state.allocate(typeB, trace); + + assertEquals(reusedA.getObjectId(), 1, "Repeated recycling should keep the smallest free id available."); + assertEquals(reusedC.getObjectId(), reusedA.getObjectId(), "Classes sharing an id space must recycle together."); + assertEquals(reusedB.getObjectId(), 1, "Independent id space should recycle separately."); + } +} diff --git a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeTests.java b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeTests.java index 62879ecda..a87d0f702 100644 --- a/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeTests.java +++ b/de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeTests.java @@ -226,6 +226,81 @@ public void testPersistCompiletimeClassTuple() { " testSuccess()"); } + @Test + public void testCompiletimeObjectIdMigration() { + test().executeProg(true) + .runCompiletimeFunctions(true) + .executeProgOnlyAfterTransforms() + .lines("package Test", + "native testSuccess()", + "class A", + " int storedId", + "tuple BuildResult(A obj, int maxId)", + "function compiletime(T t) returns T", + " return t", + "@compiletime function build() returns BuildResult", + " let keep = new A", + " keep.storedId = keep castTo int", + " let temp1 = new A", + " let temp2 = new A", + " let maxId = temp2 castTo int", + " destroy temp1", + " destroy temp2", + " return BuildResult(keep, maxId)", + "let result = compiletime(build())", + "init", + " let newA = new A", + " if result.obj castTo int == result.obj.storedId", + " and newA castTo int == result.maxId + 1", + " testSuccess()"); + } + + @Test + public void testCompiletimeObjectIdMigrationStress() { + test().executeProg(true) + .runCompiletimeFunctions(true) + .executeProgOnlyAfterTransforms() + .lines("package Test", + "native testSuccess()", + "class A", + " int idSnapshot", + "class B", + " int idSnapshot", + "tuple BuildResult(A keepA, B keepB, int maxA, int maxB)", + "function compiletime(T t) returns T", + " return t", + "@compiletime function build() returns BuildResult", + " A keepA = null", + " B keepB = null", + " int maxA = 0", + " int maxB = 0", + " for i = 1 to 200", + " let a = new A", + " a.idSnapshot = a castTo int", + " maxA = a castTo int", + " if i % 25 == 0", + " keepA = a", + " else", + " destroy a", + " let b = new B", + " b.idSnapshot = b castTo int", + " maxB = b castTo int", + " if i % 30 == 0", + " keepB = b", + " else", + " destroy b", + " return BuildResult(keepA, keepB, maxA, maxB)", + "let result = compiletime(build())", + "init", + " let newA = new A", + " let newB = new B", + " if result.keepA.idSnapshot == result.keepA castTo int", + " and result.keepB.idSnapshot == result.keepB castTo int", + " and newA castTo int == result.maxA + 1", + " and newB castTo int == result.maxB + 1", + " testSuccess()"); + } + @Test public void checkCompiletimeAnnotation1() { testAssertErrorsLines(false, "Functions annotated '@compiletime' may not take parameters.",