Merge pull request #27 from TopchetoEU/TopchetoEU/optimize-var-flatten

Optimize var flattening
This commit is contained in:
TopchetoEU 2024-09-05 22:32:48 +03:00 committed by GitHub
commit b6f04aa177
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 303 additions and 267 deletions

View File

@ -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;
}

View File

@ -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);
}

View File

@ -106,11 +106,12 @@ public final class CompileResult {
for (var suppl : instructions) {
instrRes[i] = suppl.apply(i);
// System.out.println(instrRes[i]);
i++;
}
return new FunctionBody(
scope.localsCount() + scope.allocCount(), scope.capturesCount(),
scope.localsCount(), scope.capturablesCount(), scope.capturesCount(),
length, instrRes, builtChildren
);
}

View File

@ -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());

View File

@ -31,13 +31,6 @@ public abstract class FunctionNode extends Node {
.remove(LabelContext.CONTINUE_CTX);
return new CompileResult(env, scope, params.params.size(), target -> {
// if (params.params.size() > 0) target.add(Instruction.loadArgs(true));
// if (hasArgs) {
// var argsVar = scope.defineStrict(new Variable("arguments", true), loc());
// target.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0));
// }
if (params.params.size() > 0) {
target.add(Instruction.loadArgs(true));
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1));
@ -65,7 +58,7 @@ public abstract class FunctionNode extends Node {
end.set(target.size());
}
target.add(_i -> Instruction.storeVar(varI.index()));
target.add(_i -> varI.index().toSet(false));
}
}
@ -73,14 +66,14 @@ public abstract class FunctionNode extends Node {
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
target.add(Instruction.loadRestArgs(params.params.size()));
target.add(_i -> Instruction.storeVar(restVar.index()));
target.add(_i -> restVar.index().toSet(false));
}
if (selfName != null && !scope.has(name, false)) {
var i = scope.defineSpecial(new Variable(selfName, true), end);
target.add(Instruction.loadCallee());
target.add(_i -> Instruction.storeVar(i.index(), false));
target.add(_i -> i.index().toSet(false));
}
body.resolve(target);

View File

@ -311,6 +311,7 @@ public final class JavaScript {
else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax");
i += res.n;
i += Parsing.skipEmpty(src, i);
list.add(res.result);
}

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -4,65 +4,56 @@ import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class FunctionScope extends Scope {
private final VariableList captures = new VariableList().setIndexMap(v -> ~v);
private final VariableList specials = new VariableList();
private final VariableList locals = new VariableList(specials);
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
private final HashMap<String, Variable> specialVarMap = new HashMap<>();
private final HashMap<String, Variable> functionVarMap = new HashMap<>();
private final HashMap<String, Variable> capturesMap = new HashMap<>();
private final HashSet<String> blacklistNames = new HashSet<>();
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final Scope captureParent;
public final boolean passtrough;
private void removeCapture(String name) {
var res = captures.remove(name);
if (res != null) {
childToParent.remove(res);
res.setIndexSupplier(() -> { throw new SyntaxException(null, res.name + " has been shadowed"); });
}
}
@Override public Variable define(Variable var, Location loc) {
checkNotEnded();
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (passtrough) {
blacklistNames.add(var.name);
return null;
}
removeCapture(var.name);
return locals.add(var);
else {
functionVarMap.put(var.name, var);
return variables.add(var);
}
}
@Override public Variable defineStrict(Variable var, Location loc) {
checkNotEnded();
if (locals.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (functionVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name);
var res = super.defineStrict(var, loc);
removeCapture(var.name);
return res;
return super.defineStrict(var, loc);
}
public Variable defineSpecial(Variable var, Location loc) {
return specials.add(var);
}
checkNotEnded();
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
@Override public boolean flattenVariable(Variable variable, boolean capturable) {
// if (!ended()) throw new IllegalStateException("Tried to flatten a variable before the scope has ended");
this.locals.overlay(variable);
return true;
specialVarMap.put(var.name, var);
return variables.add(var);
}
@Override public Variable get(String name, boolean capture) {
var superRes = super.get(name, capture);
if (superRes != null) return superRes;
if (specials.has(name)) return addCaptured(specials.get(name), capture);
if (locals.has(name)) return addCaptured(locals.get(name), capture);
if (captures.has(name)) return addCaptured(captures.get(name), capture);
if (specialVarMap.containsKey(name)) return addCaptured(specialVarMap.get(name), capture);
if (functionVarMap.containsKey(name)) return addCaptured(functionVarMap.get(name), capture);
if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture);
if (captureParent == null) return null;
@ -70,47 +61,32 @@ public class FunctionScope extends Scope {
if (parentVar == null) return null;
var childVar = captures.add(parentVar.clone());
capturesMap.put(childVar.name, childVar);
childToParent.put(childVar, parentVar);
return childVar;
}
@Override public boolean has(String name, boolean capture) {
if (specials.has(name)) return true;
if (locals.has(name)) return true;
if (functionVarMap.containsKey(name)) return true;
if (specialVarMap.containsKey(name)) return true;
if (capture) {
if (captures.has(name)) return true;
if (capturesMap.containsKey(name)) return true;
if (captureParent != null) return captureParent.has(name, true);
}
return false;
}
@Override public boolean finish() {
if (!super.finish()) return false;
@Override protected void onFinish() {
captures.freeze();
locals.freeze();
specials.freeze();
return true;
super.onFinish();
}
@Override public int allocCount() {
return 0;
}
@Override public int capturesCount() {
return captures.size();
}
@Override public int localsCount() {
return locals.size() + specials.size() + super.allocCount();
}
public int offset() {
return specials.size() + locals.size();
}
public int[] getCaptureIndices() {
var res = new int[captures.size()];
@ -118,7 +94,7 @@ public class FunctionScope extends Scope {
for (var el : captures.all()) {
assert childToParent.containsKey(el);
res[i] = childToParent.get(el).index();
res[i] = childToParent.get(el).index().toCaptureIndex();
i++;
}
@ -130,10 +106,12 @@ public class FunctionScope extends Scope {
if (parent.finished()) throw new RuntimeException("Parent is finished");
this.captureParent = parent;
this.passtrough = false;
this.singleEntry = false;
}
public FunctionScope(boolean passtrough) {
super();
this.captureParent = null;
this.passtrough = passtrough;
this.singleEntry = false;
}
}

View File

@ -1,27 +1,23 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class Scope {
protected final VariableList variables = new VariableList(this::parentOffset);
protected final HashMap<String, Variable> strictVarMap = new HashMap<>();
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;
private Scope child;
private List<Scope> prevChildren = new LinkedList<>();
private LinkedList<Scope> children = new LinkedList<>();
public final Scope parent;
public final HashSet<Variable> captured = new HashSet<>();
protected final Variable addCaptured(Variable var, boolean captured) {
if (captured) this.captured.add(var);
return var;
}
/**
* Wether or not the scope is going to be entered multiple times.
@ -29,11 +25,31 @@ public class Scope {
*/
public boolean singleEntry = true;
private final int parentOffset() {
if (parent != null) return parent.offset();
else return 0;
protected void transferCaptured(Variable var) {
if (!singleEntry) {
this.captured.add(var);
}
else if (parent != null) {
parent.transferCaptured(var);
}
else throw new IllegalStateException("Couldn't transfer captured variable");
}
protected final Variable addCaptured(Variable var, boolean captured) {
if (captured) transferCaptured(var);
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();
// }
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
}
@ -54,7 +70,7 @@ public class Scope {
*/
public Variable define(Variable var, Location loc) {
checkNotEnded();
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
if (parent != null) return parent.define(var, loc);
return null;
@ -69,10 +85,10 @@ public class Scope {
*/
public Variable defineStrict(Variable var, Location loc) {
checkNotEnded();
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
variables.add(var);
return var.setIndexSupplier(() -> variables.indexOfKey(var.name));
strictVarMap.put(var.name, var);
return variables.add(var);
}
/**
* Gets the index supplier of the given variable name, or null if it is a global
@ -80,7 +96,8 @@ public class Scope {
* @param capture If true, the variable is being captured by a function
*/
public Variable get(String name, boolean capture) {
var res = variables.get(name);
var res = strictVarMap.get(name);
if (res != null) return addCaptured(res, capture);
if (parent != null) return parent.get(name, capture);
@ -92,7 +109,7 @@ public class Scope {
* @param capture If true, will check beyond this function's scope
*/
public boolean has(String name, boolean capture) {
if (variables.has(name)) return true;
if (strictVarMap.containsKey(name)) return true;
if (parent != null) return parent.has(name, capture);
return false;
@ -100,31 +117,50 @@ public class Scope {
/**
* Gets the index offset from this scope to its children
*/
public int offset() {
if (parent != null) return parent.offset() + variables.size();
else return variables.size();
public final int variableOffset() {
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 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();
}
/**
* Adds this variable to the current function's locals record. Capturable indicates whether or not the variable
* should still be capturable, or be put in an array (still not implemented)
*
* @return Whether or not the request was actually fuliflled
*/
public boolean flattenVariable(Variable variable, boolean capturable) {
if (singleEntry || !capturable) {
if (parent == null) return false;
return parent.flattenVariable(variable, capturable);
public int localsCount() {
var res = 0;
for (var child : children) {
var childN = child.localsCount();
if (res < childN) res = childN;
}
else {
variables.overlay(variable);
return true;
}
}
public int localsCount() { return 0; }
return res + variables.size();
}
public int capturesCount() { return 0; }
public int allocCount() { return variables.size(); }
public int allocCount() {
var res = captured.size();
return res;
}
public int capturablesCount() {
var res = captured.size();
for (var child : children) res += child.capturablesCount();
return res;
}
/**
* Ends this scope. This will make it possible for another child to take its place
@ -142,34 +178,23 @@ public class Scope {
return true;
}
protected void onFinish() {
this.variables.freeze();
this.captured.freeze();
}
/**
* Finalizes this scope. The scope will become immutable after this call
* @return
*/
public boolean finish() {
public final boolean finish() {
if (finished) return false;
if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished");
this.onFinish();
for (var child : prevChildren) child.finish();
for (var child : children) child.finish();
var captured = new HashSet<Variable>();
var normal = new HashSet<Variable>();
for (var v : variables.all()) {
if (this.captured.contains(v)) {
if (singleEntry) captured.add(v);
}
else normal.add(v);
}
for (var v : captured) variables.remove(v);
for (var v : normal) variables.remove(v);
for (var v : captured) flattenVariable(v, true);
for (var v : normal) flattenVariable(v, false);
this.variables.freeze();
this.finished = true;
return true;
@ -190,7 +215,7 @@ public class Scope {
this.parent = parent;
this.parent.child = this;
this.parent.prevChildren.add(this);
this.parent.children.add(this);
}
else this.parent = null;
}

View File

@ -1,28 +1,28 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
public final class Variable {
private IntSupplier indexSupplier;
private Supplier<VariableIndex> indexSupplier;
private boolean frozen;
public final boolean readonly;
public final String name;
public final int index() {
public final VariableIndex index() {
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
return indexSupplier.getAsInt();
return indexSupplier.get();
}
public final void freeze() {
this.frozen = true;
}
public final Variable setIndexSupplier(IntSupplier index) {
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
this.indexSupplier = index;
return this;
}
public final IntSupplier indexSupplier() {
public final Supplier<VariableIndex> indexSupplier() {
return indexSupplier;
}
@ -35,7 +35,7 @@ public final class Variable {
this.readonly = readonly;
}
public static Variable of(String name, boolean readonly, int i) {
return new Variable(name, readonly).setIndexSupplier(() -> i);
public static Variable of(String name, boolean readonly, VariableIndex index) {
return new Variable(name, readonly).setIndexSupplier(() -> index);
}
}

View File

@ -0,0 +1,44 @@
package me.topchetoeu.jscript.compilation.scope;
import me.topchetoeu.jscript.common.Instruction;
public final class VariableIndex {
public static enum IndexType {
LOCALS,
CAPTURABLES,
CAPTURES,
}
public final VariableIndex.IndexType type;
public final int index;
public final int toCaptureIndex() {
switch (type) {
case CAPTURES: return ~index;
case CAPTURABLES: return index;
default: throw new UnsupportedOperationException("Index type " + type + " may not be captured");
}
}
public final Instruction toGet() {
switch (type) {
case CAPTURES: return Instruction.loadVar(~index);
case CAPTURABLES: return Instruction.loadVar(index);
case LOCALS: return Instruction.loadVar(index);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public final Instruction toSet(boolean keep) {
switch (type) {
case CAPTURES: return Instruction.storeVar(~index, keep);
case CAPTURABLES: return Instruction.storeVar(index, keep);
case LOCALS: return Instruction.storeVar(index, keep);
default: throw new UnsupportedOperationException("Unknown index type " + type);
}
}
public VariableIndex(VariableIndex.IndexType type, int index) {
this.type = type;
this.index = index;
}
}

View File

@ -4,27 +4,22 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.stream.StreamSupport;
import java.util.function.Supplier;
public final class VariableList {
private final class Node implements IntSupplier {
private final class VariableNode implements Supplier<VariableIndex> {
public Variable var;
public Node next;
public Node prev;
public VariableNode next;
public VariableNode prev;
public boolean frozen;
public int index;
@Override public int getAsInt() {
public VariableList list() { return VariableList.this; }
@Override public VariableIndex get() {
if (frozen) {
if (offset == null) {
return indexConverter == null ? index : indexConverter.applyAsInt(index);
}
else {
return indexConverter == null ?
index + offset.getAsInt() :
indexConverter.applyAsInt(index + offset.getAsInt());
}
if (offset == null) return new VariableIndex(indexType, index);
else new VariableIndex(indexType, index + offset.getAsInt());
}
var res = 0;
@ -34,7 +29,7 @@ public final class VariableList {
res++;
}
return indexConverter == null ? res : indexConverter.applyAsInt(res);
return new VariableIndex(indexType, res);
}
public void freeze() {
@ -50,26 +45,26 @@ public final class VariableList {
return;
}
public Node(Variable var, Node next, Node prev) {
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
this.var = var;
this.next = next;
this.prev = prev;
}
}
private Node first, last;
private VariableNode first, last;
private final HashMap<String, Node> map = new HashMap<>();
private ArrayList<Node> frozenList = null;
private HashMap<Variable, Node> varMap = new HashMap<>();
private ArrayList<VariableNode> frozenList = null;
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
private final IntSupplier offset;
private IntUnaryOperator indexConverter = null;
public final VariableIndex.IndexType indexType;
public boolean frozen() {
if (frozenList != null) {
assert frozenList != null;
assert map != null;
assert varMap == null;
assert first == null;
assert last == null;
@ -77,21 +72,20 @@ public final class VariableList {
}
else {
assert frozenList == null;
assert map != null;
assert varMap != null;
return false;
}
}
private Variable add(Variable val, boolean overlay) {
public Variable add(Variable val) {
if (frozen()) throw new RuntimeException("The scope has been frozen");
if (!overlay && map.containsKey(val.name)) {
var node = this.map.get(val.name);
val.setIndexSupplier(node);
return node.var;
if (val.indexSupplier() instanceof VariableNode prevNode) {
prevNode.list().remove(val);
}
var node = new Node(val, null, last);
var node = new VariableNode(val, null, last);
if (last != null) {
assert first != null;
@ -105,28 +99,17 @@ public final class VariableList {
first = last = node;
}
map.put(val.name, node);
varMap.put(val, node);
val.setIndexSupplier(node);
return val;
}
public Variable add(Variable val) {
return this.add(val, false);
}
public Variable overlay(Variable val) {
return this.add(val, true);
}
public Variable remove(String key) {
var res = map.get(key);
if (res != null) return remove(res.var);
else return null;
}
public Variable remove(Variable var) {
if (var == null) return null;
if (frozen()) throw new RuntimeException("The scope has been frozen");
if (var == null) return null;
var node = varMap.get(var);
if (node == null) return null;
@ -151,28 +134,18 @@ public final class VariableList {
node.next = null;
node.prev = null;
map.remove(node.var.name);
varMap.remove(node.var);
return node.var;
}
public Variable get(String name) {
var res = map.get(name);
if (res != null) return res.var;
else return null;
}
public int indexOfKey(String name) {
return map.get(name).getAsInt();
}
public boolean has(String name) {
return this.map.containsKey(name);
public Supplier<VariableIndex> indexer(Variable var) {
return varMap.get(var);
}
public int size() {
if (frozen()) return frozenList.size();
else return map.size();
else return varMap.size();
}
public void freeze() {
@ -195,7 +168,7 @@ public final class VariableList {
public Iterable<Variable> all() {
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
else return () -> new Iterator<Variable>() {
private Node curr = first;
private VariableNode curr = first;
@Override public boolean hasNext() {
return curr != null;
@ -209,25 +182,21 @@ public final class VariableList {
}
};
}
public Iterable<String> keys() {
return () -> StreamSupport.stream(all().spliterator(), false).map(v -> v.name).iterator();
}
public VariableList setIndexMap(IntUnaryOperator map) {
indexConverter = map;
return this;
}
public VariableList(IntSupplier offset) {
public VariableList(VariableIndex.IndexType type, IntSupplier offset) {
this.indexType = type;
this.offset = offset;
}
public VariableList(int offset) {
public VariableList(VariableIndex.IndexType type, int offset) {
this.indexType = type;
this.offset = () -> offset;
}
public VariableList(VariableList prev) {
public VariableList(VariableIndex.IndexType type, VariableList prev) {
this.indexType = type;
this.offset = prev::size;
}
public VariableList() {
public VariableList(VariableIndex.IndexType type) {
this.indexType = type;
this.offset = null;
}
}

View File

@ -34,9 +34,7 @@ public class VariableNode extends Node implements AssignableNode {
if (!pollute) target.add(Instruction.discard());
}
else if (pollute) {
target.add(_i -> Instruction.loadVar(i.index()));
}
else if (pollute) target.add(_i -> i.index().toGet());
}
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
@ -46,7 +44,7 @@ public class VariableNode extends Node implements AssignableNode {
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name));
else return onGlobal.get();
};
else return _i -> Instruction.loadVar(i.index());
else return _i -> i.index().toGet();
}
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) {
return toGet(target, loc, name, () -> Instruction.globGet(name));
@ -61,7 +59,7 @@ public class VariableNode extends Node implements AssignableNode {
else return Instruction.globSet(name, keep, define);
};
else if (!define && i.readonly) return _i -> Instruction.throwSyntax(new SyntaxException(loc, "Assignment to constant variable"));
else return _i -> Instruction.storeVar(i.index(), keep);
else return _i -> i.index().toSet(keep);
}
public VariableNode(Location loc, String name) {

View File

@ -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;
@ -18,7 +17,6 @@ import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.objects.ScopeValue;
public final class Frame {
public static final Key<Frame> KEY = Key.of();
@ -100,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<Value[]> locals = new ArrayList<>();
public final Value[] locals;
public final Value[][] capturables;
public final Value argsVal;
public Value self;
public Value fakeArgs;
@ -111,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];
@ -216,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:
@ -237,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;
@ -357,18 +368,18 @@ public final class Frame {
* Gets an object proxy of the capture locals
*/
public ObjectValue getCaptureScope() {
// throw new RuntimeException("Not supported");
throw new RuntimeException("Not supported");
var names = new String[captures.length];
var map = DebugContext.get(env).getMapOrEmpty(function);
// var names = new String[captures.length];
// var map = DebugContext.get(env).getMapOrEmpty(function);
for (int i = 0; i < captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < map.captureNames.length) name = map.captureNames[i];
names[i] = name;
}
// for (int i = 0; i < captures.length; i++) {
// var name = "capture_" + (i - 2);
// if (i < map.captureNames.length) name = map.captureNames[i];
// names[i] = name;
// }
return new ScopeValue(captures, names);
// return new ScopeValue(captures, names);
}
/**
* Gets an array proxy of the local locals
@ -423,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;
}
}
}

View File

@ -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;
@ -340,8 +340,6 @@ public class InstructionRunner {
frame.stackPtr -= 1;
var ptr = frame.stackPtr;
// for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
switch (op) {
case ADD:
res = Value.add(env, stack[ptr - 1], stack[ptr]);
@ -500,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;
@ -561,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);

View File

@ -257,8 +257,6 @@ const Function = function() {
parts[parts.length] = String(arguments[arguments.length - 1]);
parts[parts.length] = "\n}";
print(parts);
const res = compile(stringBuild(parts))();
return res;
};