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 dev/tools/perl_test_runner.pl
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ sub run_single_test {
}
# For tests in t/ directory (t/op/, t/base/, etc.), change to t/
# so they can find ./test.pl via require
elsif ($test_file =~ m{^t/}) {
# But not for ExifTool tests which need to run from their root dir
elsif ($test_file =~ m{^t/} && !-f 't/TestLib.pm') {
$local_test_dir = 't';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,17 @@ public void visit(BlockNode node) {

// Visit each statement in the block
int numStatements = node.elements.size();

int lastMeaningfulIndex = -1;
for (int i = numStatements - 1; i >= 0; i--) {
Node elem = node.elements.get(i);
if (elem == null) continue;
if (elem instanceof ListNode ln && ln.elements.isEmpty()) continue;
lastMeaningfulIndex = i;
break;
}
if (lastMeaningfulIndex == -1) lastMeaningfulIndex = numStatements - 1;

for (int i = 0; i < numStatements; i++) {
// Skip the 'local $_' child when For1Node handles it via LOCAL_SCALAR_SAVE_LEVEL
if (i == 0 && skipFirstChild) continue;
Expand All @@ -724,7 +735,7 @@ public void visit(BlockNode node) {

// If this is not an assignment or other value-using construct, use VOID context
// EXCEPT for the last statement in a block, which should use the block's context
boolean isLastStatement = (i == numStatements - 1);
boolean isLastStatement = (i == lastMeaningfulIndex);
if (!isLastStatement && !(stmt instanceof BinaryOperatorNode && ((BinaryOperatorNode) stmt).operator.equals("="))) {
currentCallContext = RuntimeContextType.VOID;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2030,6 +2030,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.SELECT_OP:
case Opcodes.LOAD_GLOB:
case Opcodes.SLEEP_OP:
case Opcodes.ALARM_OP:
case Opcodes.DEREF_GLOB:
case Opcodes.LOAD_GLOB_DYNAMIC:
case Opcodes.DEREF_SCALAR_STRICT:
Expand Down Expand Up @@ -3143,6 +3144,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.ALARM_OP:
return SlowOpcodeHandler.executeAlarm(bytecode, pc, registers);
case Opcodes.DEREF_GLOB:
return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code);
case Opcodes.LOAD_GLOB_DYNAMIC:
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,24 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emitReg(maxReg);
}

bytecodeCompiler.lastResultReg = rd;
} else if (op.equals("alarm")) {
int rd = bytecodeCompiler.allocateRegister();
if (node.operand != null) {
node.operand.accept(bytecodeCompiler);
int argReg = bytecodeCompiler.lastResultReg;
bytecodeCompiler.emit(Opcodes.ALARM_OP);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(argReg);
} else {
int zeroReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.LOAD_INT);
bytecodeCompiler.emitReg(zeroReg);
bytecodeCompiler.emitInt(0);
bytecodeCompiler.emit(Opcodes.ALARM_OP);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(zeroReg);
}
bytecodeCompiler.lastResultReg = rd;
} else if (op.equals("study")) {
// study $var
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,11 @@ public String disassemble() {
rs = bytecode[pc++];
sb.append("SLEEP_OP r").append(rd).append(" = sleep(r").append(rs).append(")\n");
break;
case Opcodes.ALARM_OP:
rd = bytecode[pc++];
rs = bytecode[pc++];
sb.append("ALARM_OP r").append(rd).append(" = alarm(r").append(rs).append(")\n");
break;
case Opcodes.DEREF_GLOB:
rd = bytecode[pc++];
rs = 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 @@ -603,6 +603,8 @@ public class Opcodes {
public static final short LOAD_GLOB = 153;
/** rd = Time.sleep(seconds) - sleep for specified seconds */
public static final short SLEEP_OP = 154;
/** rd = Time.alarm(seconds) - set alarm timer */
public static final short ALARM_OP = 155;

// =================================================================
// PHASE 3: OPERATORHANDLER PROMOTIONS (400-499) - Math Operators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,17 @@ public static int executeSleep(
return pc;
}

public static int executeAlarm(
int[] bytecode,
int pc,
RuntimeBase[] registers) {
int rd = bytecode[pc++];
int secondsReg = bytecode[pc++];
RuntimeScalar seconds = registers[secondsReg].scalar();
registers[rd] = Time.alarm(RuntimeContextType.SCALAR, seconds);
return pc;
}

/**
* Dereference array reference for multidimensional array access.
* Handles: $array[0][1] which is really $array[0]->[1]
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/org/perlonjava/backend/jvm/EmitOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,22 @@ static void handleCreateReference(EmitterVisitor emitterVisitor, OperatorNode no
if (node.operand instanceof OperatorNode operatorNode &&
operatorNode.operator.equals("&")) {
emitterVisitor.ctx.logDebug("Handle \\& " + operatorNode.operand);
if (operatorNode.operand instanceof OperatorNode ||
if (operatorNode.operand instanceof IdentifierNode identifierNode) {
emitterVisitor.ctx.mv.visitTypeInsn(Opcodes.NEW, "org/perlonjava/runtime/runtimetypes/RuntimeScalar");
emitterVisitor.ctx.mv.visitInsn(Opcodes.DUP);
emitterVisitor.ctx.mv.visitLdcInsn(identifierNode.name);
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESPECIAL,
"org/perlonjava/runtime/runtimetypes/RuntimeScalar",
"<init>",
"(Ljava/lang/String;)V",
false);
emitterVisitor.pushCurrentPackage();
emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"org/perlonjava/runtime/runtimetypes/RuntimeCode",
"createCodeReference",
"(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;",
false);
} else if (operatorNode.operand instanceof OperatorNode ||
operatorNode.operand instanceof BlockNode) {
operatorNode.operand.accept(emitterVisitor.with(RuntimeContextType.SCALAR));
emitterVisitor.pushCurrentPackage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ public static RuntimeScalar select(RuntimeList runtimeList, int ctx) {
if (!rbits.getDefinedBoolean() && !wbits.getDefinedBoolean() && !ebits.getDefinedBoolean()) {
double sleepTime = timeout.getDouble();
if (sleepTime > 0) {
Thread.interrupted();
try {
// Convert seconds to milliseconds
long millis = (long) (sleepTime * 1000);
int nanos = (int) ((sleepTime * 1000 - millis) * 1_000_000);
Thread.sleep(millis, nanos);
} catch (InterruptedException e) {
// Restore interrupted status
Thread.currentThread().interrupt();
// Return remaining time (we don't track it precisely, so return 0)
PerlSignalQueue.checkPendingSignals();
Thread.interrupted();
return new RuntimeScalar(0);
}
}
Expand Down
26 changes: 25 additions & 1 deletion src/main/java/org/perlonjava/runtime/operators/KillOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import com.sun.jna.platform.win32.Wincon;
import org.perlonjava.runtime.nativ.NativeUtils;
import org.perlonjava.runtime.nativ.PosixLibrary;
import org.perlonjava.runtime.runtimetypes.PerlSignalQueue;
import org.perlonjava.runtime.runtimetypes.RuntimeBase;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalHash;
import static org.perlonjava.runtime.runtimetypes.GlobalVariable.getGlobalVariable;

/**
Expand Down Expand Up @@ -68,8 +70,30 @@ public static RuntimeScalar kill(int ctx, RuntimeBase... args) {
return new RuntimeScalar(successCount);
}

// Helper method for sending signals to a single process
private static String getSignalName(int signal) {
return switch (signal) {
case 1 -> "HUP"; case 2 -> "INT"; case 3 -> "QUIT"; case 4 -> "ILL";
case 5 -> "TRAP"; case 6 -> "ABRT"; case 7 -> "BUS"; case 8 -> "FPE";
case 9 -> "KILL"; case 10 -> "USR1"; case 11 -> "SEGV"; case 12 -> "USR2";
case 13 -> "PIPE"; case 14 -> "ALRM"; case 15 -> "TERM";
default -> null;
};
}

private static boolean sendSignalToPid(int pid, int signal) {
long myPid = ProcessHandle.current().pid();
if (pid == myPid && signal != 0) {
String sigName = getSignalName(signal);
if (sigName != null) {
RuntimeScalar handler = getGlobalHash("main::SIG").get(sigName);
if (handler.getDefinedBoolean()) {
PerlSignalQueue.enqueue(sigName, handler);
PerlSignalQueue.checkPendingSignals();
return true;
}
}
return true;
}
if (NativeUtils.IS_WINDOWS) {
switch (signal) {
case 0: // Check if process exists
Expand Down
23 changes: 16 additions & 7 deletions src/main/java/org/perlonjava/runtime/operators/Readline.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,32 @@ public static RuntimeScalar readline(RuntimeIO runtimeIO) {
}

// Handle different modes of $/
if (rs != null && rs.isSlurpMode()) {
// Handle slurp mode when $/ = undef
boolean isSlurp = (rs != null && rs.isSlurpMode()) ||
(rs == null && rsScalar.type == RuntimeScalarType.UNDEF);
if (isSlurp) {
StringBuilder content = new StringBuilder();
String readChar;
while (!(readChar = runtimeIO.ioHandle.read(1).toString()).isEmpty()) {
content.append(readChar.charAt(0));
boolean isByteData = true;
RuntimeScalar chunk;
while (true) {
chunk = runtimeIO.ioHandle.read(8192);
String chunkStr = chunk.toString();
if (chunkStr.isEmpty()) break;
if (chunk.type != RuntimeScalarType.BYTE_STRING) isByteData = false;
content.append(chunkStr);
}

if (content.length() > 0) {
// Count newlines for line number tracking
String contentStr = content.toString();
for (int i = 0; i < contentStr.length(); i++) {
if (contentStr.charAt(i) == '\n') {
runtimeIO.currentLineNumber++;
}
}
return new RuntimeScalar(contentStr);
RuntimeScalar result = new RuntimeScalar(contentStr);
if (isByteData) {
result.type = RuntimeScalarType.BYTE_STRING;
}
return result;
} else if (runtimeIO.eof().getBoolean()) {
return scalarUndef;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ public static RuntimeScalar ref(RuntimeScalar runtimeScalar) {
str = ref(runtimeScalar.tiedFetch()).toString();
break;
case CODE:
if (!((RuntimeCode) runtimeScalar.value).defined()) {
str = "";
break;
}
blessId = ((RuntimeBase) runtimeScalar.value).blessId;
str = blessId == 0 ? "CODE" : NameNormalizer.getBlessStr(blessId);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ private static CommandResult executeCommand(String command, boolean captureOutpu
setGlobalVariable("main::!", e.getMessage());
exitCode = -1;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PerlCompilerException("Command execution interrupted: " + e.getMessage());
PerlSignalQueue.checkPendingSignals();
Thread.interrupted();
} finally {
// Readers are closed automatically by try-with-resources in threads
if (process != null) {
Expand Down Expand Up @@ -236,8 +236,8 @@ private static CommandResult executeCommandDirect(List<String> commandArgs) {
setGlobalVariable("main::!", e.getMessage());
exitCode = -1;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new PerlCompilerException("Command execution interrupted: " + e.getMessage());
PerlSignalQueue.checkPendingSignals();
Thread.interrupted();
} finally {
closeQuietly(reader);
closeQuietly(errorReader);
Expand Down
6 changes: 0 additions & 6 deletions src/main/java/org/perlonjava/runtime/operators/Time.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,7 @@ public static RuntimeScalar alarm(int ctx, RuntimeBase... args) {
* This method should be called at safe execution points in the interpreter.
*/
public static void checkPendingSignals() {
// Process any queued signals
PerlSignalQueue.processSignals();

// Clear interrupt flag if it was set by alarm
if (Thread.interrupted()) {
// The interrupt was handled via signal processing
}
}

/**
Expand Down
Loading