feat: reflect scope optimizations in runtime

This commit is contained in:
TopchetoEU 2024-09-05 22:30:28 +03:00
parent 7c74df4d36
commit 6f548ce5ff
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
10 changed files with 113 additions and 65 deletions

View File

@ -3,12 +3,13 @@ package me.topchetoeu.jscript.common;
public class FunctionBody { public class FunctionBody {
public final FunctionBody[] children; public final FunctionBody[] children;
public final Instruction[] instructions; 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.children = children;
this.length = length; this.length = length;
this.localsN = localsN; this.localsN = localsN;
this.capturablesN = capturablesN;
this.capturesN = capturesN; this.capturesN = capturesN;
this.instructions = instructions; this.instructions = instructions;
} }

View File

@ -35,20 +35,21 @@ public class Instruction {
LOAD_FUNC(0x30), LOAD_FUNC(0x30),
LOAD_ARR(0x31), LOAD_ARR(0x31),
LOAD_OBJ(0x32), LOAD_OBJ(0x32),
LOAD_GLOB(0x33), LOAD_REGEX(0x33),
LOAD_INTRINSICS(0x34),
LOAD_REGEX(0x35), 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_VAR(0x40),
LOAD_MEMBER(0x41), LOAD_MEMBER(0x41),
LOAD_MEMBER_INT(0x42), LOAD_MEMBER_INT(0x42),
LOAD_MEMBER_STR(0x43), LOAD_MEMBER_STR(0x43),
LOAD_ARGS(0x44),
LOAD_REST_ARGS(0x45),
LOAD_CALLEE(0x46),
LOAD_THIS(0x47),
STORE_VAR(0x48), STORE_VAR(0x48),
STORE_MEMBER(0x49), STORE_MEMBER(0x49),
STORE_MEMBER_INT(0x4A), STORE_MEMBER_INT(0x4A),
@ -368,6 +369,9 @@ public class Instruction {
public static Instruction loadIntrinsics(String key) { public static Instruction loadIntrinsics(String key) {
return new Instruction(Type.LOAD_INTRINSICS, key); return new Instruction(Type.LOAD_INTRINSICS, key);
} }
public static Instruction loadError() {
return new Instruction(Type.LOAD_ERROR);
}
public static Instruction loadMember() { public static Instruction loadMember() {
return new Instruction(Type.LOAD_MEMBER); return new Instruction(Type.LOAD_MEMBER);
} }
@ -457,12 +461,15 @@ public class Instruction {
return new Instruction(Type.OPERATION, op); return new Instruction(Type.OPERATION, op);
} }
public static Instruction stackAlloc(int n) { public static Instruction stackAlloc(int start, int n) {
return new Instruction(Type.STACK_ALLOC, n); return new Instruction(Type.STACK_ALLOC, start, start + n);
} }
public static Instruction stackRealloc(int n) { public static Instruction stackRealloc(int start, int n) {
return new Instruction(Type.STACK_REALLOC, n); return new Instruction(Type.STACK_REALLOC, start, start + n);
} }
/**
* @deprecated
*/
public static Instruction stackFree(int n) { public static Instruction stackFree(int n) {
return new Instruction(Type.STACK_FREE, n); return new Instruction(Type.STACK_FREE, n);
} }

View File

@ -111,7 +111,7 @@ public final class CompileResult {
} }
return new FunctionBody( return new FunctionBody(
scope.localsCount() + scope.allocCount(), scope.capturesCount(), scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren length, instrRes, builtChildren
); );
} }

View File

@ -25,7 +25,7 @@ public class CompoundNode extends Node {
var subtarget = hasScope ? target.subtarget() : target; var subtarget = hasScope ? target.subtarget() : target;
if (hasScope) { 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; subtarget.scope.singleEntry = singleEntry;
} }
@ -45,10 +45,7 @@ public class CompoundNode extends Node {
else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER);
} }
if (hasScope) { if (hasScope) subtarget.scope.end();
subtarget.scope.end();
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
}
if (!polluted && pollute) { if (!polluted && pollute) {
target.add(Instruction.pushUndefined()); target.add(Instruction.pushUndefined());

View File

@ -26,7 +26,7 @@ public class ForNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) { @Override public void compile(CompileResult target, boolean pollute) {
var subtarget = target.subtarget(); var subtarget = target.subtarget();
subtarget.scope.singleEntry = false; 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); declaration.compile(subtarget, false, BreakpointType.STEP_OVER);
@ -40,7 +40,7 @@ public class ForNode extends Node {
CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER);
LabelContext.popLoop(subtarget.env, label); 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); CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER);
int endI = subtarget.size(); int endI = subtarget.size();
@ -52,7 +52,6 @@ public class ForNode extends Node {
if (pollute) subtarget.add(Instruction.pushUndefined()); if (pollute) subtarget.add(Instruction.pushUndefined());
subtarget.scope.end(); 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) { public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {

View File

@ -44,7 +44,7 @@ public class SwitchNode extends Node {
value.compile(target, true, BreakpointType.STEP_OVER); value.compile(target, true, BreakpointType.STEP_OVER);
var subtarget = target.subtarget(); 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 // TODO: create a jump map
for (var ccase : cases) { for (var ccase : cases) {
@ -65,7 +65,6 @@ public class SwitchNode extends Node {
LabelContext.getBreak(target.env).pop(label); LabelContext.getBreak(target.env).pop(label);
subtarget.scope.end(); subtarget.scope.end();
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
int endI = subtarget.size(); int endI = subtarget.size();
end.set(endI); end.set(endI);

View File

@ -23,8 +23,8 @@ public class TryNode extends Node {
@Override public void resolve(CompileResult target) { @Override public void resolve(CompileResult target) {
tryBody.resolve(target); tryBody.resolve(target);
catchBody.resolve(target); if (catchBody != null) catchBody.resolve(target);
finallyBody.resolve(target); if (finallyBody != null) finallyBody.resolve(target);
} }
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) {
@ -43,8 +43,14 @@ public class TryNode extends Node {
if (captureName != null) { if (captureName != null) {
var subtarget = target.subtarget(); 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); catchBody.compile(subtarget, false);
subtarget.scope.end(); subtarget.scope.end();
} }
else catchBody.compile(target, false); else catchBody.compile(target, false);

View File

@ -9,8 +9,8 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class Scope { public class Scope {
protected final HashMap<String, Variable> strictVarMap = new HashMap<>(); protected final HashMap<String, Variable> strictVarMap = new HashMap<>();
protected final VariableList variables = new VariableList(VariableIndex.IndexType.LOCALS, this::parentVarOffset); protected final VariableList variables = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset);
protected final VariableList captured = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::parentCapOffset); protected final VariableList captured = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset);
private boolean ended = false; private boolean ended = false;
private boolean finished = false; private boolean finished = false;
@ -41,14 +41,14 @@ public class Scope {
return var; return var;
} }
private final int parentVarOffset() { // private final int parentVarOffset() {
if (parent != null) return parent.variableOffset(); // if (parent != null) return parent.variableOffset();
else return 0; // else return 0;
} // }
private final int parentCapOffset() { // private final int parentCapOffset() {
if (parent != null) return parent.capturedOffset(); // if (parent != null) return parent.capturedOffset();
else return localsCount(); // else return localsCount();
} // }
protected final SyntaxException alreadyDefinedErr(Location loc, String name) { protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", 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 * Gets the index offset from this scope to its children
*/ */
public final int variableOffset() { public final int variableOffset() {
if (parent != null) return parent.variableOffset() + variables.size(); var res = 0;
else return variables.size();
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() { public final int capturablesOffset() {
if (parent != null) return parent.capturedOffset() + captured.size(); var res = 0;
else return localsCount() + captured.size();
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() { public int localsCount() {
@ -142,7 +158,7 @@ public class Scope {
} }
public int capturablesCount() { public int capturablesCount() {
var res = captured.size(); var res = captured.size();
for (var child : children) res += child.allocCount(); for (var child : children) res += child.capturablesCount();
return res; return res;
} }

View File

@ -1,8 +1,7 @@
package me.topchetoeu.jscript.runtime; package me.topchetoeu.jscript.runtime;
import java.util.ArrayList; import java.util.Arrays;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Stack; 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 * 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 Value[][] captures;
public final List<Value[]> locals = new ArrayList<>(); public final Value[] locals;
public final Value[][] capturables;
public final Value argsVal; public final Value argsVal;
public Value self; public Value self;
public Value fakeArgs; public Value fakeArgs;
@ -110,9 +110,20 @@ public final class Frame {
public final Environment env; public final Environment env;
private final DebugContext dbg; 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]; 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]; public Value[] stack = new Value[32];
@ -215,12 +226,13 @@ public final class Frame {
if (newCtx != tryCtx) { if (newCtx != tryCtx) {
switch (newCtx.state) { switch (newCtx.state) {
case CATCH: 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; codePtr = tryCtx.catchStart;
stackPtr = tryCtx.restoreStackPtr; stackPtr = tryCtx.restoreStackPtr;
break; break;
case FINALLY: 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; codePtr = tryCtx.finallyStart;
stackPtr = tryCtx.restoreStackPtr; stackPtr = tryCtx.restoreStackPtr;
default: default:
@ -236,7 +248,7 @@ public final class Frame {
} }
else { else {
popTryFlag = false; 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()) { if (tryCtx.state != TryState.FINALLY && tryCtx.hasFinally()) {
codePtr = tryCtx.finallyStart; codePtr = tryCtx.finallyStart;
@ -422,8 +434,13 @@ public final class Frame {
var i = 0; var i = 0;
for (i = 0; i < func.body.localsN; i++) { this.locals = new Value[func.body.localsN];
this.locals.add(new Value[] { Value.UNDEFINED }); 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;
} }
} }
} }

View File

@ -140,7 +140,7 @@ public class InstructionRunner {
private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
int i = instr.get(0); int i = instr.get(0);
frame.push(frame.getVar(i)[0]); frame.push(frame.getVar(i));
frame.codePtr++; frame.codePtr++;
return null; return null;
@ -179,7 +179,7 @@ public class InstructionRunner {
var captures = new Value[instr.params.length - 5][]; var captures = new Value[instr.params.length - 5][];
for (var i = 5; i < instr.params.length; i++) { 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); 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(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
int i = instr.get(0); int i = instr.get(0);
frame.getVar(i)[0] = val; frame.setVar(i, val);
frame.codePtr++; frame.codePtr++;
return null; return null;
@ -498,29 +498,34 @@ public class InstructionRunner {
frame.codePtr++; frame.codePtr++;
return null; 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) { 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++; frame.codePtr++;
return null; return null;
} }
private static Value execStackRealloc(Environment env, Instruction instr, Frame frame) { 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++; frame.codePtr++;
return null; return null;
} }
private static Value execStackFree(Environment env, Instruction instr, Frame frame) { 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++) { // TODO: Remove if safe to do so
frame.locals.remove(frame.locals.size() - 1);
}
frame.codePtr++; frame.codePtr++;
return null; return null;
@ -559,6 +564,7 @@ public class InstructionRunner {
case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame); case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame);
case LOAD_CALLEE: return execLoadCallee(env, instr, frame); case LOAD_CALLEE: return execLoadCallee(env, instr, frame);
case LOAD_THIS: return execLoadThis(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 DISCARD: return execDiscard(env, instr, frame);
case STORE_MEMBER: return execStoreMember(env, instr, frame); case STORE_MEMBER: return execStoreMember(env, instr, frame);