refactor: use new system to reorder variables that overlaps neighboring scopes

This commit is contained in:
TopchetoEU 2024-09-05 21:25:39 +03:00
parent 641d4d1863
commit 7c74df4d36
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
12 changed files with 205 additions and 217 deletions

View File

@ -106,6 +106,7 @@ public final class CompileResult {
for (var suppl : instructions) {
instrRes[i] = suppl.apply(i);
// System.out.println(instrRes[i]);
i++;
}

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

@ -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::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<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,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<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 +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;
}

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

@ -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<Frame> 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

View File

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

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