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
1,122 changes: 1,046 additions & 76 deletions src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java

Large diffs are not rendered by default.

153 changes: 147 additions & 6 deletions src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,60 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

case Opcodes.STORE_GLOBAL_CODE: {
// Store global code: GlobalVariable.globalCodeRefs.put(name, codeRef)
int nameIdx = bytecode[pc++] & 0xFF;
int codeReg = bytecode[pc++] & 0xFF;
String name = code.stringPool[nameIdx];
RuntimeScalar codeRef = (RuntimeScalar) registers[codeReg];
// Store the code reference in the global namespace
GlobalVariable.globalCodeRefs.put(name, codeRef);
break;
}

case Opcodes.CREATE_CLOSURE: {
// Create closure with captured variables
// Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ...
int rd = bytecode[pc++] & 0xFF;
int templateIdx = bytecode[pc++] & 0xFF;
int numCaptures = bytecode[pc++] & 0xFF;

// Get the template InterpretedCode from constants
InterpretedCode template = (InterpretedCode) code.constants[templateIdx];

// Capture the current register values
RuntimeBase[] capturedVars = new RuntimeBase[numCaptures];
for (int i = 0; i < numCaptures; i++) {
int captureReg = bytecode[pc++] & 0xFF;
capturedVars[i] = registers[captureReg];
}

// Create a new InterpretedCode with the captured variables
InterpretedCode closureCode = new InterpretedCode(
template.bytecode,
template.constants,
template.stringPool,
template.maxRegisters,
capturedVars, // The captured variables!
template.sourceName,
template.sourceLine,
template.pcToTokenIndex
);

// Wrap in RuntimeScalar
registers[rd] = new RuntimeScalar((RuntimeCode) closureCode);
break;
}

case Opcodes.SET_SCALAR: {
// Set scalar value: registers[rd].set(registers[rs])
// Used to set the value in a persistent scalar without overwriting the reference
int rd = bytecode[pc++] & 0xFF;
int rs = bytecode[pc++] & 0xFF;
((RuntimeScalar) registers[rd]).set((RuntimeScalar) registers[rs]);
break;
}

// =================================================================
// ARITHMETIC OPERATORS
// =================================================================
Expand Down Expand Up @@ -313,6 +367,21 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

case Opcodes.REPEAT: {
// String/list repetition: rd = rs1 x rs2
int rd = bytecode[pc++] & 0xFF;
int rs1 = bytecode[pc++] & 0xFF;
int rs2 = bytecode[pc++] & 0xFF;
// Call Operator.repeat(base, count, context)
// Context: 1 = scalar context (for string repetition)
registers[rd] = Operator.repeat(
registers[rs1],
(RuntimeScalar) registers[rs2],
1 // scalar context
);
break;
}

case Opcodes.LENGTH: {
// String length: rd = length(rs)
int rd = bytecode[pc++] & 0xFF;
Expand Down Expand Up @@ -408,6 +477,14 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int rd = bytecode[pc++] & 0xFF;
int arrayReg = bytecode[pc++] & 0xFF;
int indexReg = bytecode[pc++] & 0xFF;

// Check type
if (!(registers[arrayReg] instanceof RuntimeArray)) {
throw new RuntimeException("ARRAY_GET: register " + arrayReg + " contains " +
(registers[arrayReg] == null ? "null" : registers[arrayReg].getClass().getName()) +
" instead of RuntimeArray");
}

RuntimeArray arr = (RuntimeArray) registers[arrayReg];
RuntimeScalar idx = (RuntimeScalar) registers[indexReg];
// Uses RuntimeArray API directly
Expand Down Expand Up @@ -438,11 +515,24 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
}

case Opcodes.ARRAY_SIZE: {
// Array size: rd = scalar(@array)
// Array size: rd = scalar(@array) or scalar(list)
int rd = bytecode[pc++] & 0xFF;
int arrayReg = bytecode[pc++] & 0xFF;
RuntimeArray arr = (RuntimeArray) registers[arrayReg];
registers[rd] = new RuntimeScalar(arr.size());
int operandReg = bytecode[pc++] & 0xFF;
RuntimeBase operand = registers[operandReg];

int size;
if (operand instanceof RuntimeArray) {
size = ((RuntimeArray) operand).size();
} else if (operand instanceof RuntimeList) {
size = ((RuntimeList) operand).size();
} else if (operand instanceof RuntimeScalar) {
// Scalar in array context - treat as 1-element list
size = 1;
} else {
throw new RuntimeException("ARRAY_SIZE: register " + operandReg + " contains unexpected type: " +
(operand == null ? "null" : operand.getClass().getName()));
}
registers[rd] = new RuntimeScalar(size);
break;
}

Expand Down Expand Up @@ -899,8 +989,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int startReg = bytecode[pc++] & 0xFF;
int endReg = bytecode[pc++] & 0xFF;

RuntimeScalar start = (RuntimeScalar) registers[startReg];
RuntimeScalar end = (RuntimeScalar) registers[endReg];
RuntimeBase startBase = registers[startReg];
RuntimeBase endBase = registers[endReg];

// Handle null registers by creating undef scalars
RuntimeScalar start = (startBase instanceof RuntimeScalar) ? (RuntimeScalar) startBase :
(startBase == null) ? new RuntimeScalar() : startBase.scalar();
RuntimeScalar end = (endBase instanceof RuntimeScalar) ? (RuntimeScalar) endBase :
(endBase == null) ? new RuntimeScalar() : endBase.scalar();

PerlRange range = PerlRange.createRange(start, end);
registers[rd] = range;
break;
Expand Down Expand Up @@ -946,6 +1043,50 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

case Opcodes.NEW_ARRAY: {
// Create empty array: rd = new RuntimeArray()
int rd = bytecode[pc++] & 0xFF;
registers[rd] = new RuntimeArray();
break;
}

case Opcodes.NEW_HASH: {
// Create empty hash: rd = new RuntimeHash()
int rd = bytecode[pc++] & 0xFF;
registers[rd] = new RuntimeHash();
break;
}

case Opcodes.ARRAY_SET_FROM_LIST: {
// Set array content from list: array_reg.setFromList(list_reg)
// Format: [ARRAY_SET_FROM_LIST] [array_reg] [list_reg]
int arrayReg = bytecode[pc++] & 0xFF;
int listReg = bytecode[pc++] & 0xFF;

RuntimeArray array = (RuntimeArray) registers[arrayReg];
RuntimeBase listBase = registers[listReg];
RuntimeList list = listBase.getList();

// setFromList clears and repopulates the array
array.setFromList(list);
break;
}

case Opcodes.HASH_SET_FROM_LIST: {
// Set hash content from list: hash_reg = RuntimeHash.createHash(list_reg)
// Format: [HASH_SET_FROM_LIST] [hash_reg] [list_reg]
int hashReg = bytecode[pc++] & 0xFF;
int listReg = bytecode[pc++] & 0xFF;

RuntimeHash existingHash = (RuntimeHash) registers[hashReg];
RuntimeBase listBase = registers[listReg];

// Create new hash from list, then copy elements to existing hash
RuntimeHash newHash = RuntimeHash.createHash(listBase);
existingHash.elements = newHash.elements;
break;
}

// =================================================================
// SLOW OPERATIONS
// =================================================================
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/org/perlonjava/interpreter/InterpretedCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,41 @@ public String disassemble() {
sb.append("MAP r").append(rd).append(" = map(r").append(rs1)
.append(", r").append(rs2).append(", ctx=").append(mapCtx).append(")\n");
break;
case Opcodes.NEW_ARRAY:
rd = bytecode[pc++] & 0xFF;
sb.append("NEW_ARRAY r").append(rd).append(" = new RuntimeArray()\n");
break;
case Opcodes.NEW_HASH:
rd = bytecode[pc++] & 0xFF;
sb.append("NEW_HASH r").append(rd).append(" = new RuntimeHash()\n");
break;
case Opcodes.ARRAY_SET_FROM_LIST:
rs1 = bytecode[pc++] & 0xFF; // array register
rs2 = bytecode[pc++] & 0xFF; // list register
sb.append("ARRAY_SET_FROM_LIST r").append(rs1).append(".setFromList(r").append(rs2).append(")\n");
break;
case Opcodes.HASH_SET_FROM_LIST:
rs1 = bytecode[pc++] & 0xFF; // hash register
rs2 = bytecode[pc++] & 0xFF; // list register
sb.append("HASH_SET_FROM_LIST r").append(rs1).append(".setFromList(r").append(rs2).append(")\n");
break;
case Opcodes.STORE_GLOBAL_CODE:
int codeNameIdx = bytecode[pc++] & 0xFF;
rs = bytecode[pc++] & 0xFF;
sb.append("STORE_GLOBAL_CODE '").append(stringPool[codeNameIdx]).append("' = r").append(rs).append("\n");
break;
case Opcodes.CREATE_CLOSURE:
rd = bytecode[pc++] & 0xFF;
int templateIdx = bytecode[pc++] & 0xFF;
int numCaptures = bytecode[pc++] & 0xFF;
sb.append("CREATE_CLOSURE r").append(rd).append(" = closure(template[").append(templateIdx).append("], captures=[");
for (int i = 0; i < numCaptures; i++) {
if (i > 0) sb.append(", ");
int captureReg = bytecode[pc++] & 0xFF;
sb.append("r").append(captureReg);
}
sb.append("])\n");
break;
case Opcodes.NOT:
rd = bytecode[pc++] & 0xFF;
rs = bytecode[pc++] & 0xFF;
Expand Down Expand Up @@ -506,6 +541,24 @@ public String disassemble() {
String globName = stringPool[globNameIdx];
sb.append(" r").append(rd).append(" = *").append(globName);
break;
case Opcodes.SLOWOP_RETRIEVE_BEGIN_SCALAR:
case Opcodes.SLOWOP_RETRIEVE_BEGIN_ARRAY:
case Opcodes.SLOWOP_RETRIEVE_BEGIN_HASH:
// Format: [rd] [name_idx] [begin_id]
rd = bytecode[pc++] & 0xFF;
int varNameIdx = bytecode[pc++] & 0xFF;
int beginId = bytecode[pc++] & 0xFF;
String varName = stringPool[varNameIdx];
sb.append(" r").append(rd).append(" = ").append(varName)
.append(" (BEGIN_").append(beginId).append(")");
break;
case Opcodes.SLOWOP_LOCAL_SCALAR:
// Format: [rd] [name_idx]
rd = bytecode[pc++] & 0xFF;
int localNameIdx = bytecode[pc++] & 0xFF;
String localVarName = stringPool[localNameIdx];
sb.append(" r").append(rd).append(" = local ").append(localVarName);
break;
default:
sb.append(" (operands not decoded)");
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static RuntimeList runCode(String perlCode, String sourceName, int source
Node ast = parser.parse();

// Step 4: Compile AST to interpreter bytecode
BytecodeCompiler compiler = new BytecodeCompiler(sourceName, sourceLine);
BytecodeCompiler compiler = new BytecodeCompiler(sourceName, sourceLine, errorUtil);
InterpretedCode code = compiler.compile(ast);

// Step 5: Execute via apply() (just like compiled code)
Expand Down
44 changes: 43 additions & 1 deletion src/main/java/org/perlonjava/interpreter/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Bytecode opcodes for the PerlOnJava interpreter.
*
* Design: Pure register machine with 3-address code format.
* DENSE opcodes (0-92, NO GAPS) enable JVM tableswitch optimization.
* DENSE opcodes (0-96, NO GAPS) enable JVM tableswitch optimization.
*
* Register architecture is REQUIRED for control flow correctness:
* Perl's GOTO/last/next/redo would corrupt a stack-based architecture.
Expand Down Expand Up @@ -413,6 +413,30 @@ public class Opcodes {
/** Map operator: rd = ListOperators.map(list_reg, closure_reg, context) */
public static final byte MAP = 92;

/** Create empty array: rd = new RuntimeArray() */
public static final byte NEW_ARRAY = 93;

/** Create empty hash: rd = new RuntimeHash() */
public static final byte NEW_HASH = 94;

/** Set array from list: array_reg.setFromList(list_reg) */
public static final byte ARRAY_SET_FROM_LIST = 95;

/** Set hash from list: hash_reg = RuntimeHash.createHash(list_reg) then copy elements */
public static final byte HASH_SET_FROM_LIST = 96;

/** Store global code: GlobalVariable.getGlobalCodeRef().put(stringPool[nameIdx], codeRef) */
public static final byte STORE_GLOBAL_CODE = 97;

/** Create closure with captured variables: rd = createClosure(template, registers[rs1], registers[rs2], ...)
* Format: CREATE_CLOSURE rd template_const_idx num_captures reg1 reg2 ... */
public static final byte CREATE_CLOSURE = 98;

/** Set scalar value: ((RuntimeScalar)registers[rd]).set((RuntimeScalar)registers[rs])
* Format: SET_SCALAR rd rs
* Used to set the value in a persistent scalar without overwriting the reference */
public static final byte SET_SCALAR = 99;

// =================================================================
// Slow Operation IDs (0-255)
// =================================================================
Expand Down Expand Up @@ -485,6 +509,24 @@ public class Opcodes {
/** Slow op ID: rd = getGlobalIO(name) - load glob/filehandle from global variables */
public static final int SLOWOP_LOAD_GLOB = 21;

/** Slow op ID: rd = Time.sleep(seconds) - sleep for specified seconds */
public static final int SLOWOP_SLEEP = 22;

/** Slow op ID: rd = deref_array(scalar_ref) - dereference array reference for multidimensional access */
public static final int SLOWOP_DEREF_ARRAY = 23;

/** Slow op ID: rd = PersistentVariable.retrieveBeginScalar(var_name, begin_id) - retrieve BEGIN scalar */
public static final int SLOWOP_RETRIEVE_BEGIN_SCALAR = 24;

/** Slow op ID: rd = PersistentVariable.retrieveBeginArray(var_name, begin_id) - retrieve BEGIN array */
public static final int SLOWOP_RETRIEVE_BEGIN_ARRAY = 25;

/** Slow op ID: rd = PersistentVariable.retrieveBeginHash(var_name, begin_id) - retrieve BEGIN hash */
public static final int SLOWOP_RETRIEVE_BEGIN_HASH = 26;

/** Slow op ID: rd = GlobalRuntimeScalar.makeLocal(var_name) - temporarily localize global variable */
public static final int SLOWOP_LOCAL_SCALAR = 27;

// =================================================================
// OPCODES 93-255: RESERVED FOR FUTURE FAST OPERATIONS
// =================================================================
Expand Down
Loading