feat: make try-catch call-less

This commit is contained in:
TopchetoEU 2023-08-25 14:26:08 +03:00
parent bef35ebb1b
commit 0ea01996ee
No known key found for this signature in database
GPG Key ID: 24E57B2E9C61AD19
9 changed files with 197 additions and 111 deletions

View File

@ -54,7 +54,7 @@ public class Main {
public static void main(String args[]) { public static void main(String args[]) {
var in = new BufferedReader(new InputStreamReader(System.in)); var in = new BufferedReader(new InputStreamReader(System.in));
engine = new TypescriptEngine(new File(".")); engine = new PolyfillEngine(new File("."));
var scope = engine.global().globalChild(); var scope = engine.global().globalChild();
var exited = new boolean[1]; var exited = new boolean[1];

View File

@ -135,8 +135,8 @@ public class Instruction {
this.params = params; this.params = params;
} }
public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) { public static Instruction tryInstr(int n, int catchN, int finallyN) {
return new Instruction(null, Type.TRY, hasCatch, hasFinally); return new Instruction(null, Type.TRY, n, catchN, finallyN);
} }
public static Instruction throwInstr() { public static Instruction throwInstr() {
return new Instruction(null, Type.THROW); return new Instruction(null, Type.THROW);

View File

@ -5,7 +5,8 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; 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; import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class TryStatement extends Statement { public class TryStatement extends Statement {
@ -24,57 +25,42 @@ public class TryStatement extends Statement {
if (finallyBody != null) finallyBody.declare(globScope); if (finallyBody != null) finallyBody.declare(globScope);
} }
private void compileBody(List<Instruction> 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("<catchargs>");
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 @Override
public void compile(List<Instruction> target, ScopeRecord scope) { public void compile(List<Instruction> 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) { 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) { 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++) { // for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) { // if (target.get(i).type == Type.NOP) {
var instr = target.get(i); // var instr = target.get(i);
if (instr.is(0, "break")) { // if (instr.is(0, "break")) {
target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location)); // target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
} // }
else if (instr.is(0, "cont")) { // else if (instr.is(0, "cont")) {
target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location)); // 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) { public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {

View File

@ -9,13 +9,41 @@ import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.CallContext.DataKey; import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.LocalScope; 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.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
public class CodeFrame { 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<Integer> STACK_N_KEY = new DataKey<>(); public static final DataKey<Integer> STACK_N_KEY = new DataKey<>();
public static final DataKey<Integer> MAX_STACK_KEY = new DataKey<>(); public static final DataKey<Integer> MAX_STACK_KEY = new DataKey<>();
@ -31,9 +59,16 @@ public class CodeFrame {
public Object[] stack = new Object[32]; public Object[] stack = new Object[32];
public int stackPtr = 0; public int stackPtr = 0;
public int codePtr = 0; public int codePtr = 0;
public boolean jumpFlag = false;
private DebugCommand debugCmd = null; private DebugCommand debugCmd = null;
private Location prevLoc = 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() { public Object peek() {
return peek(0); return peek(0);
} }
@ -114,6 +149,7 @@ public class CodeFrame {
// } // }
try { try {
this.jumpFlag = false;
return Runners.exec(debugCmd, instr, this, ctx); return Runners.exec(debugCmd, instr, this, ctx);
} }
catch (EngineException e) { catch (EngineException e) {
@ -122,7 +158,104 @@ public class CodeFrame {
} }
public Object next(CallContext ctx) throws InterruptedException { 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 { public Object run(CallContext ctx) throws InterruptedException {

View File

@ -124,68 +124,9 @@ public class Runners {
} }
public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var finallyFunc = (boolean)instr.get(1) ? frame.pop() : null; frame.addTry(instr.get(0), instr.get(1), instr.get(2));
var catchFunc = (boolean)instr.get(0) ? frame.pop() : null; frame.codePtr++;
var func = frame.pop(); return NO_RETURN;
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;
} }
public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) { 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) { public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIf(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { 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; return NO_RETURN;
} }
public static Object execJmpIfNot(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException { 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; return NO_RETURN;
} }

View File

@ -11,6 +11,10 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord { public class GlobalScope implements ScopeRecord {
public final ObjectValue obj; public final ObjectValue obj;
public final GlobalScope parent;
@Override
public GlobalScope parent() { return parent; }
public boolean has(CallContext ctx, String name) throws InterruptedException { public boolean has(CallContext ctx, String name) throws InterruptedException {
return obj.hasMember(ctx, name, false); return obj.hasMember(ctx, name, false);
@ -74,9 +78,11 @@ public class GlobalScope implements ScopeRecord {
} }
public GlobalScope() { public GlobalScope() {
this.parent = null;
this.obj = new ObjectValue(); this.obj = new ObjectValue();
} }
public GlobalScope(GlobalScope parent) { public GlobalScope(GlobalScope parent) {
this.parent = null;
this.obj = new ObjectValue(); this.obj = new ObjectValue();
this.obj.setPrototype(null, parent.obj); this.obj.setPrototype(null, parent.obj);
} }

View File

@ -1,12 +1,16 @@
package me.topchetoeu.jscript.engine.scope; package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
public class LocalScope { public class LocalScope {
private String[] names; private String[] names;
private LocalScope parent; private LocalScope parent;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final ValueVariable[] locals; public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) { public ValueVariable get(int i) {
if (i >= locals.length) return catchVars.get(i - locals.length);
if (i >= 0) return locals[i]; if (i >= 0) return locals[i];
else return captures[~i]; else return captures[~i];
} }

View File

@ -15,6 +15,9 @@ public class LocalScopeRecord implements ScopeRecord {
return locals.toArray(String[]::new); return locals.toArray(String[]::new);
} }
@Override
public LocalScopeRecord parent() { return parent; }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this, global); return new LocalScopeRecord(this, global);
} }
@ -67,6 +70,9 @@ public class LocalScopeRecord implements ScopeRecord {
locals.add(name); locals.add(name);
return locals.size() - 1; return locals.size() - 1;
} }
public void undefine() {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord(GlobalScope global) { public LocalScopeRecord(GlobalScope global) {
this.parent = null; this.parent = null;

View File

@ -3,5 +3,6 @@ package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord { public interface ScopeRecord {
public Object getKey(String name); public Object getKey(String name);
public Object define(String name); public Object define(String name);
public ScopeRecord parent();
public LocalScopeRecord child(); public LocalScopeRecord child();
} }