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
2 changes: 1 addition & 1 deletion src/main/java/org/perlonjava/app/cli/CompilerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
*/
public class CompilerOptions implements Cloneable {
public boolean debugEnabled = false;
public boolean disassembleEnabled = System.getenv("JPERL_DISASSEMBLE") != null;
public boolean disassembleEnabled = false;
public boolean useInterpreter = false;
public boolean tokenizeOnly = false;
public boolean parseOnly = false;
Expand Down
323 changes: 103 additions & 220 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java

Large diffs are not rendered by default.

126 changes: 46 additions & 80 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1376,27 +1376,22 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

case Opcodes.DEREF: {
// Dereference: rd = $$rs (scalar reference dereference)
// Always call scalarDeref() — throws "Not a SCALAR reference" for
// non-reference types (IO, FORMAT, etc.), matching Perl semantics.
// Can receive RuntimeScalar or RuntimeList
int rd = bytecode[pc++];
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];

// Only dereference if it's a RuntimeScalar with REFERENCE type
if (value instanceof RuntimeScalar) {
RuntimeScalar sv = (RuntimeScalar) value;
// Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.)
// Pass through non-scalar reference types (array/hash/code/regex refs) —
// those are handled by the JVM compiler as non-scalar refs and should not
// throw here (decl-refs.t uses $$arrayref in no-strict context).
if (sv.type == RuntimeScalarType.ARRAYREFERENCE
|| sv.type == RuntimeScalarType.HASHREFERENCE
|| sv.type == RuntimeScalarType.CODE
|| sv.type == RuntimeScalarType.REGEX) {
registers[rd] = sv; // pass through non-scalar refs
RuntimeScalar scalar = (RuntimeScalar) value;
if (scalar.type == RuntimeScalarType.REFERENCE) {
registers[rd] = scalar.scalarDeref();
} else {
registers[rd] = sv.scalarDeref();
// Non-reference scalar, just copy
registers[rd] = value;
}
} else {
// RuntimeList or other types, pass through
registers[rd] = value;
}
break;
Expand Down Expand Up @@ -1774,16 +1769,12 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
pc = executeSystemOps(opcode, bytecode, pc, registers);
break;

// Group 9: Special I/O (151-154)
// Group 9: Special I/O (151-154) and DEREF_GLOB
case Opcodes.EVAL_STRING:
case Opcodes.SELECT_OP:
case Opcodes.LOAD_GLOB:
case Opcodes.SLEEP_OP:
case Opcodes.LOAD_SYMBOLIC_GLOB:
case Opcodes.DEREF_GLOB:
case Opcodes.DEREF_HASH_NONSTRICT:
case Opcodes.DEREF_ARRAY_NONSTRICT:
case Opcodes.DEREF_NONSTRICT:
pc = executeSpecialIO(opcode, bytecode, pc, registers, code);
break;

Expand Down Expand Up @@ -1858,72 +1849,57 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;

case Opcodes.STORE_SYMBOLIC_SCALAR: {
// Strict symbolic scalar store: throws for string refs, allows REFERENCE.
// Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg)
// Format: STORE_SYMBOLIC_SCALAR nameReg valueReg
int nameReg = bytecode[pc++];
int valueReg = bytecode[pc++];

// Get the variable name from the name register
RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg];
// scalarDeref() throws "strict refs" for STRING and acts as deref for REFERENCE
RuntimeScalar targetVar = nameScalar.scalarDeref();
targetVar.set(registers[valueReg]);
break;
}

case Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT: {
// Non-strict symbolic scalar store: allows string-keyed global variable store.
// Format: STORE_SYMBOLIC_SCALAR_NONSTRICT nameReg valueReg
int nameReg = bytecode[pc++];
int valueReg = bytecode[pc++];
String varName = nameScalar.toString();

RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg];
// Normalize the variable name to include package prefix if needed
// This is important for ${label:var} cases where "colon" becomes "main::colon"
String normalizedName = NameNormalizer.normalizeVariableName(
varName,
"main" // Use main package as default for symbolic references
);

if (nameScalar.type == RuntimeScalarType.REFERENCE) {
// ${\ ref} = value — dereference then assign
nameScalar.scalarDeref().set(registers[valueReg]);
} else {
// ${"varname"} = value — symbolic reference store
String normalizedName = NameNormalizer.normalizeVariableName(
nameScalar.toString(),
code.compilePackage
);
GlobalVariable.getGlobalVariable(normalizedName).set(registers[valueReg]);
}
// Get the global variable and set its value
RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName);
RuntimeBase value = registers[valueReg];
globalVar.set(value);
break;
}

case Opcodes.LOAD_SYMBOLIC_SCALAR: {
// Strict symbolic scalar load: rd = ${\ref} only.
// Throws "strict refs" for strings, matching Perl strict semantics.
// Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get()
// OR dereference if nameReg contains a scalar reference
// Format: LOAD_SYMBOLIC_SCALAR rd nameReg
int rd = bytecode[pc++];
int nameReg = bytecode[pc++];
// scalarDeref() handles both REFERENCE (dereference) and STRING
// (throws "Can't use string ... as a SCALAR ref while strict refs in use")
registers[rd] = ((RuntimeScalar) registers[nameReg]).scalarDeref();
break;
}

case Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT: {
// Non-strict symbolic scalar load: rd = ${"varname"} or ${\ref}.
// Allows string-keyed global variable lookup (no strict refs).
// Format: LOAD_SYMBOLIC_SCALAR_NONSTRICT rd nameReg
int rd = bytecode[pc++];
int nameReg = bytecode[pc++];

// Get the value from the name register
RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg];

// Check if it's a scalar reference - if so, dereference it
if (nameScalar.type == RuntimeScalarType.REFERENCE) {
// ${\ref} dereference the scalar reference
// This is ${\ref} - dereference the reference
registers[rd] = nameScalar.scalarDeref();
} else {
// ${"varname"} symbolic reference: look up global by name
// This is ${"varname"} - symbolic reference to variable
String varName = nameScalar.toString();

// Normalize the variable name to include package prefix if needed
// This is important for ${label:var} cases where "colon" becomes "main::colon"
String normalizedName = NameNormalizer.normalizeVariableName(
varName,
code.compilePackage // Use compile-time package for name resolution
"main" // Use main package as default for symbolic references
);
registers[rd] = GlobalVariable.getGlobalVariable(normalizedName);

// Get the global variable and load its value
RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName);
registers[rd] = globalVar;
}
break;
}
Expand Down Expand Up @@ -2075,7 +2051,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

default:
// Unknown opcode
int opcodeInt = opcode & 0xFF;
int opcodeInt = opcode;
throw new RuntimeException(
"Unknown opcode: " + opcodeInt +
" at pc=" + (pc - 1) +
Expand Down Expand Up @@ -2244,19 +2220,17 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];

// Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.)
// Pass through non-scalar reference types (array/hash/code/regex refs).
// Only dereference if it's a RuntimeScalar with REFERENCE type
if (value instanceof RuntimeScalar) {
RuntimeScalar sv = (RuntimeScalar) value;
if (sv.type == RuntimeScalarType.ARRAYREFERENCE
|| sv.type == RuntimeScalarType.HASHREFERENCE
|| sv.type == RuntimeScalarType.CODE
|| sv.type == RuntimeScalarType.REGEX) {
registers[rd] = sv; // pass through non-scalar refs
RuntimeScalar scalar = (RuntimeScalar) value;
if (scalar.type == RuntimeScalarType.REFERENCE) {
registers[rd] = scalar.scalarDeref();
} else {
registers[rd] = sv.scalarDeref();
// Non-reference scalar, just copy
registers[rd] = value;
}
} else {
// RuntimeList or other types, pass through
registers[rd] = value;
}
return pc;
Expand Down Expand Up @@ -3113,7 +3087,7 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc,

/**
* Execute special I/O operations (opcodes 151-154).
* Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP
* Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB
*/
private static int executeSpecialIO(int opcode, int[] bytecode, int pc,
RuntimeBase[] registers, InterpretedCode code) {
Expand All @@ -3126,16 +3100,8 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc,
return SlowOpcodeHandler.executeLoadGlob(bytecode, pc, registers, code);
case Opcodes.SLEEP_OP:
return SlowOpcodeHandler.executeSleep(bytecode, pc, registers);
case Opcodes.LOAD_SYMBOLIC_GLOB:
return SlowOpcodeHandler.executeLoadSymbolicGlob(bytecode, pc, registers);
case Opcodes.DEREF_GLOB:
return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers);
case Opcodes.DEREF_HASH_NONSTRICT:
return SlowOpcodeHandler.executeDerefHashNonStrict(bytecode, pc, registers, code);
case Opcodes.DEREF_ARRAY_NONSTRICT:
return SlowOpcodeHandler.executeDerefArrayNonStrict(bytecode, pc, registers, code);
case Opcodes.DEREF_NONSTRICT:
return SlowOpcodeHandler.executeDerefNonStrict(bytecode, pc, registers, code);
return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code);
default:
throw new RuntimeException("Unknown special I/O opcode: " + opcode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,11 +651,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Use strict vs non-strict store opcode depending on current strict refs
short storeOp = bytecodeCompiler.isStrictRefsEnabled()
? Opcodes.STORE_SYMBOLIC_SCALAR
: Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT;
bytecodeCompiler.emit(storeOp);
// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emitReg(valueReg);

Expand All @@ -671,11 +668,8 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Use strict vs non-strict store opcode depending on current strict refs
short storeOp2 = bytecodeCompiler.isStrictRefsEnabled()
? Opcodes.STORE_SYMBOLIC_SCALAR
: Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT;
bytecodeCompiler.emit(storeOp2);
// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emitReg(valueReg);

Expand Down Expand Up @@ -902,27 +896,6 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("*") &&
(leftOp.operand instanceof BlockNode ||
leftOp.operand instanceof OperatorNode ||
leftOp.operand instanceof StringNode)) {
// Dynamic typeglob assignment: *{"Pkg::name"} = value, *$ref = value, *{'name'} = value
// Evaluate the expression to get the glob name at runtime
leftOp.operand.accept(bytecodeCompiler);
int nameReg = bytecodeCompiler.lastResultReg;

// Load the glob via symbolic reference
int globReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex());
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(nameReg);

// Store value to glob
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
24 changes: 5 additions & 19 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -835,16 +835,10 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
// Allocate register for result
int rd = bytecodeCompiler.allocateRegister();

// Emit direct opcode EVAL_STRING with call-site strict/feature/warning flags
// so EvalStringHandler inherits the pragmas in effect at the eval call site
// (not just the end-of-compilation snapshot in InterpretedCode)
int callSiteStrictOptions = bytecodeCompiler.getCurrentStrictOptions();
int callSiteFeatureFlags = bytecodeCompiler.getCurrentFeatureFlags();
// Emit direct opcode EVAL_STRING
bytecodeCompiler.emitWithToken(Opcodes.EVAL_STRING, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(stringReg);
bytecodeCompiler.emitInt(callSiteStrictOptions);
bytecodeCompiler.emitInt(callSiteFeatureFlags);

bytecodeCompiler.lastResultReg = rd;
} else {
Expand Down Expand Up @@ -1675,33 +1669,25 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.throwCompilerException("open requires arguments");
}

// Compile the filehandle argument (first arg) as an lvalue register
// We must NOT push it through ARRAY_PUSH (which copies via addToArray),
// because IOOperator.open needs to call fileHandle.set() on the actual lvalue.
argsList.elements.get(0).accept(bytecodeCompiler);
int fhReg = bytecodeCompiler.lastResultReg;

// Compile remaining arguments into a list (mode, filename/ref, ...)
// Compile all arguments into a list
int argsReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.NEW_ARRAY);
bytecodeCompiler.emitReg(argsReg);

for (int i = 1; i < argsList.elements.size(); i++) {
argsList.elements.get(i).accept(bytecodeCompiler);
for (Node arg : argsList.elements) {
arg.accept(bytecodeCompiler);
int elemReg = bytecodeCompiler.lastResultReg;

bytecodeCompiler.emit(Opcodes.ARRAY_PUSH);
bytecodeCompiler.emitReg(argsReg);
bytecodeCompiler.emitReg(elemReg);
}

// Call open: OPEN rd ctx fhReg argsReg
// fhReg is the actual lvalue register for the filehandle (written back directly)
// Call open with context and args
int rd = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.OPEN);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emit(bytecodeCompiler.currentCallContext);
bytecodeCompiler.emitReg(fhReg);
bytecodeCompiler.emitReg(argsReg);

bytecodeCompiler.lastResultReg = rd;
Expand Down
Loading