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
3 changes: 2 additions & 1 deletion docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ priorities and future plans.

## Work in progress

- Keep nested Rose::HTML::Objects form results alive with counted argument owners so weak parent links and qualified field names survive argument evaluation (#1851).
- Warn for undefined right operands of `.=` while keeping an undefined left operand quiet (#1382).
- Fix bundled IO, nested launcher, locale, and core user/group database module compatibility.

- Snapshot mutable array entries while marking package roots and during reachability walks so concurrent thread teardown cannot fail with `ConcurrentModificationException`.

- Enforce restricted-hash key access and implement `Hash::Util` key, value, and hash locking (#1502, #1172).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6738,6 +6738,23 @@ void emitAliasWithTarget(int destReg, int srcReg) {
}
}

int compileCallArguments(Node argsNode) {
List<Node> arguments = argsNode instanceof ListNode list
? list.elements : (argsNode == null ? List.of() : List.of(argsNode));
int argsReg = allocateRegister();
emit(Opcodes.NEW_CALL_ARGUMENTS);
emitReg(argsReg);
emit(arguments.size());
for (Node argument : arguments) {
compileNode(argument, -1, RuntimeContextType.LIST);
int valueReg = lastResultReg;
emit(Opcodes.ADD_CALL_ARGUMENT);
emitReg(argsReg);
emitReg(valueReg);
}
return argsReg;
}

void compileNode(Node node, int targetReg, int callContext) {
if (node == null) {
// Skip null nodes (e.g., from format declarations that produce empty statements)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2056,9 +2056,14 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
}

RuntimeBase argsBase = registers[argsReg];
RuntimeCode.CallArguments stagedArgs =
argsBase instanceof RuntimeCode.CallArguments staged
? staged : null;

RuntimeArray callArgs;
if (argsBase instanceof RuntimeArray) {
if (stagedArgs != null) {
callArgs = null;
} else if (argsBase instanceof RuntimeArray) {
callArgs = (RuntimeArray) argsBase;
} else if (argsBase instanceof RuntimeList) {
if (((RuntimeList) argsBase).elements.size() == 1
Expand All @@ -2083,7 +2088,11 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
// establishes mortal marks, warning/hint stacks, args-stack state,
// and void-result cleanup. Bypassing it keeps scope temporaries alive
// in large-code interpreter fallbacks (Net::LDAP ref-loop cleanup).
if (shareArgs) {
if (stagedArgs != null) {
result = shareArgs
? RuntimeCode.apply(codeRef, stagedArgs, context)
: RuntimeCode.apply(codeRef, "", stagedArgs, context);
} else if (shareArgs) {
result = RuntimeCode.apply(codeRef, callArgs, context);
} else {
result = RuntimeCode.apply(codeRef, "", callArgs, context);
Expand Down Expand Up @@ -2256,9 +2265,14 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
RuntimeScalar method = registers[methodReg].scalar();
RuntimeScalar currentSub = registers[currentSubReg].scalar();
RuntimeBase argsBase = registers[argsReg];
RuntimeCode.CallArguments stagedArgs =
argsBase instanceof RuntimeCode.CallArguments staged
? staged : null;

RuntimeArray callArgs;
if (argsBase instanceof RuntimeArray) {
if (stagedArgs != null) {
callArgs = null;
} else if (argsBase instanceof RuntimeArray) {
callArgs = (RuntimeArray) argsBase;
} else if (argsBase instanceof RuntimeList) {
callArgs = new RuntimeArray();
Expand All @@ -2274,7 +2288,9 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
CallerStack.pushLazy(lazyPkg, () -> getCallSiteInfo(code, lazyPc, lazyPkg));
RuntimeList result;
try {
result = RuntimeCode.call(invocant, method, currentSub, callArgs, context);
result = stagedArgs == null
? RuntimeCode.call(invocant, method, currentSub, callArgs, context)
: RuntimeCode.call(invocant, method, currentSub, stagedArgs, context);

// Keep method calls on the shared tail-call handoff as well.
result = RuntimeCode.resolveTailCalls(result, context);
Expand Down Expand Up @@ -3020,6 +3036,19 @@ private static RuntimeList execute(SuspendedInterpreterFrame frame) {
pc = InlineOpcodeHandler.executeCreateList(bytecode, pc, registers);
}

case Opcodes.NEW_CALL_ARGUMENTS -> {
int rd = bytecode[pc++];
int capacity = bytecode[pc++];
registers[rd] = RuntimeCode.newCallArguments(capacity);
}

case Opcodes.ADD_CALL_ARGUMENT -> {
int argsReg = bytecode[pc++];
int valueReg = bytecode[pc++];
((RuntimeCode.CallArguments) registers[argsReg])
.add((RuntimeBase) registers[valueReg]);
}

case Opcodes.SCALAR_IF_WANTARRAY -> {
pc = InlineOpcodeHandler.executeScalarIfWantarray(bytecode, pc, registers, callContext);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,7 @@ else if (node.right instanceof ListNode) {
bytecodeCompiler.compileNode(node.left, -1, RuntimeContextType.SCALAR);
int coderefReg = bytecodeCompiler.lastResultReg;

bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST);
int argsReg = bytecodeCompiler.lastResultReg;
int argsReg = bytecodeCompiler.compileCallArguments(node.right);

// Allocate result register
int rd = bytecodeCompiler.allocateOutputRegister();
Expand Down Expand Up @@ -344,8 +343,7 @@ else if (node.right instanceof BinaryOperatorNode rightCall) {
bytecodeCompiler.emit(subIdx);

// Compile arguments in list context
bytecodeCompiler.compileNode(argsNode, -1, RuntimeContextType.LIST);
int argsReg = bytecodeCompiler.lastResultReg;
int argsReg = bytecodeCompiler.compileCallArguments(argsNode);

// Allocate result register
int rd = bytecodeCompiler.allocateOutputRegister();
Expand Down Expand Up @@ -574,12 +572,12 @@ && unwrapScalarAnonymousInvocation(outerArgs.elements.get(0)) instanceof BinaryO
if (savedCallerLineOverride <= 0 && node.left != null && node.left.getIndex() > 0) {
bytecodeCompiler.callerLineTokenOverride = node.left.getIndex();
}
int rs2;
try {
bytecodeCompiler.compileNode(node.right, -1, RuntimeContextType.LIST);
rs2 = bytecodeCompiler.compileCallArguments(node.right);
} finally {
bytecodeCompiler.callerLineTokenOverride = savedCallerLineOverride;
}
int rs2 = bytecodeCompiler.lastResultReg;

// Check if this is a &func (no parens) call that should share caller's @_
boolean shareCallerArgs = node.getBooleanAnnotation("shareCallerArgs");
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,20 @@ public static String disassemble(InterpretedCode interpretedCode) {
sb.append("]\n");
break;
}
case Opcodes.NEW_CALL_ARGUMENTS: {
rd = interpretedCode.bytecode[pc++];
int argumentCapacity = interpretedCode.bytecode[pc++];
sb.append("NEW_CALL_ARGUMENTS r").append(rd)
.append(" capacity=").append(argumentCapacity).append("\n");
break;
}
case Opcodes.ADD_CALL_ARGUMENT: {
int stagedArgsReg = interpretedCode.bytecode[pc++];
int stagedValueReg = interpretedCode.bytecode[pc++];
sb.append("ADD_CALL_ARGUMENT r").append(stagedArgsReg)
.append(" <- r").append(stagedValueReg).append("\n");
break;
}
case Opcodes.CALL_SUB:
case Opcodes.CALL_SUB_SHARE_ARGS:
rd = interpretedCode.bytecode[pc++];
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2148,6 +2148,12 @@ public class Opcodes {
*/
public static final short CALL_SUB_SHARE_ARGS = 401;

/** Start collecting evaluated call arguments with temporary lifetimes. */
public static final short NEW_CALL_ARGUMENTS = 637;

/** Append one evaluated expression to a staged call-argument list. */
public static final short ADD_CALL_ARGUMENT = 638;

// Missing system operators needed for interpreter fallback of large files (e.g. taint.t)
public static final short SYMLINK = 402;
public static final short CHROOT = 403;
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/org/perlonjava/backend/jvm/Dereference.java
Original file line number Diff line number Diff line change
Expand Up @@ -1084,8 +1084,9 @@ static void handleArrowOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
} else {
if (argCount <= 127) mv.visitIntInsn(Opcodes.BIPUSH, argCount);
else mv.visitIntInsn(Opcodes.SIPUSH, argCount);
mv.visitTypeInsn(Opcodes.ANEWARRAY,
"org/perlonjava/runtime/runtimetypes/RuntimeBase");
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeCode", "newCallArguments",
"(I)Lorg/perlonjava/runtime/runtimetypes/RuntimeCode$CallArguments;", false);
mv.visitVarInsn(Opcodes.ASTORE, argsArraySlot);

for (int index = 0; index < argCount; index++) {
Expand All @@ -1095,10 +1096,10 @@ static void handleArrowOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
paramList.elements.get(index).accept(listVisitor);
mv.visitVarInsn(Opcodes.ASTORE, argSlot);
mv.visitVarInsn(Opcodes.ALOAD, argsArraySlot);
if (index <= 127) mv.visitIntInsn(Opcodes.BIPUSH, index);
else mv.visitIntInsn(Opcodes.SIPUSH, index);
mv.visitVarInsn(Opcodes.ALOAD, argSlot);
mv.visitInsn(Opcodes.AASTORE);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeCode$CallArguments", "add",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;)V", false);
if (pooledArg) emitterVisitor.ctx.javaClassInfo.releaseSpillSlot();
}
}
Expand Down Expand Up @@ -1147,7 +1148,7 @@ static void handleArrowOperator(EmitterVisitor emitterVisitor, BinaryOperatorNod
}
String argsDescriptor = directRuntimeArray
? "Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;"
: "[Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;";
: "Lorg/perlonjava/runtime/runtimetypes/RuntimeCode$CallArguments;";
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeCode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,9 +594,9 @@ public static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSca

/**
* Implements {@code .=}. Perl treats an undefined left operand as the empty
* string without issuing the uninitialized warning that ordinary {@code .}
* emits. Keep this as a distinct entry point so eval-time warning changes do
* not turn concat-assignment into warning-aware concatenation.
* string without a warning, but still warns when the right operand is
* undefined. Keep the two warning checks separate from ordinary {@code .}
* so eval-time warning changes preserve that distinction.
*/
public static RuntimeScalar stringConcatAssign(RuntimeScalar runtimeScalar, RuntimeScalar b) {
// Typeglobs stringify when merely observed, but they cannot be used as
Expand Down Expand Up @@ -626,7 +626,7 @@ public static RuntimeScalar stringConcatAssign(RuntimeScalar runtimeScalar, Runt
&& runtimeScalar.type != RuntimeScalarType.BYTE_STRING)
|| (b.type != RuntimeScalarType.STRING
&& b.type != RuntimeScalarType.BYTE_STRING)) {
return stringConcat(runtimeScalar, b, false);
return stringConcat(runtimeScalar, b, false, true);
}

String bStr = b.toString();
Expand All @@ -640,6 +640,12 @@ public static RuntimeScalar stringConcatAssign(RuntimeScalar runtimeScalar, Runt

private static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeScalar b,
boolean warnUninitialized) {
return stringConcat(runtimeScalar, b, warnUninitialized, warnUninitialized);
}

private static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeScalar b,
boolean warnLeftUninitialized,
boolean warnRightUninitialized) {
RuntimeScalar overloaded = tryStringConcatOverload(runtimeScalar, b);
if (overloaded != null) return overloaded;

Expand All @@ -654,8 +660,9 @@ private static RuntimeScalar stringConcat(RuntimeScalar runtimeScalar, RuntimeSc
boolean callSiteWarnsUninitialized = callSiteWarningBits == null
|| WarningFlags.areWarningsForcedOn()
|| WarningFlags.isEnabledInBits(callSiteWarningBits, "uninitialized");
if (warnUninitialized && callSiteWarnsUninitialized
&& (!aResolved.getDefinedBoolean() || !bResolved.getDefinedBoolean())) {
boolean leftIsUndefined = warnLeftUninitialized && !aResolved.getDefinedBoolean();
boolean rightIsUndefined = warnRightUninitialized && !bResolved.getDefinedBoolean();
if (callSiteWarnsUninitialized && (leftIsUndefined || rightIsUndefined)) {
WarnDie.warnWithCategory(
new RuntimeScalar("Use of uninitialized value in concatenation (.)"),
RuntimeScalarCache.scalarEmptyString, "uninitialized");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public final class ExecutionRuntimeState {
public final Deque<RuntimeCode.EvalSourceFrame> activeEvalSources = new ArrayDeque<>();
public final ArrayDeque<ArrayList<String>> syntheticCallerFrames = new ArrayDeque<>();
public final Deque<RuntimeArray> argsStack = new ArrayDeque<>();
/** Weak roots for JVM call arguments while their expressions are evaluated. */
final ArrayList<java.lang.ref.WeakReference<RuntimeCode.CallArguments>> callArgumentFrames =
new ArrayList<>();
public final Deque<RuntimeCode> activeCodeStack = new ArrayDeque<>();
/** Perl source callsites aligned with the active Perl code frames. */
public final Deque<CallerStack.CallerInfo> activeCodeCallSites = new ArrayDeque<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ final class LifecycleRuntimeState {
// Parallel to pending. A non-scalar transient owner kind is recorded only
// for trace attribution; it never changes runtime reachability.
final ArrayList<String> pendingTransientOwnerKinds = new ArrayList<>();
final ArrayList<MortalList.DeferredArgumentOwnerRelease> deferredArgumentOwnerReleases =
new ArrayList<>();
final ArrayList<TiedVariableBase> pendingTiedReleases = new ArrayList<>();
final ArrayList<RuntimeScalar> pendingIoReleases = new ArrayList<>();
final ArrayList<RuntimeScalar> deferredCaptures = new ArrayList<>();
Expand Down
Loading
Loading