From 0ea01996ee41a363af99965ef0777abe0d6cc6cd Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Fri, 25 Aug 2023 14:26:08 +0300 Subject: [PATCH] feat: make try-catch call-less --- src/me/topchetoeu/jscript/Main.java | 2 +- .../jscript/compilation/Instruction.java | 4 +- .../compilation/control/TryStatement.java | 70 ++++----- .../jscript/engine/frame/CodeFrame.java | 137 +++++++++++++++++- .../jscript/engine/frame/Runners.java | 78 ++-------- .../jscript/engine/scope/GlobalScope.java | 6 + .../jscript/engine/scope/LocalScope.java | 4 + .../engine/scope/LocalScopeRecord.java | 6 + .../jscript/engine/scope/ScopeRecord.java | 1 + 9 files changed, 197 insertions(+), 111 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 34a2426..aa92604 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -54,7 +54,7 @@ public class Main { public static void main(String args[]) { var in = new BufferedReader(new InputStreamReader(System.in)); - engine = new TypescriptEngine(new File(".")); + engine = new PolyfillEngine(new File(".")); var scope = engine.global().globalChild(); var exited = new boolean[1]; diff --git a/src/me/topchetoeu/jscript/compilation/Instruction.java b/src/me/topchetoeu/jscript/compilation/Instruction.java index 54d5234..6e3be1c 100644 --- a/src/me/topchetoeu/jscript/compilation/Instruction.java +++ b/src/me/topchetoeu/jscript/compilation/Instruction.java @@ -135,8 +135,8 @@ public class Instruction { this.params = params; } - public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) { - return new Instruction(null, Type.TRY, hasCatch, hasFinally); + public static Instruction tryInstr(int n, int catchN, int finallyN) { + return new Instruction(null, Type.TRY, n, catchN, finallyN); } public static Instruction throwInstr() { return new Instruction(null, Type.THROW); diff --git a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java index 2af777d..ca25589 100644 --- a/src/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -5,7 +5,8 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Statement; -import me.topchetoeu.jscript.compilation.Instruction.Type; +import me.topchetoeu.jscript.engine.scope.GlobalScope; +import me.topchetoeu.jscript.engine.scope.LocalScopeRecord; import me.topchetoeu.jscript.engine.scope.ScopeRecord; public class TryStatement extends Statement { @@ -24,57 +25,42 @@ public class TryStatement extends Statement { if (finallyBody != null) finallyBody.declare(globScope); } - private void compileBody(List target, ScopeRecord scope, Statement body, String arg) { - var subscope = scope.child(); - int start = target.size(); - - target.add(Instruction.nop()); - - subscope.define("this"); - var argsVar = subscope.define(""); - - if (arg != null) { - target.add(Instruction.loadVar(argsVar)); - target.add(Instruction.loadMember(0)); - target.add(Instruction.storeVar(subscope.define(arg))); - } - - int bodyStart = target.size(); - body.compile(target, subscope); - target.add(Instruction.signal("no_return")); - - target.get(bodyStart).locate(body.loc()); - - - target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), 0, subscope.getCaptures())); - } - @Override public void compile(List target, ScopeRecord scope) { - int start = target.size(); + int start = target.size(), tryN, catchN = -1, finN = -1; - compileBody(target, scope, tryBody, null); + target.add(Instruction.nop()); + tryBody.compileNoPollution(target, scope); + tryN = target.size() - start; if (catchBody != null) { - compileBody(target, scope, catchBody, name); + int tmp = target.size(); + var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope; + local.define(name); + catchBody.compileNoPollution(target, scope); + local.undefine(); + catchN = target.size() - tmp; } + if (finallyBody != null) { - compileBody(target, scope, finallyBody, null); + int tmp = target.size(); + finallyBody.compileNoPollution(target, scope); + finN = target.size() - tmp; } - for (int i = start; i < target.size(); i++) { - if (target.get(i).type == Type.NOP) { - var instr = target.get(i); - if (instr.is(0, "break")) { - target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location)); - } - else if (instr.is(0, "cont")) { - target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location)); - } - } - } + // for (int i = start; i < target.size(); i++) { + // if (target.get(i).type == Type.NOP) { + // var instr = target.get(i); + // if (instr.is(0, "break")) { + // target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location)); + // } + // else if (instr.is(0, "cont")) { + // target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location)); + // } + // } + // } - target.add(Instruction.tryInstr(catchBody != null, finallyBody != null).locate(loc())); + target.set(start, Instruction.tryInstr(tryN, catchN, finN).locate(loc())); } public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 4dc0580..4e3a6c5 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -9,13 +9,41 @@ import me.topchetoeu.jscript.engine.DebugCommand; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.CallContext.DataKey; import me.topchetoeu.jscript.engine.scope.LocalScope; +import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; public class CodeFrame { - private record TryCtx(int tryStart, int tryEnd, int catchStart, int catchEnd, int finallyStart, int finallyEnd) { } + private class TryCtx { + public static final int STATE_TRY = 0; + public static final int STATE_CATCH = 1; + public static final int STATE_FINALLY_THREW = 2; + public static final int STATE_FINALLY_RETURNED = 3; + public static final int STATE_FINALLY_JUMPED = 4; + + public final boolean hasCatch, hasFinally; + public final int tryStart, catchStart, finallyStart, end; + public int state; + public Object retVal; + public int jumpPtr; + public EngineException err; + + public TryCtx(int tryStart, int tryN, int catchN, int finallyN) { + hasCatch = catchN >= 0; + hasFinally = finallyN >= 0; + + if (catchN < 0) catchN = 0; + if (finallyN < 0) finallyN = 0; + + this.tryStart = tryStart; + this.catchStart = tryStart + tryN; + this.finallyStart = catchStart + catchN; + this.end = finallyStart + finallyN; + this.jumpPtr = end; + } + } public static final DataKey STACK_N_KEY = new DataKey<>(); public static final DataKey MAX_STACK_KEY = new DataKey<>(); @@ -31,9 +59,16 @@ public class CodeFrame { public Object[] stack = new Object[32]; public int stackPtr = 0; public int codePtr = 0; + public boolean jumpFlag = false; private DebugCommand debugCmd = null; private Location prevLoc = null; + public void addTry(int n, int catchN, int finallyN) { + var res = new TryCtx(codePtr + 1, n, catchN, finallyN); + + tryStack.add(res); + } + public Object peek() { return peek(0); } @@ -114,6 +149,7 @@ public class CodeFrame { // } try { + this.jumpFlag = false; return Runners.exec(debugCmd, instr, this, ctx); } catch (EngineException e) { @@ -122,7 +158,104 @@ public class CodeFrame { } public Object next(CallContext ctx) throws InterruptedException { - return nextNoTry(ctx); + TryCtx tryCtx = null; + + while (!tryStack.isEmpty()) { + var tmp = tryStack.get(tryStack.size() - 1); + var remove = false; + + if (tmp.state == TryCtx.STATE_TRY) { + if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) { + if (jumpFlag) tmp.jumpPtr = codePtr; + else tmp.jumpPtr = tmp.end; + + if (tmp.hasFinally) { + tmp.state = TryCtx.STATE_FINALLY_JUMPED; + codePtr = tmp.finallyStart; + } + else codePtr = tmp.jumpPtr; + remove = !tmp.hasFinally; + } + } + else if (tmp.state == TryCtx.STATE_CATCH) { + if (codePtr < tmp.catchStart || codePtr >= tmp.finallyStart) { + if (jumpFlag) tmp.jumpPtr = codePtr; + else tmp.jumpPtr = tmp.end; + scope.catchVars.remove(scope.catchVars.size() - 1); + + if (tmp.hasFinally) { + tmp.state = TryCtx.STATE_FINALLY_JUMPED; + codePtr = tmp.finallyStart; + } + else codePtr = tmp.jumpPtr; + remove = !tmp.hasFinally; + } + } + else if (codePtr < tmp.finallyStart || codePtr >= tmp.end) { + if (!jumpFlag) { + if (tmp.state == TryCtx.STATE_FINALLY_THREW) throw tmp.err; + else if (tmp.state == TryCtx.STATE_FINALLY_RETURNED) return tmp.retVal; + } + else codePtr = tmp.jumpPtr; + remove = true; + } + + if (remove) tryStack.remove(tryStack.size() - 1); + else { + tryCtx = tmp; + break; + } + } + + if (tryCtx == null) return nextNoTry(ctx); + else if (tryCtx.state == TryCtx.STATE_TRY) { + try { + var res = nextNoTry(ctx); + if (res != Runners.NO_RETURN && tryCtx.hasFinally) { + tryCtx.retVal = res; + tryCtx.state = TryCtx.STATE_FINALLY_RETURNED; + } + + else return res; + } + catch (EngineException e) { + if (tryCtx.hasCatch) { + tryCtx.state = TryCtx.STATE_CATCH; + codePtr = tryCtx.catchStart; + scope.catchVars.add(new ValueVariable(false, e.value)); + return Runners.NO_RETURN; + } + else if (tryCtx.hasFinally) { + tryCtx.err = e; + tryCtx.state = TryCtx.STATE_FINALLY_THREW; + } + else throw e; + } + + codePtr = tryCtx.finallyStart; + return Runners.NO_RETURN; + } + else if (tryCtx.state == TryCtx.STATE_CATCH) { + try { + var res = nextNoTry(ctx); + if (res != Runners.NO_RETURN && tryCtx.hasFinally) { + tryCtx.retVal = res; + tryCtx.state = TryCtx.STATE_FINALLY_RETURNED; + } + else return res; + } + catch (EngineException e) { + if (tryCtx.hasFinally) { + tryCtx.err = e; + tryCtx.state = TryCtx.STATE_FINALLY_THREW; + } + else throw e; + } + + codePtr = tryCtx.finallyStart; + return Runners.NO_RETURN; + } + else return nextNoTry(ctx); } public Object run(CallContext ctx) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index e4da6f8..1e0511f 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -124,68 +124,9 @@ public class Runners { } public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - var finallyFunc = (boolean)instr.get(1) ? frame.pop() : null; - var catchFunc = (boolean)instr.get(0) ? frame.pop() : null; - var func = frame.pop(); - - if ( - !Values.isFunction(func) || - catchFunc != null && !Values.isFunction(catchFunc) || - finallyFunc != null && !Values.isFunction(finallyFunc) - ) throw EngineException.ofType("TRY instruction can be applied only upon functions."); - - Object res = new SignalValue("no_return"); - EngineException exception = null; - - Values.function(func).name = frame.function.name + "::try"; - if (catchFunc != null) Values.function(catchFunc).name = frame.function.name + "::catch"; - if (finallyFunc != null) Values.function(finallyFunc).name = frame.function.name + "::finally"; - - try { - ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL); - res = Values.call(ctx, func, frame.thisArg); - } - catch (EngineException e) { - exception = e.setCause(exception); - } - - if (exception != null && catchFunc != null) { - var exc = exception; - exception = null; - try { - ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL); - var _res = Values.call(ctx, catchFunc, frame.thisArg, exc.value); - if (!SignalValue.isSignal(_res, "no_return")) res = _res; - } - catch (EngineException e) { - exception = e.setCause(exc); - } - } - - if (finallyFunc != null) { - try { - ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL); - var _res = Values.call(ctx, finallyFunc, frame.thisArg); - if (!SignalValue.isSignal(_res, "no_return")) { - res = _res; - exception = null; - } - } - catch (EngineException e) { - exception = e.setCause(exception); - } - } - - if (exception != null) throw exception; - if (SignalValue.isSignal(res, "no_return")) { - frame.codePtr++; - return NO_RETURN; - } - else if (SignalValue.isSignal(res, "jmp_*")) { - frame.codePtr += Integer.parseInt(((SignalValue)res).data.substring(4)); - return NO_RETURN; - } - else return res; + frame.addTry(instr.get(0), instr.get(1), instr.get(2)); + frame.codePtr++; + return NO_RETURN; } public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) { @@ -325,14 +266,23 @@ public class Runners { public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) { frame.codePtr += (int)instr.get(0); + frame.jumpFlag = true; return NO_RETURN; } public static Object execJmpIf(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - frame.codePtr += Values.toBoolean(frame.pop()) ? (int)instr.get(0) : 1; + if (Values.toBoolean(frame.pop())) { + frame.codePtr += (int)instr.get(0); + frame.jumpFlag = true; + } + else frame.codePtr ++; return NO_RETURN; } public static Object execJmpIfNot(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { - frame.codePtr += Values.not(frame.pop()) ? (int)instr.get(0) : 1; + if (Values.not(frame.pop())) { + frame.codePtr += (int)instr.get(0); + frame.jumpFlag = true; + } + else frame.codePtr ++; return NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index a14d881..384ceb8 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -11,6 +11,10 @@ import me.topchetoeu.jscript.exceptions.EngineException; public class GlobalScope implements ScopeRecord { public final ObjectValue obj; + public final GlobalScope parent; + + @Override + public GlobalScope parent() { return parent; } public boolean has(CallContext ctx, String name) throws InterruptedException { return obj.hasMember(ctx, name, false); @@ -74,9 +78,11 @@ public class GlobalScope implements ScopeRecord { } public GlobalScope() { + this.parent = null; this.obj = new ObjectValue(); } public GlobalScope(GlobalScope parent) { + this.parent = null; this.obj = new ObjectValue(); this.obj.setPrototype(null, parent.obj); } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index 39457a8..efa0bb4 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -1,12 +1,16 @@ package me.topchetoeu.jscript.engine.scope; +import java.util.ArrayList; + public class LocalScope { private String[] names; private LocalScope parent; public final ValueVariable[] captures; public final ValueVariable[] locals; + public final ArrayList catchVars = new ArrayList<>(); public ValueVariable get(int i) { + if (i >= locals.length) return catchVars.get(i - locals.length); if (i >= 0) return locals[i]; else return captures[~i]; } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java index 0cb5ae1..ee5eefa 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java @@ -15,6 +15,9 @@ public class LocalScopeRecord implements ScopeRecord { return locals.toArray(String[]::new); } + @Override + public LocalScopeRecord parent() { return parent; } + public LocalScopeRecord child() { return new LocalScopeRecord(this, global); } @@ -67,6 +70,9 @@ public class LocalScopeRecord implements ScopeRecord { locals.add(name); return locals.size() - 1; } + public void undefine() { + locals.remove(locals.size() - 1); + } public LocalScopeRecord(GlobalScope global) { this.parent = null; diff --git a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java index fe4d5d3..fa76859 100644 --- a/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java +++ b/src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java @@ -3,5 +3,6 @@ package me.topchetoeu.jscript.engine.scope; public interface ScopeRecord { public Object getKey(String name); public Object define(String name); + public ScopeRecord parent(); public LocalScopeRecord child(); }