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 = false;
public boolean disassembleEnabled = System.getenv("JPERL_DISASSEMBLE") != null;
public boolean useInterpreter = false;
public boolean tokenizeOnly = false;
public boolean parseOnly = false;
Expand Down
173 changes: 152 additions & 21 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,30 @@ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String
* @param varName The variable name with sigil (e.g., "$A", "@array")
* @return true if access should be blocked under strict vars
*/
/** Returns true if strict refs is currently enabled at compile time. */
boolean isStrictRefsEnabled() {
if (emitterContext == null || emitterContext.symbolTable == null) {
return false;
}
return emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS);
}

/** Returns the current strict options bitmask at this point in compilation. */
int getCurrentStrictOptions() {
if (emitterContext == null || emitterContext.symbolTable == null) {
return 0;
}
return emitterContext.symbolTable.strictOptionsStack.peek();
}

/** Returns the current feature flags bitmask at this point in compilation. */
int getCurrentFeatureFlags() {
if (emitterContext == null || emitterContext.symbolTable == null) {
return 0;
}
return emitterContext.symbolTable.featureFlagsStack.peek();
}

boolean shouldBlockGlobalUnderStrictVars(String varName) {
// Only check if strict vars is enabled
if (emitterContext == null || emitterContext.symbolTable == null) {
Expand Down Expand Up @@ -2194,6 +2218,25 @@ void compileVariableDeclaration(OperatorNode node, String op) {
OperatorNode sigilOp = (OperatorNode) node.operand;
String sigil = sigilOp.operator;

if (sigil.equals("*") && sigilOp.operand instanceof IdentifierNode) {
// local *glob — save glob state and return same glob object
// Mirrors JVM path: load glob, call DynamicVariableManager.pushLocalVariable(RuntimeGlob)
String globName = NameNormalizer.normalizeVariableName(((IdentifierNode) sigilOp.operand).name, getCurrentPackage());
int nameIdx = addToStringPool(globName);

int globReg = allocateRegister();
emitWithToken(Opcodes.LOAD_GLOB, node.getIndex());
emitReg(globReg);
emit(nameIdx);

// Push glob to local variable stack (saves state, returns same object)
emit(Opcodes.PUSH_LOCAL_VARIABLE);
emitReg(globReg);

lastResultReg = globReg;
return;
}

if (sigil.equals("$") && sigilOp.operand instanceof IdentifierNode) {
String varName = "$" + ((IdentifierNode) sigilOp.operand).name;

Expand Down Expand Up @@ -2601,31 +2644,60 @@ void compileVariableReference(OperatorNode node, String op) {
operandOp.accept(this);
int refReg = lastResultReg;

// Dereference to get the array
// The reference should contain a RuntimeArray
// For @$scalar, we need to dereference it
int rd = allocateRegister();
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(rd);
emitReg(refReg);
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(rd);
emitReg(refReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex());
emitReg(rd);
emitReg(refReg);
emit(pkgIdx);
}

lastResultReg = rd;
// Note: We don't check scalar context here because dereferencing
// should return the array itself. The slice or other operation
// will handle scalar context conversion if needed.
} else if (node.operand instanceof BlockNode) {
// @{ block } - evaluate block and dereference the result
// The block should return an arrayref
BlockNode blockNode = (BlockNode) node.operand;
blockNode.accept(this);
int refReg = lastResultReg;

// Dereference to get the array
int rd = allocateRegister();
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(rd);
emitReg(refReg);
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(rd);
emitReg(refReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex());
emitReg(rd);
emitReg(refReg);
emit(pkgIdx);
}

lastResultReg = rd;
} else if (node.operand instanceof StringNode strNode) {
// @{'name'} — symbolic array reference
int nameReg = allocateRegister();
int strIdx = addToStringPool(strNode.value);
emit(Opcodes.LOAD_STRING);
emitReg(nameReg);
emit(strIdx);

int rd = allocateRegister();
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_ARRAY, node.getIndex());
emitReg(rd);
emitReg(nameReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_ARRAY_NONSTRICT, node.getIndex());
emitReg(rd);
emitReg(nameReg);
emit(pkgIdx);
}
lastResultReg = rd;
} else {
throwCompilerException("Unsupported @ operand: " + node.operand.getClass().getSimpleName());
Expand Down Expand Up @@ -2666,9 +2738,17 @@ void compileVariableReference(OperatorNode node, String op) {
refOp.accept(this);
int scalarReg = lastResultReg;
int hashReg = allocateRegister();
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
emit(pkgIdx);
}
if (currentCallContext == RuntimeContextType.SCALAR) {
int rd = allocateRegister();
emit(Opcodes.ARRAY_SIZE);
Expand All @@ -2683,15 +2763,44 @@ void compileVariableReference(OperatorNode node, String op) {
blockNode.accept(this);
int scalarReg = lastResultReg;
int hashReg = allocateRegister();
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex());
emitReg(hashReg);
emitReg(scalarReg);
emit(pkgIdx);
}
lastResultReg = hashReg;
} else if (node.operand instanceof StringNode strNode) {
// %{'name'} — symbolic hash reference
int nameReg = allocateRegister();
int strIdx = addToStringPool(strNode.value);
emit(Opcodes.LOAD_STRING);
emitReg(nameReg);
emit(strIdx);

int hashReg = allocateRegister();
if (isStrictRefsEnabled()) {
emitWithToken(Opcodes.DEREF_HASH, node.getIndex());
emitReg(hashReg);
emitReg(nameReg);
} else {
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_HASH_NONSTRICT, node.getIndex());
emitReg(hashReg);
emitReg(nameReg);
emit(pkgIdx);
}
lastResultReg = hashReg;
} else {
throwCompilerException("Unsupported % operand: " + node.operand.getClass().getSimpleName());
}
} else if (op.equals("*")) {
// Glob variable dereference: *x
// Glob variable dereference: *x or *{expr}
if (node.operand instanceof IdentifierNode) {
IdentifierNode idNode = (IdentifierNode) node.operand;
String varName = idNode.name;
Expand All @@ -2710,6 +2819,28 @@ void compileVariableReference(OperatorNode node, String op) {
emitReg(rd);
emit(nameIdx);

lastResultReg = rd;
} else if (node.operand instanceof BlockNode || node.operand instanceof StringNode) {
// *{expr} or *{'name'} — dynamic glob via symbolic reference
node.operand.accept(this);
int nameReg = lastResultReg;

int rd = allocateRegister();
emitWithToken(Opcodes.LOAD_SYMBOLIC_GLOB, node.getIndex());
emitReg(rd);
emitReg(nameReg);

lastResultReg = rd;
} else if (node.operand instanceof OperatorNode) {
// *$ref or **postfix — dereference scalar as glob
node.operand.accept(this);
int scalarReg = lastResultReg;

int rd = allocateRegister();
emitWithToken(Opcodes.DEREF_GLOB, node.getIndex());
emitReg(rd);
emitReg(scalarReg);

lastResultReg = rd;
} else {
throwCompilerException("Unsupported * operand: " + node.operand.getClass().getSimpleName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1376,22 +1376,27 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

case Opcodes.DEREF: {
// Dereference: rd = $$rs (scalar reference dereference)
// Can receive RuntimeScalar or RuntimeList
// Always call scalarDeref() — throws "Not a SCALAR reference" for
// non-reference types (IO, FORMAT, etc.), matching Perl semantics.
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 scalar = (RuntimeScalar) value;
if (scalar.type == RuntimeScalarType.REFERENCE) {
registers[rd] = scalar.scalarDeref();
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
} else {
// Non-reference scalar, just copy
registers[rd] = value;
registers[rd] = sv.scalarDeref();
}
} else {
// RuntimeList or other types, pass through
registers[rd] = value;
}
break;
Expand Down Expand Up @@ -1774,6 +1779,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
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 @@ -2219,17 +2229,19 @@ private static int executeTypeOps(int opcode, int[] bytecode, int pc,
int rs = bytecode[pc++];
RuntimeBase value = registers[rs];

// Only dereference if it's a RuntimeScalar with REFERENCE type
// Call scalarDeref() for scalar refs, undef, non-ref types (strings, globs, etc.)
// Pass through non-scalar reference types (array/hash/code/regex refs).
if (value instanceof RuntimeScalar) {
RuntimeScalar scalar = (RuntimeScalar) value;
if (scalar.type == RuntimeScalarType.REFERENCE) {
registers[rd] = scalar.scalarDeref();
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
} else {
// Non-reference scalar, just copy
registers[rd] = value;
registers[rd] = sv.scalarDeref();
}
} else {
// RuntimeList or other types, pass through
registers[rd] = value;
}
return pc;
Expand Down Expand Up @@ -3099,6 +3111,16 @@ 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);
default:
throw new RuntimeException("Unknown special I/O opcode: " + opcode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,27 @@ 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
Loading