diff --git a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java index f1e2033..e46f9c0 100644 --- a/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java +++ b/src/main/java/me/topchetoeu/jscript/common/FunctionBody.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.common; public class FunctionBody { public final FunctionBody[] children; public final Instruction[] instructions; - public final int localsN, capturesN, length; + public final int localsN, capturablesN, capturesN, length; - public FunctionBody(int localsN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) { + public FunctionBody(int localsN, int capturablesN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) { this.children = children; this.length = length; this.localsN = localsN; + this.capturablesN = capturablesN; this.capturesN = capturesN; this.instructions = instructions; } diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index d846072..c418e10 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -35,20 +35,21 @@ public class Instruction { LOAD_FUNC(0x30), LOAD_ARR(0x31), LOAD_OBJ(0x32), - LOAD_GLOB(0x33), - LOAD_INTRINSICS(0x34), - LOAD_REGEX(0x35), + LOAD_REGEX(0x33), + + LOAD_GLOB(0x38), + LOAD_INTRINSICS(0x39), + LOAD_ARGS(0x3A), + LOAD_REST_ARGS(0x3B), + LOAD_CALLEE(0x3C), + LOAD_THIS(0x3D), + LOAD_ERROR(0x3E), LOAD_VAR(0x40), LOAD_MEMBER(0x41), LOAD_MEMBER_INT(0x42), LOAD_MEMBER_STR(0x43), - LOAD_ARGS(0x44), - LOAD_REST_ARGS(0x45), - LOAD_CALLEE(0x46), - LOAD_THIS(0x47), - STORE_VAR(0x48), STORE_MEMBER(0x49), STORE_MEMBER_INT(0x4A), @@ -368,6 +369,9 @@ public class Instruction { public static Instruction loadIntrinsics(String key) { return new Instruction(Type.LOAD_INTRINSICS, key); } + public static Instruction loadError() { + return new Instruction(Type.LOAD_ERROR); + } public static Instruction loadMember() { return new Instruction(Type.LOAD_MEMBER); } @@ -457,12 +461,15 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - public static Instruction stackAlloc(int n) { - return new Instruction(Type.STACK_ALLOC, n); + public static Instruction stackAlloc(int start, int n) { + return new Instruction(Type.STACK_ALLOC, start, start + n); } - public static Instruction stackRealloc(int n) { - return new Instruction(Type.STACK_REALLOC, n); + public static Instruction stackRealloc(int start, int n) { + return new Instruction(Type.STACK_REALLOC, start, start + n); } + /** + * @deprecated + */ public static Instruction stackFree(int n) { return new Instruction(Type.STACK_FREE, n); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index c4e6382..60e86e8 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -111,7 +111,7 @@ public final class CompileResult { } return new FunctionBody( - scope.localsCount() + scope.allocCount(), scope.capturesCount(), + scope.localsCount(), scope.capturablesCount(), scope.capturesCount(), length, instrRes, builtChildren ); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java index 29888f6..958b825 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -25,7 +25,7 @@ public class CompoundNode extends Node { var subtarget = hasScope ? target.subtarget() : target; if (hasScope) { - subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); subtarget.scope.singleEntry = singleEntry; } @@ -45,10 +45,7 @@ public class CompoundNode extends Node { else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); } - if (hasScope) { - subtarget.scope.end(); - subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); - } + if (hasScope) subtarget.scope.end(); if (!polluted && pollute) { target.add(Instruction.pushUndefined()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java index e2181e2..933ec14 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -26,7 +26,7 @@ public class ForNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { var subtarget = target.subtarget(); subtarget.scope.singleEntry = false; - subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); declaration.compile(subtarget, false, BreakpointType.STEP_OVER); @@ -40,7 +40,7 @@ public class ForNode extends Node { CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); LabelContext.popLoop(subtarget.env, label); - subtarget.add(_i -> Instruction.stackRealloc(subtarget.scope.allocCount())); + subtarget.add(_i -> Instruction.stackRealloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER); int endI = subtarget.size(); @@ -52,7 +52,6 @@ public class ForNode extends Node { if (pollute) subtarget.add(Instruction.pushUndefined()); subtarget.scope.end(); - subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); } public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java index e3b1496..773e34b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java @@ -44,7 +44,7 @@ public class SwitchNode extends Node { value.compile(target, true, BreakpointType.STEP_OVER); var subtarget = target.subtarget(); - subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.allocCount())); + subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); // TODO: create a jump map for (var ccase : cases) { @@ -65,7 +65,6 @@ public class SwitchNode extends Node { LabelContext.getBreak(target.env).pop(label); subtarget.scope.end(); - subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount())); int endI = subtarget.size(); end.set(endI); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java index b972a1a..fffb14e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java @@ -23,8 +23,8 @@ public class TryNode extends Node { @Override public void resolve(CompileResult target) { tryBody.resolve(target); - catchBody.resolve(target); - finallyBody.resolve(target); + if (catchBody != null) catchBody.resolve(target); + if (finallyBody != null) finallyBody.resolve(target); } @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { @@ -43,8 +43,14 @@ public class TryNode extends Node { if (captureName != null) { var subtarget = target.subtarget(); - subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); + subtarget.scope.singleEntry = true; + + var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); + subtarget.add(Instruction.loadError()); + subtarget.add(_i -> catchVar.index().toSet(false)); catchBody.compile(subtarget, false); + subtarget.scope.end(); } else catchBody.compile(target, false); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 1131766..4059df0 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -9,8 +9,8 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Scope { protected final HashMap strictVarMap = new HashMap<>(); - protected final VariableList variables = new VariableList(VariableIndex.IndexType.LOCALS, this::parentVarOffset); - protected final VariableList captured = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::parentCapOffset); + protected final VariableList variables = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset); + protected final VariableList captured = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset); private boolean ended = false; private boolean finished = false; @@ -41,14 +41,14 @@ public class Scope { return var; } - private final int parentVarOffset() { - if (parent != null) return parent.variableOffset(); - else return 0; - } - private final int parentCapOffset() { - if (parent != null) return parent.capturedOffset(); - else return localsCount(); - } + // private final int parentVarOffset() { + // if (parent != null) return parent.variableOffset(); + // else return 0; + // } + // private final int parentCapOffset() { + // if (parent != null) return parent.capturedOffset(); + // else return localsCount(); + // } protected final SyntaxException alreadyDefinedErr(Location loc, String name) { return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); @@ -118,12 +118,28 @@ public class Scope { * Gets the index offset from this scope to its children */ public final int variableOffset() { - if (parent != null) return parent.variableOffset() + variables.size(); - else return variables.size(); + var res = 0; + + for (var curr = parent; curr != null; curr = curr.parent) { + res += parent.variables.size(); + } + + return res; + + // if (parent != null) return parent.variableOffset() + variables.size(); + // else return variables.size(); } - public final int capturedOffset() { - if (parent != null) return parent.capturedOffset() + captured.size(); - else return localsCount() + captured.size(); + public final int capturablesOffset() { + var res = 0; + + for (var curr = this; curr != null; curr = curr.parent) { + if (curr != this) res += parent.captured.size(); + if (curr.parent == null) res += curr.localsCount(); + } + + return res; + // if (parent != null) return parent.capturedOffset() + captured.size(); + // else return localsCount() + captured.size(); } public int localsCount() { @@ -142,7 +158,7 @@ public class Scope { } public int capturablesCount() { var res = captured.size(); - for (var child : children) res += child.allocCount(); + for (var child : children) res += child.capturablesCount(); return res; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 170162c..90c0af9 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,8 +1,7 @@ package me.topchetoeu.jscript.runtime; -import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Stack; @@ -99,7 +98,8 @@ public final class Frame { * A list of one-element arrays of values. This is so that we can pass captures to other functions */ public final Value[][] captures; - public final List locals = new ArrayList<>(); + public final Value[] locals; + public final Value[][] capturables; public final Value argsVal; public Value self; public Value fakeArgs; @@ -110,9 +110,20 @@ public final class Frame { public final Environment env; private final DebugContext dbg; - public Value[] getVar(int i) { + public Value getVar(int i) { + if (i < 0) return captures[~i][0]; + else if (i < locals.length) return locals[i]; + else return capturables[i - locals.length][0]; + } + public Value setVar(int i, Value val) { + if (i < 0) return captures[~i][0] = val; + else if (i < locals.length) return locals[i] = val; + else return capturables[i - locals.length][0] = val; + } + public Value[] captureVar(int i) { if (i < 0) return captures[~i]; - else return locals.get(i); + if (i >= locals.length) return capturables[i - locals.length]; + else throw new RuntimeException("Illegal capture"); } public Value[] stack = new Value[32]; @@ -215,12 +226,13 @@ public final class Frame { if (newCtx != tryCtx) { switch (newCtx.state) { case CATCH: - if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value }); + // TODO: may cause problems + // if (tryCtx.state != TryState.CATCH) locals.add(new Value[] { error.value }); codePtr = tryCtx.catchStart; stackPtr = tryCtx.restoreStackPtr; break; case FINALLY: - if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); + // if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); codePtr = tryCtx.finallyStart; stackPtr = tryCtx.restoreStackPtr; default: @@ -236,7 +248,7 @@ public final class Frame { } else { popTryFlag = false; - if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); + // if (tryCtx.state == TryState.CATCH) locals.remove(locals.size() - 1); if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) { codePtr = tryCtx.finallyStart; @@ -422,8 +434,13 @@ public final class Frame { var i = 0; - for (i = 0; i < func.body.localsN; i++) { - this.locals.add(new Value[] { Value.UNDEFINED }); + this.locals = new Value[func.body.localsN]; + Arrays.fill(locals, Value.UNDEFINED); + + this.capturables = new Value[func.body.capturablesN][1]; + + for (i = 0; i < func.body.capturablesN; i++) { + this.capturables[i][0] = Value.UNDEFINED; } } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 0c464bf..a6186ac 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -140,7 +140,7 @@ public class InstructionRunner { private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { int i = instr.get(0); - frame.push(frame.getVar(i)[0]); + frame.push(frame.getVar(i)); frame.codePtr++; return null; @@ -179,7 +179,7 @@ public class InstructionRunner { var captures = new Value[instr.params.length - 5][]; for (var i = 5; i < instr.params.length; i++) { - captures[i - 5] = frame.getVar(instr.get(i)); + captures[i - 5] = frame.captureVar(instr.get(i)); } var func = new CodeFunction(env, name, frame.function.body.children[id], captures); @@ -278,7 +278,7 @@ public class InstructionRunner { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); int i = instr.get(0); - frame.getVar(i)[0] = val; + frame.setVar(i, val); frame.codePtr++; return null; @@ -498,29 +498,34 @@ public class InstructionRunner { frame.codePtr++; return null; } + private static Value execLoadError(Environment env, Instruction instr, Frame frame) { + frame.push(frame.tryStack.peek().error.value); + frame.codePtr++; + return null; + } private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) { - int n = instr.get(0); + int offset = instr.get(0); + int n = instr.get(1); - for (var i = 0; i < n; i++) frame.locals.add(new Value[] { Value.UNDEFINED }); + for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { Value.UNDEFINED }; frame.codePtr++; return null; } private static Value execStackRealloc(Environment env, Instruction instr, Frame frame) { - int n = instr.get(0); + int offset = instr.get(0); + int n = instr.get(1); - for (var i = frame.locals.size() - n; i < frame.locals.size(); i++) frame.locals.set(i, new Value[] { frame.locals.get(i)[0] }); + for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { frame.capturables[i][0] }; frame.codePtr++; return null; } private static Value execStackFree(Environment env, Instruction instr, Frame frame) { - int n = instr.get(0); + // int n = instr.get(0); - for (var i = 0; i < n; i++) { - frame.locals.remove(frame.locals.size() - 1); - } + // TODO: Remove if safe to do so frame.codePtr++; return null; @@ -559,6 +564,7 @@ public class InstructionRunner { case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame); case LOAD_CALLEE: return execLoadCallee(env, instr, frame); case LOAD_THIS: return execLoadThis(env, instr, frame); + case LOAD_ERROR: return execLoadError(env, instr, frame); case DISCARD: return execDiscard(env, instr, frame); case STORE_MEMBER: return execStoreMember(env, instr, frame);