Skip to content

Commit 2a4a9a2

Browse files
authored
Merge pull request #191 from fglock/feature/interpreter-phase2-control-flow
Implement variable sharing between interpreter and compiled code
2 parents 2a5acee + ea0de2b commit 2a4a9a2

9 files changed

Lines changed: 1650 additions & 86 deletions

File tree

src/main/java/org/perlonjava/interpreter/BytecodeCompiler.java

Lines changed: 1046 additions & 76 deletions
Large diffs are not rendered by default.

src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,60 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
221221
break;
222222
}
223223

224+
case Opcodes.STORE_GLOBAL_CODE: {
225+
// Store global code: GlobalVariable.globalCodeRefs.put(name, codeRef)
226+
int nameIdx = bytecode[pc++] & 0xFF;
227+
int codeReg = bytecode[pc++] & 0xFF;
228+
String name = code.stringPool[nameIdx];
229+
RuntimeScalar codeRef = (RuntimeScalar) registers[codeReg];
230+
// Store the code reference in the global namespace
231+
GlobalVariable.globalCodeRefs.put(name, codeRef);
232+
break;
233+
}
234+
235+
case Opcodes.CREATE_CLOSURE: {
236+
// Create closure with captured variables
237+
// Format: CREATE_CLOSURE rd template_idx num_captures reg1 reg2 ...
238+
int rd = bytecode[pc++] & 0xFF;
239+
int templateIdx = bytecode[pc++] & 0xFF;
240+
int numCaptures = bytecode[pc++] & 0xFF;
241+
242+
// Get the template InterpretedCode from constants
243+
InterpretedCode template = (InterpretedCode) code.constants[templateIdx];
244+
245+
// Capture the current register values
246+
RuntimeBase[] capturedVars = new RuntimeBase[numCaptures];
247+
for (int i = 0; i < numCaptures; i++) {
248+
int captureReg = bytecode[pc++] & 0xFF;
249+
capturedVars[i] = registers[captureReg];
250+
}
251+
252+
// Create a new InterpretedCode with the captured variables
253+
InterpretedCode closureCode = new InterpretedCode(
254+
template.bytecode,
255+
template.constants,
256+
template.stringPool,
257+
template.maxRegisters,
258+
capturedVars, // The captured variables!
259+
template.sourceName,
260+
template.sourceLine,
261+
template.pcToTokenIndex
262+
);
263+
264+
// Wrap in RuntimeScalar
265+
registers[rd] = new RuntimeScalar((RuntimeCode) closureCode);
266+
break;
267+
}
268+
269+
case Opcodes.SET_SCALAR: {
270+
// Set scalar value: registers[rd].set(registers[rs])
271+
// Used to set the value in a persistent scalar without overwriting the reference
272+
int rd = bytecode[pc++] & 0xFF;
273+
int rs = bytecode[pc++] & 0xFF;
274+
((RuntimeScalar) registers[rd]).set((RuntimeScalar) registers[rs]);
275+
break;
276+
}
277+
224278
// =================================================================
225279
// ARITHMETIC OPERATORS
226280
// =================================================================
@@ -313,6 +367,21 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
313367
break;
314368
}
315369

370+
case Opcodes.REPEAT: {
371+
// String/list repetition: rd = rs1 x rs2
372+
int rd = bytecode[pc++] & 0xFF;
373+
int rs1 = bytecode[pc++] & 0xFF;
374+
int rs2 = bytecode[pc++] & 0xFF;
375+
// Call Operator.repeat(base, count, context)
376+
// Context: 1 = scalar context (for string repetition)
377+
registers[rd] = Operator.repeat(
378+
registers[rs1],
379+
(RuntimeScalar) registers[rs2],
380+
1 // scalar context
381+
);
382+
break;
383+
}
384+
316385
case Opcodes.LENGTH: {
317386
// String length: rd = length(rs)
318387
int rd = bytecode[pc++] & 0xFF;
@@ -408,6 +477,14 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
408477
int rd = bytecode[pc++] & 0xFF;
409478
int arrayReg = bytecode[pc++] & 0xFF;
410479
int indexReg = bytecode[pc++] & 0xFF;
480+
481+
// Check type
482+
if (!(registers[arrayReg] instanceof RuntimeArray)) {
483+
throw new RuntimeException("ARRAY_GET: register " + arrayReg + " contains " +
484+
(registers[arrayReg] == null ? "null" : registers[arrayReg].getClass().getName()) +
485+
" instead of RuntimeArray");
486+
}
487+
411488
RuntimeArray arr = (RuntimeArray) registers[arrayReg];
412489
RuntimeScalar idx = (RuntimeScalar) registers[indexReg];
413490
// Uses RuntimeArray API directly
@@ -438,11 +515,24 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
438515
}
439516

440517
case Opcodes.ARRAY_SIZE: {
441-
// Array size: rd = scalar(@array)
518+
// Array size: rd = scalar(@array) or scalar(list)
442519
int rd = bytecode[pc++] & 0xFF;
443-
int arrayReg = bytecode[pc++] & 0xFF;
444-
RuntimeArray arr = (RuntimeArray) registers[arrayReg];
445-
registers[rd] = new RuntimeScalar(arr.size());
520+
int operandReg = bytecode[pc++] & 0xFF;
521+
RuntimeBase operand = registers[operandReg];
522+
523+
int size;
524+
if (operand instanceof RuntimeArray) {
525+
size = ((RuntimeArray) operand).size();
526+
} else if (operand instanceof RuntimeList) {
527+
size = ((RuntimeList) operand).size();
528+
} else if (operand instanceof RuntimeScalar) {
529+
// Scalar in array context - treat as 1-element list
530+
size = 1;
531+
} else {
532+
throw new RuntimeException("ARRAY_SIZE: register " + operandReg + " contains unexpected type: " +
533+
(operand == null ? "null" : operand.getClass().getName()));
534+
}
535+
registers[rd] = new RuntimeScalar(size);
446536
break;
447537
}
448538

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

902-
RuntimeScalar start = (RuntimeScalar) registers[startReg];
903-
RuntimeScalar end = (RuntimeScalar) registers[endReg];
992+
RuntimeBase startBase = registers[startReg];
993+
RuntimeBase endBase = registers[endReg];
994+
995+
// Handle null registers by creating undef scalars
996+
RuntimeScalar start = (startBase instanceof RuntimeScalar) ? (RuntimeScalar) startBase :
997+
(startBase == null) ? new RuntimeScalar() : startBase.scalar();
998+
RuntimeScalar end = (endBase instanceof RuntimeScalar) ? (RuntimeScalar) endBase :
999+
(endBase == null) ? new RuntimeScalar() : endBase.scalar();
1000+
9041001
PerlRange range = PerlRange.createRange(start, end);
9051002
registers[rd] = range;
9061003
break;
@@ -946,6 +1043,50 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
9461043
break;
9471044
}
9481045

1046+
case Opcodes.NEW_ARRAY: {
1047+
// Create empty array: rd = new RuntimeArray()
1048+
int rd = bytecode[pc++] & 0xFF;
1049+
registers[rd] = new RuntimeArray();
1050+
break;
1051+
}
1052+
1053+
case Opcodes.NEW_HASH: {
1054+
// Create empty hash: rd = new RuntimeHash()
1055+
int rd = bytecode[pc++] & 0xFF;
1056+
registers[rd] = new RuntimeHash();
1057+
break;
1058+
}
1059+
1060+
case Opcodes.ARRAY_SET_FROM_LIST: {
1061+
// Set array content from list: array_reg.setFromList(list_reg)
1062+
// Format: [ARRAY_SET_FROM_LIST] [array_reg] [list_reg]
1063+
int arrayReg = bytecode[pc++] & 0xFF;
1064+
int listReg = bytecode[pc++] & 0xFF;
1065+
1066+
RuntimeArray array = (RuntimeArray) registers[arrayReg];
1067+
RuntimeBase listBase = registers[listReg];
1068+
RuntimeList list = listBase.getList();
1069+
1070+
// setFromList clears and repopulates the array
1071+
array.setFromList(list);
1072+
break;
1073+
}
1074+
1075+
case Opcodes.HASH_SET_FROM_LIST: {
1076+
// Set hash content from list: hash_reg = RuntimeHash.createHash(list_reg)
1077+
// Format: [HASH_SET_FROM_LIST] [hash_reg] [list_reg]
1078+
int hashReg = bytecode[pc++] & 0xFF;
1079+
int listReg = bytecode[pc++] & 0xFF;
1080+
1081+
RuntimeHash existingHash = (RuntimeHash) registers[hashReg];
1082+
RuntimeBase listBase = registers[listReg];
1083+
1084+
// Create new hash from list, then copy elements to existing hash
1085+
RuntimeHash newHash = RuntimeHash.createHash(listBase);
1086+
existingHash.elements = newHash.elements;
1087+
break;
1088+
}
1089+
9491090
// =================================================================
9501091
// SLOW OPERATIONS
9511092
// =================================================================

src/main/java/org/perlonjava/interpreter/InterpretedCode.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,41 @@ public String disassemble() {
475475
sb.append("MAP r").append(rd).append(" = map(r").append(rs1)
476476
.append(", r").append(rs2).append(", ctx=").append(mapCtx).append(")\n");
477477
break;
478+
case Opcodes.NEW_ARRAY:
479+
rd = bytecode[pc++] & 0xFF;
480+
sb.append("NEW_ARRAY r").append(rd).append(" = new RuntimeArray()\n");
481+
break;
482+
case Opcodes.NEW_HASH:
483+
rd = bytecode[pc++] & 0xFF;
484+
sb.append("NEW_HASH r").append(rd).append(" = new RuntimeHash()\n");
485+
break;
486+
case Opcodes.ARRAY_SET_FROM_LIST:
487+
rs1 = bytecode[pc++] & 0xFF; // array register
488+
rs2 = bytecode[pc++] & 0xFF; // list register
489+
sb.append("ARRAY_SET_FROM_LIST r").append(rs1).append(".setFromList(r").append(rs2).append(")\n");
490+
break;
491+
case Opcodes.HASH_SET_FROM_LIST:
492+
rs1 = bytecode[pc++] & 0xFF; // hash register
493+
rs2 = bytecode[pc++] & 0xFF; // list register
494+
sb.append("HASH_SET_FROM_LIST r").append(rs1).append(".setFromList(r").append(rs2).append(")\n");
495+
break;
496+
case Opcodes.STORE_GLOBAL_CODE:
497+
int codeNameIdx = bytecode[pc++] & 0xFF;
498+
rs = bytecode[pc++] & 0xFF;
499+
sb.append("STORE_GLOBAL_CODE '").append(stringPool[codeNameIdx]).append("' = r").append(rs).append("\n");
500+
break;
501+
case Opcodes.CREATE_CLOSURE:
502+
rd = bytecode[pc++] & 0xFF;
503+
int templateIdx = bytecode[pc++] & 0xFF;
504+
int numCaptures = bytecode[pc++] & 0xFF;
505+
sb.append("CREATE_CLOSURE r").append(rd).append(" = closure(template[").append(templateIdx).append("], captures=[");
506+
for (int i = 0; i < numCaptures; i++) {
507+
if (i > 0) sb.append(", ");
508+
int captureReg = bytecode[pc++] & 0xFF;
509+
sb.append("r").append(captureReg);
510+
}
511+
sb.append("])\n");
512+
break;
478513
case Opcodes.NOT:
479514
rd = bytecode[pc++] & 0xFF;
480515
rs = bytecode[pc++] & 0xFF;
@@ -506,6 +541,24 @@ public String disassemble() {
506541
String globName = stringPool[globNameIdx];
507542
sb.append(" r").append(rd).append(" = *").append(globName);
508543
break;
544+
case Opcodes.SLOWOP_RETRIEVE_BEGIN_SCALAR:
545+
case Opcodes.SLOWOP_RETRIEVE_BEGIN_ARRAY:
546+
case Opcodes.SLOWOP_RETRIEVE_BEGIN_HASH:
547+
// Format: [rd] [name_idx] [begin_id]
548+
rd = bytecode[pc++] & 0xFF;
549+
int varNameIdx = bytecode[pc++] & 0xFF;
550+
int beginId = bytecode[pc++] & 0xFF;
551+
String varName = stringPool[varNameIdx];
552+
sb.append(" r").append(rd).append(" = ").append(varName)
553+
.append(" (BEGIN_").append(beginId).append(")");
554+
break;
555+
case Opcodes.SLOWOP_LOCAL_SCALAR:
556+
// Format: [rd] [name_idx]
557+
rd = bytecode[pc++] & 0xFF;
558+
int localNameIdx = bytecode[pc++] & 0xFF;
559+
String localVarName = stringPool[localNameIdx];
560+
sb.append(" r").append(rd).append(" = local ").append(localVarName);
561+
break;
509562
default:
510563
sb.append(" (operands not decoded)");
511564
break;

src/main/java/org/perlonjava/interpreter/InterpreterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static RuntimeList runCode(String perlCode, String sourceName, int source
5555
Node ast = parser.parse();
5656

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

6161
// Step 5: Execute via apply() (just like compiled code)

src/main/java/org/perlonjava/interpreter/Opcodes.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Bytecode opcodes for the PerlOnJava interpreter.
55
*
66
* Design: Pure register machine with 3-address code format.
7-
* DENSE opcodes (0-92, NO GAPS) enable JVM tableswitch optimization.
7+
* DENSE opcodes (0-96, NO GAPS) enable JVM tableswitch optimization.
88
*
99
* Register architecture is REQUIRED for control flow correctness:
1010
* Perl's GOTO/last/next/redo would corrupt a stack-based architecture.
@@ -413,6 +413,30 @@ public class Opcodes {
413413
/** Map operator: rd = ListOperators.map(list_reg, closure_reg, context) */
414414
public static final byte MAP = 92;
415415

416+
/** Create empty array: rd = new RuntimeArray() */
417+
public static final byte NEW_ARRAY = 93;
418+
419+
/** Create empty hash: rd = new RuntimeHash() */
420+
public static final byte NEW_HASH = 94;
421+
422+
/** Set array from list: array_reg.setFromList(list_reg) */
423+
public static final byte ARRAY_SET_FROM_LIST = 95;
424+
425+
/** Set hash from list: hash_reg = RuntimeHash.createHash(list_reg) then copy elements */
426+
public static final byte HASH_SET_FROM_LIST = 96;
427+
428+
/** Store global code: GlobalVariable.getGlobalCodeRef().put(stringPool[nameIdx], codeRef) */
429+
public static final byte STORE_GLOBAL_CODE = 97;
430+
431+
/** Create closure with captured variables: rd = createClosure(template, registers[rs1], registers[rs2], ...)
432+
* Format: CREATE_CLOSURE rd template_const_idx num_captures reg1 reg2 ... */
433+
public static final byte CREATE_CLOSURE = 98;
434+
435+
/** Set scalar value: ((RuntimeScalar)registers[rd]).set((RuntimeScalar)registers[rs])
436+
* Format: SET_SCALAR rd rs
437+
* Used to set the value in a persistent scalar without overwriting the reference */
438+
public static final byte SET_SCALAR = 99;
439+
416440
// =================================================================
417441
// Slow Operation IDs (0-255)
418442
// =================================================================
@@ -485,6 +509,24 @@ public class Opcodes {
485509
/** Slow op ID: rd = getGlobalIO(name) - load glob/filehandle from global variables */
486510
public static final int SLOWOP_LOAD_GLOB = 21;
487511

512+
/** Slow op ID: rd = Time.sleep(seconds) - sleep for specified seconds */
513+
public static final int SLOWOP_SLEEP = 22;
514+
515+
/** Slow op ID: rd = deref_array(scalar_ref) - dereference array reference for multidimensional access */
516+
public static final int SLOWOP_DEREF_ARRAY = 23;
517+
518+
/** Slow op ID: rd = PersistentVariable.retrieveBeginScalar(var_name, begin_id) - retrieve BEGIN scalar */
519+
public static final int SLOWOP_RETRIEVE_BEGIN_SCALAR = 24;
520+
521+
/** Slow op ID: rd = PersistentVariable.retrieveBeginArray(var_name, begin_id) - retrieve BEGIN array */
522+
public static final int SLOWOP_RETRIEVE_BEGIN_ARRAY = 25;
523+
524+
/** Slow op ID: rd = PersistentVariable.retrieveBeginHash(var_name, begin_id) - retrieve BEGIN hash */
525+
public static final int SLOWOP_RETRIEVE_BEGIN_HASH = 26;
526+
527+
/** Slow op ID: rd = GlobalRuntimeScalar.makeLocal(var_name) - temporarily localize global variable */
528+
public static final int SLOWOP_LOCAL_SCALAR = 27;
529+
488530
// =================================================================
489531
// OPCODES 93-255: RESERVED FOR FUTURE FAST OPERATIONS
490532
// =================================================================

0 commit comments

Comments
 (0)