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 @@ -803,6 +803,13 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
.aliasLvalueReference(registers[reference].getFirst());
}

case Opcodes.ALIAS_GLOBAL_SCALAR -> {
int nameIdx = bytecode[pc++];
int scalarReg = bytecode[pc++];
GlobalVariable.aliasGlobalVariable(code.stringPool[nameIdx],
registers[scalarReg].getFirst());
}

case Opcodes.LOAD_CONST -> {
// Load from constant pool: rd = constants[index]
int rd = bytecode[pc++];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,63 @@ private static int compileRhs(BytecodeCompiler bc, Node rhs, int context) {
return bc.lastResultReg;
}

/** Compile a parenthesized reference-alias assignment element by element. */
private static boolean compileReferenceAliasListAssignment(
BytecodeCompiler bc, BinaryOperatorNode node) {
if (!(node.left instanceof OperatorNode referenceOp)
|| !referenceOp.operator.equals("\\")
|| !(referenceOp.operand instanceof ListNode targets)
|| targets.elements.size() <= 1) {
return false;
}
if (!bc.symbolTable.isFeatureCategoryEnabled("refaliasing")) {
bc.throwCompilerException("Experimental aliasing via reference not enabled");
return true;
}

// A reference to a parenthesized list produces one reference per
// target. SET_FROM_LIST would copy values, rather than replace slots.
int rhsReg = compileRhs(bc, node.right, RuntimeContextType.LIST);
int rhsListReg = bc.allocateRegister();
bc.emit(Opcodes.SCALAR_TO_LIST);
bc.emitReg(rhsListReg);
bc.emitReg(rhsReg);

for (int i = 0; i < targets.elements.size(); i++) {
Node target = targets.elements.get(i);
if (!(target instanceof BinaryOperatorNode element)
|| !(element.operator.equals("[") || element.operator.equals("{"))) {
bc.throwCompilerException("Assignment to unsupported ref aliasing target");
return true;
}
if (element.operator.equals("[")
&& element.left instanceof OperatorNode arrayOp
&& arrayOp.operator.equals("$")
&& arrayOp.operand instanceof IdentifierNode) {
bc.handleArrayElementLvalueAccess(element, arrayOp);
} else {
bc.compileNode(element, -1, RuntimeContextType.LVALUE);
}
int targetReg = bc.lastResultReg;

int indexReg = bc.allocateRegister();
bc.emit(Opcodes.LOAD_INT);
bc.emitReg(indexReg);
bc.emit(i);
int referenceReg = bc.allocateRegister();
bc.emit(Opcodes.ARRAY_GET);
bc.emitReg(referenceReg);
bc.emitReg(rhsListReg);
bc.emitReg(indexReg);

bc.emit(Opcodes.ALIAS_LVALUE_REFERENCE);
bc.emitReg(targetReg);
bc.emitReg(referenceReg);
}
bc.lastResultReg = rhsListReg;
return true;
}

private static boolean handleLocalAssignment(BytecodeCompiler bc, BinaryOperatorNode node, OperatorNode leftOp, int rhsContext) {
if (!leftOp.operator.equals("local")) return false;
Node localOperand = leftOp.operand;
Expand Down Expand Up @@ -641,6 +698,10 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
return;
}

if (compileReferenceAliasListAssignment(bytecodeCompiler, node)) {
return;
}

// Special case: my $x = value
if (node.left instanceof OperatorNode leftOp) {
if (leftOp.operator.equals("my") || leftOp.operator.equals("state")) {
Expand Down Expand Up @@ -1625,7 +1686,7 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
return;
}

if (bytecodeCompiler.hasVariable(varName)) {
if (bytecodeCompiler.hasVariable(varName) && !bytecodeCompiler.isOurVariable(varName)) {
int targetReg = bytecodeCompiler.getVariableRegister(varName);
int derefReg = bytecodeCompiler.allocateRegister();
switch (varNode.operator) {
Expand All @@ -1642,7 +1703,21 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.lastResultReg = targetReg;
} else {
bytecodeCompiler.throwCompilerException("Variable " + varName + " not found for ref aliasing");
String globalName = NameNormalizer.normalizeVariableName(
varName.substring(1), bytecodeCompiler.getCurrentPackage());
int derefReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex());
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(valueReg);
int nameIdx = bytecodeCompiler.addToStringPool(globalName);
bytecodeCompiler.emit(Opcodes.ALIAS_GLOBAL_SCALAR);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.emitReg(derefReg);
int targetReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.LOAD_GLOBAL_SCALAR);
bytecodeCompiler.emitReg(targetReg);
bytecodeCompiler.emit(nameIdx);
bytecodeCompiler.lastResultReg = targetReg;
}
} else {
bytecodeCompiler.throwCompilerException("Assignment to unsupported ref aliasing target: " + leftOp.operator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ public static String disassemble(InterpretedCode interpretedCode) {
sb.append("ALIAS_LVALUE_REFERENCE r").append(rd)
.append(" <- r").append(src).append("\n");
break;
case Opcodes.ALIAS_GLOBAL_SCALAR:
int globalAliasNameIdx = interpretedCode.bytecode[pc++];
src = interpretedCode.bytecode[pc++];
sb.append("ALIAS_GLOBAL_SCALAR ").append(interpretedCode.stringPool[globalAliasNameIdx])
.append(" <- r").append(src).append("\n");
break;
case Opcodes.ASSIGN_LEXICAL_SCALAR:
rd = interpretedCode.bytecode[pc++];
src = interpretedCode.bytecode[pc++];
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2568,6 +2568,8 @@ public class Opcodes {

/** Array-element lvalue fetch. Format: ARRAY_GET_LVALUE rd arrayReg indexReg. */
public static final short ARRAY_GET_LVALUE = 542;
/** Alias a package scalar slot to a scalar. Format: ALIAS_GLOBAL_SCALAR nameIdx scalarReg. */
public static final short ALIAS_GLOBAL_SCALAR = 546;

/** Preallocate hash buckets for {@code keys %hash = CAPACITY}. Format: hashReg capacityReg. */
public static final short HASH_PREALLOCATE = 543;
Expand Down
71 changes: 71 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,11 @@ static void handleAssignOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
}
}

if (isReferenceAliasListAssignment(node.left)) {
emitReferenceAliasListAssignment(emitterVisitor, node);
break;
}

// The left value can be a variable, an operator or a subroutine call:
// `pos`, `substr`, `vec`, `sub :lvalue`

Expand Down Expand Up @@ -1002,6 +1007,23 @@ static void handleAssignOperator(EmitterVisitor emitterVisitor, BinaryOperatorNo
ctx.javaClassInfo.releaseSpillSlot();
}
break;
} else if ((symEntry == null || symEntry.decl().equals("our")) && varNode.operator.equals("$")) {
String globalName = NameNormalizer.normalizeVariableName(
varName.substring(1), ctx.symbolTable.getCurrentPackage());
mv.visitLdcInsn(globalName);
mv.visitVarInsn(Opcodes.ALOAD, rhsSlot);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeScalar", "scalarDeref",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable", "aliasGlobalVariable",
"(Ljava/lang/String;Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)V", false);
mv.visitLdcInsn(globalName);
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/GlobalVariable", "getGlobalVariable",
"(Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false);
if (pooledRhs) ctx.javaClassInfo.releaseSpillSlot();
break;
}
}
// Fall through for unsupported ref aliasing targets (global vars, etc.)
Expand Down Expand Up @@ -1140,6 +1162,55 @@ private static boolean isScalarLvalueTarget(Node node) {
|| (binop.right instanceof BinaryOperatorNode call && call.operator.equals("("));
}

private static boolean isReferenceAliasListAssignment(Node left) {
return left instanceof OperatorNode referenceOp
&& referenceOp.operator.equals("\\")
&& referenceOp.operand instanceof ListNode targets
&& targets.elements.size() > 1;
}

/** Emits \(TARGETS) = \(REFERENTS) without collapsing the RHS list to scalar context. */
private static void emitReferenceAliasListAssignment(EmitterVisitor emitterVisitor, BinaryOperatorNode node) {
EmitterContext ctx = emitterVisitor.ctx;
MethodVisitor mv = ctx.mv;
ListNode targets = (ListNode) ((OperatorNode) node.left).operand;
if (!ctx.symbolTable.isFeatureCategoryEnabled("refaliasing")) {
throw new PerlCompilerException(node.tokenIndex, "Experimental aliasing via reference not enabled", ctx.errorUtil);
}

node.right.accept(emitterVisitor.with(RuntimeContextType.LIST));
int rhsListSlot = ctx.javaClassInfo.acquireSpillSlot();
boolean pooledRhsList = rhsListSlot >= 0;
if (!pooledRhsList) rhsListSlot = ctx.symbolTable.allocateLocalVariable();
mv.visitVarInsn(Opcodes.ASTORE, rhsListSlot);

for (int i = 0; i < targets.elements.size(); i++) {
Node target = targets.elements.get(i);
if (!(target instanceof BinaryOperatorNode targetElement)
|| !(targetElement.operator.equals("[") || targetElement.operator.equals("{"))) {
throw new PerlCompilerException(node.tokenIndex,
"Assignment to unsupported ref aliasing target", ctx.errorUtil);
}
if (targetElement.operator.equals("[")) {
Dereference.handleArrayElementOperator(
emitterVisitor.with(RuntimeContextType.LVALUE), targetElement, "getLvalue");
} else {
targetElement.accept(emitterVisitor.with(RuntimeContextType.LVALUE));
}
mv.visitVarInsn(Opcodes.ALOAD, rhsListSlot);
mv.visitFieldInsn(Opcodes.GETFIELD,
"org/perlonjava/runtime/runtimetypes/RuntimeList", "elements", "Ljava/util/List;");
mv.visitLdcInsn(i);
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
mv.visitTypeInsn(Opcodes.CHECKCAST, "org/perlonjava/runtime/runtimetypes/RuntimeScalar");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeScalar", "aliasLvalueReference",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false);
if (i < targets.elements.size() - 1) mv.visitInsn(Opcodes.POP);
}
if (pooledRhsList) ctx.javaClassInfo.releaseSpillSlot();
}

/**
* Emits a scalar assignment where the LHS is a ternary operator with at least one
* branch that is a LIST assignment expression.
Expand Down
11 changes: 11 additions & 0 deletions src/test/resources/unit/interpreter_our_undef_list_placeholder.t
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ is($multidimensional_hash{"left\034right"}, 'combined',
'declared-reference scalar alias writes through its source');
}

{
use feature 'refaliasing';
no warnings 'experimental::refaliasing';
my @source = qw(left right);
my @target = qw(one two);
\($target[0], $target[1]) = \($source[1], $source[0]);
$target[0] = 'updated';
is_deeply(\@source, ['left', 'updated'],
'parenthesized array-element reference aliases replace their matching slots');
}

{
no warnings 'numeric';
is('a' x ('Inf' + 0), '', 'infinite positive repeat count is empty');
Expand Down
Loading