Skip to content
Merged
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
441 changes: 377 additions & 64 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1382,17 +1382,21 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

case Opcodes.CREATE_REF: {
// Create reference: rd = rs.createReference()
// For multi-element lists, create references to each element
// For lists, create a list of references to each element
int rd = bytecode[pc++];
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];

// Special handling for RuntimeList
if (value instanceof RuntimeList list && list.elements.size() != 1) {
// Multi-element or empty list: create list of references
registers[rd] = list.createListReference();
if (value == null) {
// Null value - return undef
registers[rd] = RuntimeScalarCache.scalarUndef;
} else if (value instanceof RuntimeList list) {
if (list.size() == 1) {
registers[rd] = list.getFirst().createReference();
} else {
registers[rd] = list.createListReference();
}
} else {
// Single value or single-element list: create single reference
registers[rd] = value.createReference();
}
break;
Expand Down Expand Up @@ -1755,6 +1759,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.HASH_SLICE:
case Opcodes.HASH_SLICE_SET:
case Opcodes.HASH_SLICE_DELETE:
case Opcodes.HASH_KEYVALUE_SLICE:
case Opcodes.LIST_SLICE_FROM:
pc = executeSliceOps(opcode, bytecode, pc, registers, code);
break;
Expand Down Expand Up @@ -2281,13 +2286,17 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
int rd = bytecode[pc++];
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];

// Special handling for RuntimeList
if (value instanceof RuntimeList list && list.elements.size() != 1) {
// Multi-element or empty list: create list of references
registers[rd] = list.createListReference();
if (value instanceof RuntimeList list) {
if (list.size() == 1) {
registers[rd] = list.getFirst().createReference();
} else {
RuntimeList refs = new RuntimeList();
for (RuntimeScalar element : list) {
refs.add(element.createReference());
}
registers[rd] = refs;
}
} else {
// Single value or single-element list: create single reference
registers[rd] = value.createReference();
}
return pc;
Expand Down Expand Up @@ -3061,6 +3070,8 @@ private static int executeSliceOps(int opcode, int[] bytecode, int pc,
return SlowOpcodeHandler.executeHashSliceSet(bytecode, pc, registers);
case Opcodes.HASH_SLICE_DELETE:
return SlowOpcodeHandler.executeHashSliceDelete(bytecode, pc, registers);
case Opcodes.HASH_KEYVALUE_SLICE:
return SlowOpcodeHandler.executeHashKeyValueSlice(bytecode, pc, registers);
case Opcodes.LIST_SLICE_FROM:
return SlowOpcodeHandler.executeListSliceFrom(bytecode, pc, registers);
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,18 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("*")) {
// Glob assignment where the glob comes from an expression, e.g. $ref->** = ...
// or 'name'->** = ...
// Compile the glob expression to obtain the RuntimeGlob, then store through it.
leftOp.accept(bytecodeCompiler);
int globReg = bytecodeCompiler.lastResultReg;

bytecodeCompiler.emit(Opcodes.STORE_GLOB);
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("pos")) {
// pos($var) = value - lvalue assignment to regex position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ else if (node.right instanceof BinaryOperatorNode) {
return;
}

// Index/value slice: %array[indices] (and postfix ->%[...] parses into this form)
if (leftOp.operator.equals("%")) {
bytecodeCompiler.handleArrayKeyValueSlice(node, leftOp);
return;
}

// Handle normal array element access: $array[index]
if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) {
bytecodeCompiler.handleArrayElementAccess(node, leftOp);
Expand Down Expand Up @@ -333,6 +339,12 @@ else if (node.right instanceof BinaryOperatorNode) {
return;
}

// Key/value slice: %hash{keys} (and postfix ->%{...} parses into this form)
if (leftOp.operator.equals("%")) {
bytecodeCompiler.handleHashKeyValueSlice(node, leftOp);
return;
}

// Handle normal hash element access: $hash{key}
if (leftOp.operator.equals("$") && leftOp.operand instanceof IdentifierNode) {
bytecodeCompiler.handleHashElementAccess(node, leftOp);
Expand Down
78 changes: 68 additions & 10 deletions src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ public String disassemble() {
while (pc < bytecode.length) {
int startPc = pc;
int opcode = bytecode[pc++];
int rs1;
int rs2;
sb.append(String.format("%4d: ", startPc));

switch (opcode) {
Expand Down Expand Up @@ -354,43 +356,94 @@ public String disassemble() {
case Opcodes.LOAD_GLOBAL_SCALAR:
rd = bytecode[pc++];
int nameIdx = bytecode[pc++];
sb.append("LOAD_GLOBAL_SCALAR r").append(rd).append(" = $").append(stringPool[nameIdx]).append("\n");
sb.append("LOAD_GLOBAL_SCALAR r").append(rd).append(" = $");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append("\n");
break;
case Opcodes.LOAD_GLOBAL_ARRAY:
rd = bytecode[pc++];
nameIdx = bytecode[pc++];
sb.append("LOAD_GLOBAL_ARRAY r").append(rd).append(" = @").append(stringPool[nameIdx]).append("\n");
sb.append("LOAD_GLOBAL_ARRAY r").append(rd).append(" = @");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append("\n");
break;
case Opcodes.STORE_GLOBAL_ARRAY:
nameIdx = bytecode[pc++];
int storeArraySrcReg = bytecode[pc++];
sb.append("STORE_GLOBAL_ARRAY @").append(stringPool[nameIdx]).append(" = r").append(storeArraySrcReg).append("\n");
sb.append("STORE_GLOBAL_ARRAY @");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append(" = r").append(storeArraySrcReg).append("\n");
break;
case Opcodes.LOAD_GLOBAL_HASH:
rd = bytecode[pc++];
nameIdx = bytecode[pc++];
sb.append("LOAD_GLOBAL_HASH r").append(rd).append(" = %").append(stringPool[nameIdx]).append("\n");
sb.append("LOAD_GLOBAL_HASH r").append(rd).append(" = %");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append("\n");
break;
case Opcodes.STORE_GLOBAL_HASH:
nameIdx = bytecode[pc++];
int storeHashSrcReg = bytecode[pc++];
sb.append("STORE_GLOBAL_HASH %").append(stringPool[nameIdx]).append(" = r").append(storeHashSrcReg).append("\n");
sb.append("STORE_GLOBAL_HASH %");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append(" = r").append(storeHashSrcReg).append("\n");
break;
case Opcodes.LOAD_GLOBAL_CODE:
rd = bytecode[pc++];
nameIdx = bytecode[pc++];
sb.append("LOAD_GLOBAL_CODE r").append(rd).append(" = &").append(stringPool[nameIdx]).append("\n");
sb.append("LOAD_GLOBAL_CODE r").append(rd).append(" = &");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append("\n");
break;
case Opcodes.STORE_GLOBAL_SCALAR:
nameIdx = bytecode[pc++];
int srcReg = bytecode[pc++];
sb.append("STORE_GLOBAL_SCALAR $").append(stringPool[nameIdx]).append(" = r").append(srcReg).append("\n");
sb.append("STORE_GLOBAL_SCALAR $");
if (stringPool != null && nameIdx >= 0 && nameIdx < stringPool.length) {
sb.append(stringPool[nameIdx]);
} else {
sb.append("<bad_string_idx:").append(nameIdx).append(">");
}
sb.append(" = r").append(srcReg).append("\n");
break;
case Opcodes.HASH_KEYVALUE_SLICE: {
rd = bytecode[pc++];
int kvRs1 = bytecode[pc++]; // hash register
int kvRs2 = bytecode[pc++]; // keys list register
sb.append("HASH_KEYVALUE_SLICE r").append(rd)
.append(" = r").append(kvRs1)
.append(".getKeyValueSlice(r").append(kvRs2).append(")\n");
break;
}
case Opcodes.ADD_SCALAR:
rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
sb.append("ADD_SCALAR r").append(rd).append(" = r").append(rs1).append(" + r").append(rs2).append("\n");
int addRs1 = bytecode[pc++];
int addRs2 = bytecode[pc++];
sb.append("ADD_SCALAR r").append(rd).append(" = r").append(addRs1).append(" + r").append(addRs2).append("\n");
break;
case Opcodes.SUB_SCALAR:
rd = bytecode[pc++];
Expand Down Expand Up @@ -1287,6 +1340,11 @@ public String disassemble() {
rs = bytecode[pc++];
sb.append("DEREF_ARRAY r").append(rd).append(" = @{r").append(rs).append("}\n");
break;
case Opcodes.DEREF_HASH:
rd = bytecode[pc++];
rs = bytecode[pc++];
sb.append("DEREF_HASH r").append(rd).append(" = %{r").append(rs).append("}\n");
break;
case Opcodes.RETRIEVE_BEGIN_SCALAR:
rd = bytecode[pc++];
nameIdx = bytecode[pc++];
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1131,5 +1131,10 @@ public class Opcodes {
* Format: DO_FILE rd fileReg ctx */
public static final short DO_FILE = 340;

/** Hash key/value slice: rd = hash.getKeyValueSlice(keys_list)
* Perl: %hash{keys} returns alternating key/value pairs.
* Format: HASH_KEYVALUE_SLICE rd hashReg keysListReg */
public static final short HASH_KEYVALUE_SLICE = 344;

private Opcodes() {} // Utility class - no instantiation
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,27 @@ public static int executeGetppid(int[] bytecode, int pc, RuntimeBase[] registers
return pc;
}

/**
* HASH_KEYVALUE_SLICE: rd = hash.getKeyValueSlice(keys_list)
* Format: [HASH_KEYVALUE_SLICE] [rd] [hashReg] [keysListReg]
* Effect: Returns alternating key/value pairs for the given keys.
*/
public static int executeHashKeyValueSlice(
int[] bytecode,
int pc,
RuntimeBase[] registers) {

int rd = bytecode[pc++];
int hashReg = bytecode[pc++];
int keysListReg = bytecode[pc++];

RuntimeHash hash = (RuntimeHash) registers[hashReg];
RuntimeList keysList = (RuntimeList) registers[keysListReg];

registers[rd] = hash.getKeyValueSlice(keysList);
return pc;
}

/**
* SLOW_FORK: rd = fork()
* Format: [SLOW_FORK] [rd]
Expand Down Expand Up @@ -354,7 +375,7 @@ public static int executeDerefScalarStrict(

int rd = bytecode[pc++];
int rs = bytecode[pc++];
registers[rd] = ((RuntimeScalar) registers[rs]).scalarDeref();
registers[rd] = registers[rs].scalar().scalarDeref();
return pc;
}

Expand All @@ -373,7 +394,7 @@ public static int executeDerefScalarNonStrict(
int rs = bytecode[pc++];
int pkgIdx = bytecode[pc++];
String pkg = code.stringPool[pkgIdx];
registers[rd] = ((RuntimeScalar) registers[rs]).scalarDerefNonStrict(pkg);
registers[rd] = registers[rs].scalar().scalarDerefNonStrict(pkg);
return pc;
}

Expand All @@ -392,20 +413,9 @@ public static int executeDerefGlob(
int rs = bytecode[pc++];
int nameIdx = bytecode[pc++]; // currentPackage (unused at runtime, consumed for alignment)

RuntimeBase val = registers[rs];

// PVIO case: *STDOUT{IO} returns RuntimeIO directly — wrap in a temporary glob
if (val instanceof RuntimeIO io) {
RuntimeGlob tmp = new RuntimeGlob("__ANON__");
tmp.setIO(io);
registers[rd] = tmp;
return pc;
}

// General case: use globDeref() — throws "Not a GLOB reference" for invalid refs.
// Matches JVM path (EmitVariable.java: globDeref()).
RuntimeScalar scalar = (RuntimeScalar) val;
registers[rd] = scalar.globDeref();
// Delegate to RuntimeScalar.globDeref() (after scalar context coercion).
// This centralizes PVIO / GLOBREFERENCE handling and matches the JVM path.
registers[rd] = registers[rs].scalar().globDeref();
return pc;
}

Expand Down
Loading