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
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,23 @@ private void enterScope() {
savedBaseRegister.push(baseRegisterForStatement);
// Update base to protect all registers allocated before this scope
baseRegisterForStatement = nextRegister;
// Mirror the JVM compiler: push a new pragma frame so that strict/feature/warning
// changes inside this block (e.g. `use strict`, `use 5.012`) are scoped to the block
// and do not leak into the surrounding code after the block exits.
if (emitterContext != null && emitterContext.symbolTable != null) {
savedSymbolTableScopes.push(emitterContext.symbolTable.enterScope());
} else {
savedSymbolTableScopes.push(-1); // sentinel so stacks stay in sync
}
}

/**
* Helper: Exit the current lexical scope.
* Restores register allocation state to what it was before entering the scope.
*/
// Saved symbol-table scope indices, parallel to savedNextRegister / savedBaseRegister.
private final Stack<Integer> savedSymbolTableScopes = new Stack<>();

private void exitScope() {
if (variableScopes.size() > 1) {
variableScopes.pop();
Expand All @@ -247,6 +258,11 @@ private void exitScope() {
baseRegisterForStatement = savedBaseRegister.pop();
}
}
// Restore the pragma frame (strict/feature/warning flags) for the scope we just exited.
if (emitterContext != null && emitterContext.symbolTable != null
&& !savedSymbolTableScopes.isEmpty()) {
emitterContext.symbolTable.exitScope(savedSymbolTableScopes.pop());
}
}

/**
Expand Down Expand Up @@ -2530,6 +2546,10 @@ void compileVariableReference(OperatorNode node, String op) {
lastResultReg = getVariableRegister(varName);
} else {
// Global variable - load it
// Check strict vars before loading an undeclared global
if (shouldBlockGlobalUnderStrictVars(varName)) {
throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name");
}
// Use NameNormalizer to properly handle special variables (like $&)
// which must always be in the "main" package
String globalVarName = varName.substring(1); // Remove $ sigil first
Expand All @@ -2556,9 +2576,14 @@ void compileVariableReference(OperatorNode node, String op) {
block.accept(this);
int blockResultReg = lastResultReg;

// Load via symbolic reference
// Load via symbolic reference — use strict vs non-strict opcode.
// LOAD_SYMBOLIC_SCALAR throws "strict refs" for non-reference strings.
// LOAD_SYMBOLIC_SCALAR_NONSTRICT allows symbolic variable lookup.
int rd = allocateRegister();
emitWithToken(Opcodes.LOAD_SYMBOLIC_SCALAR, node.getIndex());
short symOp = isStrictRefsEnabled()
? Opcodes.LOAD_SYMBOLIC_SCALAR
: Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT;
emitWithToken(symOp, node.getIndex());
emitReg(rd);
emitReg(blockResultReg);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1858,57 +1858,72 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;

case Opcodes.STORE_SYMBOLIC_SCALAR: {
// Store via symbolic reference: GlobalVariable.getGlobalVariable(nameReg.toString()).set(valueReg)
// Strict symbolic scalar store: throws for string refs, allows REFERENCE.
// 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];
String varName = nameScalar.toString();
// scalarDeref() throws "strict refs" for STRING and acts as deref for REFERENCE
RuntimeScalar targetVar = nameScalar.scalarDeref();
targetVar.set(registers[valueReg]);
break;
}

// 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
);
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++];

// Get the global variable and set its value
RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName);
RuntimeBase value = registers[valueReg];
globalVar.set(value);
RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg];

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]);
}
break;
}

case Opcodes.LOAD_SYMBOLIC_SCALAR: {
// Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get()
// OR dereference if nameReg contains a scalar reference
// Strict symbolic scalar load: rd = ${\ref} only.
// Throws "strict refs" for strings, matching Perl strict semantics.
// 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) {
// This is ${\ref} - dereference the reference
// ${\ref} dereference the scalar reference
registers[rd] = nameScalar.scalarDeref();
} else {
// This is ${"varname"} - symbolic reference to variable
// ${"varname"} symbolic reference: look up global by name
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,
"main" // Use main package as default for symbolic references
code.compilePackage // Use compile-time package for name resolution
);

// Get the global variable and load its value
RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName);
registers[rd] = globalVar;
registers[rd] = GlobalVariable.getGlobalVariable(normalizedName);
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,11 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
// 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);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emitReg(valueReg);

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

// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
// 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);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emitReg(valueReg);

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/InterpretedCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,16 @@ public String disassemble() {
.append("} pkg=").append(stringPool[derefNsPkgIdx]).append("\n");
break;
}
case Opcodes.LOAD_SYMBOLIC_SCALAR_NONSTRICT:
rd = bytecode[pc++];
rs = bytecode[pc++];
sb.append("LOAD_SYMBOLIC_SCALAR_NONSTRICT r").append(rd).append(" = ${r").append(rs).append("}\n");
break;
case Opcodes.STORE_SYMBOLIC_SCALAR_NONSTRICT:
rd = bytecode[pc++];
rs = bytecode[pc++];
sb.append("STORE_SYMBOLIC_SCALAR_NONSTRICT ${r").append(rd).append("} = r").append(rs).append("\n");
break;
case Opcodes.GET_TYPE:
rd = bytecode[pc++];
rs = bytecode[pc++];
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1091,5 +1091,22 @@ public class Opcodes {
* Format: DEREF_NONSTRICT rd scalarReg packageIdx */
public static final short DEREF_NONSTRICT = 337;

/** Load via symbolic reference (strict refs): throws if nameReg is a string.
* Allows REFERENCE type (${\ ref}). Same format as LOAD_SYMBOLIC_SCALAR.
* Format: LOAD_SYMBOLIC_SCALAR rd nameReg (strict — already the default opcode 232) */
// Note: LOAD_SYMBOLIC_SCALAR (232) is the strict variant.

/** Load via symbolic reference (no strict refs): allows string-keyed global lookup.
* Format: LOAD_SYMBOLIC_SCALAR_NONSTRICT rd nameReg */
public static final short LOAD_SYMBOLIC_SCALAR_NONSTRICT = 338;

/** Store via symbolic reference (strict refs): throws if nameReg is a string.
* Format: STORE_SYMBOLIC_SCALAR rd nameReg */
// Note: STORE_SYMBOLIC_SCALAR (231) is the strict variant.

/** Store via symbolic reference (no strict refs): allows string-keyed global store.
* Format: STORE_SYMBOLIC_SCALAR_NONSTRICT nameReg valueReg */
public static final short STORE_SYMBOLIC_SCALAR_NONSTRICT = 339;

private Opcodes() {} // Utility class - no instantiation
}