From 7c74df4d36a4f80fb3d2fa93ca2da5144dd8c997 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 5 Sep 2024 21:25:39 +0300 Subject: [PATCH 1/2] refactor: use new system to reorder variables that overlaps neighboring scopes --- .../jscript/compilation/CompileResult.java | 1 + .../jscript/compilation/FunctionNode.java | 13 +- .../jscript/compilation/JavaScript.java | 1 + .../compilation/scope/FunctionScope.java | 82 +++++------ .../jscript/compilation/scope/Scope.java | 127 ++++++++++-------- .../jscript/compilation/scope/Variable.java | 16 +-- .../compilation/scope/VariableIndex.java | 44 ++++++ .../compilation/scope/VariableList.java | 105 +++++---------- .../compilation/values/VariableNode.java | 8 +- .../me/topchetoeu/jscript/runtime/Frame.java | 19 ++- .../jscript/runtime/InstructionRunner.java | 2 - src/main/resources/lib/index.js | 4 +- 12 files changed, 205 insertions(+), 217 deletions(-) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index 4234653..c4e6382 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -106,6 +106,7 @@ public final class CompileResult { for (var suppl : instructions) { instrRes[i] = suppl.apply(i); + // System.out.println(instrRes[i]); i++; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 154b360..9e09ff2 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -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); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index 5e06b59..87d3d7e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -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); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java index 4f8f143..e935be3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -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 childToParent = new HashMap<>(); + private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES); + + private final HashMap specialVarMap = new HashMap<>(); + private final HashMap functionVarMap = new HashMap<>(); + private final HashMap capturesMap = new HashMap<>(); private final HashSet blacklistNames = new HashSet<>(); + private final HashMap 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; } } 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 3129e36..1131766 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -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 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); private boolean ended = false; private boolean finished = false; private Scope child; - private List prevChildren = new LinkedList<>(); + private LinkedList children = new LinkedList<>(); public final Scope parent; - public final HashSet 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,10 +25,30 @@ public class Scope { */ public boolean singleEntry = true; - private final int parentOffset() { - if (parent != null) return parent.offset(); + + 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,34 @@ public class Scope { /** * Gets the index offset from this scope to its children */ - public int offset() { - if (parent != null) return parent.offset() + variables.size(); + public final int variableOffset() { + if (parent != null) return parent.variableOffset() + variables.size(); else return variables.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); - } - else { - variables.overlay(variable); - return true; - } + public final int capturedOffset() { + if (parent != null) return parent.capturedOffset() + captured.size(); + else return localsCount() + captured.size(); } - public int localsCount() { return 0; } + public int localsCount() { + var res = 0; + for (var child : children) { + var childN = child.localsCount(); + if (res < childN) res = childN; + } + + 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.allocCount(); + return res; + } /** * Ends this scope. This will make it possible for another child to take its place @@ -142,34 +162,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(); - var normal = new HashSet(); - - 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 +199,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; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java index af74088..3a91792 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java @@ -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 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 index) { this.indexSupplier = index; return this; } - public final IntSupplier indexSupplier() { + public final Supplier 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); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java new file mode 100644 index 0000000..3e04a05 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java @@ -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; + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java index 9dfed4b..3e71689 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java @@ -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 { 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 map = new HashMap<>(); - private ArrayList frozenList = null; - private HashMap varMap = new HashMap<>(); + private ArrayList frozenList = null; + private HashMap 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 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 all() { if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator(); else return () -> new Iterator() { - 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 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; } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index 0bf8ea6..eb4db6f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -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 toGet(CompileResult target, Location loc, String name, Supplier 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 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) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 31f9434..170162c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -18,7 +18,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 KEY = Key.of(); @@ -357,18 +356,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 diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 67badc2..0c464bf 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -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]); diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index c807527..43e8837 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -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; }; @@ -310,4 +308,4 @@ setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("symbol", Symbol.prototype); -setGlobalPrototype("object", Object.prototype); \ No newline at end of file +setGlobalPrototype("object", Object.prototype); -- 2.45.2 From 6f548ce5ffad3eb5fc41e15fd432b57f83049122 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:30:28 +0300 Subject: [PATCH 2/2] feat: reflect scope optimizations in runtime --- .../jscript/common/FunctionBody.java | 5 +- .../jscript/common/Instruction.java | 31 +++++++----- .../jscript/compilation/CompileResult.java | 2 +- .../jscript/compilation/CompoundNode.java | 7 +-- .../jscript/compilation/control/ForNode.java | 5 +- .../compilation/control/SwitchNode.java | 3 +- .../jscript/compilation/control/TryNode.java | 12 +++-- .../jscript/compilation/scope/Scope.java | 48 ++++++++++++------- .../me/topchetoeu/jscript/runtime/Frame.java | 37 ++++++++++---- .../jscript/runtime/InstructionRunner.java | 28 ++++++----- 10 files changed, 113 insertions(+), 65 deletions(-) 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); -- 2.45.2