Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/CodeGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,49 @@ private void visitExpression(Node node, int contextFlags) {
stackChange(1);
break;

case Token.OBJECT_REST:
{
// Handle object rest operation: {...rest} in destructuring
visitExpression(child, 0); // source object
Object[] excludedKeys = (Object[]) node.getProp(Node.OBJECT_IDS_PROP);

// Count static vs computed keys and build static key indices array
int computedKeyCount = 0;
java.util.List<Integer> staticKeyIndices = new java.util.ArrayList<>();
for (Object key : excludedKeys) {
if (key instanceof Node) {
computedKeyCount++;
} else {
String keyStr = key.toString();
int keyIndex = strings.getOrDefault(keyStr, -1);
if (keyIndex == -1) {
keyIndex = strings.size();
strings.put(keyStr, keyIndex);
}
staticKeyIndices.add(keyIndex);
}
}

// Store static key indices in literalIds
int staticKeysLiteralId = literalIds.size();
int[] staticKeyArray = staticKeyIndices.stream().mapToInt(i -> i).toArray();
literalIds.add(staticKeyArray);

// Evaluate & push computed keys onto stack
for (Object key : excludedKeys) {
if (key instanceof Node) {
visitExpression((Node) key, 0); // Evaluate computed key expression
}
}

// Bytecode: [Icode_OBJECT_REST] [literalId:index] [computedKeyCount:uint8]
addIndexOp(Icode_OBJECT_REST, staticKeysLiteralId);
addUint8(computedKeyCount);

stackChange(-computedKeyCount); // Computed keys removed from stack
}
break;

case Token.REF_CALL:
case Token.CALL:
case Token.NEW:
Expand Down
7 changes: 6 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Icode.java
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,11 @@ abstract class Icode {
// spread
Icode_SPREAD = Icode_DELPROP_SUPER - 1,

// object rest - create object excluding extracted keys
Icode_OBJECT_REST = Icode_SPREAD - 1,

// Last icode
MIN_ICODE = Icode_SPREAD;
MIN_ICODE = Icode_OBJECT_REST;

static String bytecodeName(int bytecode) {
if (!validBytecode(bytecode)) {
Expand Down Expand Up @@ -358,6 +361,8 @@ static String bytecodeName(int bytecode) {
return "DELPROP_SUPER";
case Icode_SPREAD:
return "SPREAD";
case Icode_OBJECT_REST:
return "OBJECT_REST";
}

// icode without name
Expand Down
67 changes: 67 additions & 0 deletions rhino/src/main/java/org/mozilla/javascript/Interpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,32 @@ static <T extends ScriptOrFn<T>> void dumpICode(
out.println(tname + " \"" + str + '"');
break;
}
case Icode_OBJECT_REST:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I like the way this byte code is structured, it's the first time we've had a code whose length depend on its data in this way, but I understand how you got there. How big a change would it be to restructure the two complex literal types in InterpreterData (regexes and template literals) as a single Object[] and put your data structure in that?

Edit: Ah, I see the class compiled version is doing the same sort of thing. Maybe this is the point to start moving this sort of complex data onto the JSDesccriptor and see if that helps tackle the big TODO in the ScriptRuntime change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bug with dumpIcode. Earlier I was directly storing computed keys and static keys in arrays[] and their counts. @andreabergia had suggested me to use itsliteralIds instead. I'd forgotten to update branch in dumpIcode along with those changes.

This is how the bytecodes emitted looks now. For:

  const obj = {a: 1, b: 2, c: 3};
  const {a, ...rest} = obj;
...
      [43] BINDNAME
      [44] REG_STR_C2 "$0"
      [45] NAME
      [46] REG_IND_C2
      [47] OBJECT_REST excluding [static: "a"; computed: 0]
// [48] would be just 0 byte for computed key count
      [49] REG_STR_C3 "rest"
      [50] SETCONST
...

{
// read count of computed keys from bytecode
int computedKeyCount = iCode[pc++] & 0xFF;

if (indexReg >= 0) {
int[] staticKeyIndices = (int[]) idata.literalIds[indexReg];
StringBuilder keys = new StringBuilder();
keys.append("[static: ");
for (int i = 0; i < staticKeyIndices.length; i++) {
if (i > 0) keys.append(", ");
keys.append("\"").append(strings[staticKeyIndices[i]]).append("\"");
}
keys.append("; computed: ").append(computedKeyCount).append("]");
out.println(tname + " excluding " + keys);
} else {
out.println(
tname
+ " literalId: "
+ (-indexReg - 1)
+ ", computed: "
+ computedKeyCount);
}
icodeLength = pc - old_pc;
break;
}
case Icode_REG_IND_C0:
indexReg = 0;
out.println(tname);
Expand Down Expand Up @@ -1727,6 +1753,7 @@ private abstract static class InstructionClass {
instructionObjs[base + Icode_SCOPE_LOAD] = new DoScopeLoad();
instructionObjs[base + Icode_SCOPE_SAVE] = new DoScopeSave();
instructionObjs[base + Icode_SPREAD] = new DoSpread();
instructionObjs[base + Icode_OBJECT_REST] = new DoObjectRest();
instructionObjs[base + Icode_CLOSURE_EXPR] = new DoClosureExpr();
instructionObjs[base + Icode_METHOD_EXPR] = new DoMethodExpr();
instructionObjs[base + Icode_CLOSURE_STMT] = new DoClosureStatement();
Expand Down Expand Up @@ -4478,6 +4505,46 @@ NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
}
}

private static class DoObjectRest extends InstructionClass {
@Override
NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
// indexReg contains the literalId for static key indices array
int computedKeyCount = frame.idata.itsICode[frame.pc++] & 0xFF;

// Get static key indices from literalIds using indexReg
int[] staticKeyIndices = (int[]) frame.idata.literalIds[state.indexReg];
int staticKeyCount = staticKeyIndices.length;

Object[] excludeKeys = new Object[staticKeyCount + computedKeyCount];

// Fill static keys from the strings table using stored indices
for (int i = 0; i < staticKeyCount; i++) {
excludeKeys[i] = frame.idata.itsStringTable[staticKeyIndices[i]];
}

// Pop computed keys from stack: [source, computed_key_1, ..., computed_key_N]
for (int i = computedKeyCount - 1; i >= 0; i--) {
Object computedKey = frame.stack[state.stackTop];
if (computedKey == DOUBLE_MARK) {
computedKey = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
}
excludeKeys[staticKeyCount + i] = ScriptRuntime.toString(computedKey);
state.stackTop--;
}

Object source = frame.stack[state.stackTop];
if (source == DOUBLE_MARK) {
source = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
}

// Create rest object
Scriptable result = ScriptRuntime.doObjectRest(cx, frame.scope, source, excludeKeys);

frame.stack[state.stackTop] = result;
return null;
}
}

private static class DoObjectLit extends InstructionClass {
@Override
NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
Expand Down
5 changes: 4 additions & 1 deletion rhino/src/main/java/org/mozilla/javascript/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public class Node implements Iterable<Node> {
OPTIONAL_CHAINING = 30,
SUPER_PROPERTY_ACCESS = 31,
NUMBER_OF_SPREAD = 32,
LAST_PROP = NUMBER_OF_SPREAD,
OBJECT_REST_PROP = 33, // marks a CALL node as object rest operation
LAST_PROP = OBJECT_REST_PROP,
FIRST_PROP = FUNCTION_PROP;

// values of ISNUMBER_PROP to specify
Expand Down Expand Up @@ -457,6 +458,8 @@ static String propName(int propType) {
return "super_property_access";
case NUMBER_OF_SPREAD:
return "number_of_spread";
case OBJECT_REST_PROP:
return "object_rest_prop";

default:
Kit.codeBug();
Expand Down
29 changes: 27 additions & 2 deletions rhino/src/main/java/org/mozilla/javascript/NodeTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,19 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco
objectLiteral.addChildToBack(new Node(Token.VOID, Node.newNumber(0.0)));
}
}
current = c.getFirstChild(); // should be a NAME, checked below
// Process all NAME children of the inner LET node (not just the first)
// This includes the main temp variable ($0) and any computed property temps
// ($1, $2, etc.)
for (Node child = c.getFirstChild(); child != null; child = child.getNext()) {
if (child.getType() != Token.NAME) throw Kit.codeBug();
list.add(ScriptRuntime.getIndexObject(child.getString()));
Node init = child.getFirstChild();
if (init == null) {
init = new Node(Token.VOID, Node.newNumber(0.0));
}
objectLiteral.addChildToBack(init);
}
continue; // Already processed all children, move to next sibling of LETEXPR
}
if (current.getType() != Token.NAME) throw Kit.codeBug();
list.add(ScriptRuntime.getIndexObject(current.getString()));
Expand Down Expand Up @@ -500,7 +512,20 @@ protected Node visitLet(boolean createWith, Node parent, Node previous, Node sco
}
// We're removing the LETEXPR, so move the symbols
Scope.joinScopes((Scope) current, (Scope) scopeNode);
current = c.getFirstChild(); // should be a NAME, checked below
// Process all NAME children of the inner LET node (not just the first)
// This includes the main temp variable ($0) and any computed property temps
// ($1, $2, etc.)
for (Node child = c.getFirstChild(); child != null; child = child.getNext()) {
if (child.getType() != Token.NAME) throw Kit.codeBug();
Node stringNode = Node.newString(child.getString());
stringNode.setScope((Scope) scopeNode);
Node init = child.getFirstChild();
if (init == null) {
init = new Node(Token.VOID, Node.newNumber(0.0));
}
newVars.addChildToBack(new Node(Token.SETVAR, stringNode, init));
}
continue; // Already processed all children, move to next sibling of LETEXPR
}
if (current.getType() != Token.NAME) throw Kit.codeBug();
Node stringNode = Node.newString(current.getString());
Expand Down
Loading