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
81 changes: 81 additions & 0 deletions dev/tools/check_opcodes.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env perl
use strict;
use warnings;

# Check Opcodes.java for duplicate or out-of-order opcode numbers.
# Optionally renumber the absolute block (284+) to be contiguous after
# the last hand-assigned opcode.
#
# Usage (run from repo root):
# perl dev/tools/check_opcodes.pl src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
# perl dev/tools/check_opcodes.pl src/main/java/org/perlonjava/backend/bytecode/Opcodes.java --renumber

my ($file, $flag) = @ARGV;
die "Usage: $0 Opcodes.java [--renumber]\n" unless $file && -f $file;
my $renumber = ($flag // '') eq '--renumber';

my $content = do { open my $fh, '<', $file or die "Cannot open $file: $!"; local $/; <$fh> };

# Collect all public static final short NAME = NUMBER; (skip LASTOP+N expressions)
my (%name2num, %num2names);
while ($content =~ /\bpublic\s+static\s+final\s+short\s+(\w+)\s*=\s*(\d+)\s*;/g) {
my ($name, $num) = ($1, $2);
$name2num{$name} = $num;
push @{ $num2names{$num} }, $name;
}

# --- Duplicates ---
my @dups = sort { $a <=> $b } grep { @{ $num2names{$_} } > 1 } keys %num2names;
if (@dups) {
print "DUPLICATES:\n";
for my $n (@dups) {
print " $n => ", join(", ", @{ $num2names{$n} }), "\n";
}
} else {
print "No duplicates.\n";
}

# --- Range and gaps ---
my @sorted = sort { $a <=> $b } keys %num2names;
printf "Range: %d..%d (%d distinct values)\n", $sorted[0], $sorted[-1], scalar @sorted;

my @gaps;
for my $i (1 .. $#sorted) {
my ($prev, $cur) = @sorted[ $i - 1, $i ];
push @gaps, sprintf(" %d..%d (gap of %d)", $prev, $cur, $cur - $prev - 1) if $cur - $prev > 1;
}
if (@gaps) {
print "GAPS:\n";
print "$_\n" for @gaps;
} else {
print "No gaps.\n";
}

# --- Renumber ---
if ($renumber) {
# Find the last opcode below 284 (the hand-assigned range)
my @low = sort { $a <=> $b } grep { $_ < 284 } keys %num2names;
my $next = ($low[-1] // 283) + 1;
print "\nRenumbering 284+ starting at $next:\n";

my @high = sort { $a <=> $b } grep { $_ >= 284 } keys %num2names;
my %remap;
for my $old (@high) {
$remap{$old} = $next++;
}

for my $old (sort { $a <=> $b } keys %remap) {
my $new = $remap{$old};
next if $old == $new;
my @names = @{ $num2names{$old} };
printf " %d -> %d (%s)\n", $old, $new, join(", ", @names);
for my $name (@names) {
# Replace NAME = OLD; with NAME = NEW;
$content =~ s/\b(\Q$name\E\s*=\s*)\d+(\s*;)/$1$new$2/;
}
}

open my $fh, '>', $file or die "Cannot write $file: $!";
print $fh $content;
print "Written.\n";
}
1 change: 1 addition & 0 deletions src/main/java/org/perlonjava/app/cli/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@ private static int processLongSwitches(String[] args, CompilerOptions parsedArgs
// Enable disassemble mode
validateExclusiveOptions(parsedArgs, "disassemble");
parsedArgs.disassembleEnabled = true;
RuntimeCode.setDisassemble(true);
break;
case "--interpreter":
// Use bytecode interpreter instead of JVM compiler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,15 @@ String getCurrentPackage() {
return symbolTable.getCurrentPackage();
}

/**
* Set the compile-time package for name normalization.
* Called by eval STRING handlers to sync the package from the call site,
* so bare names like *named compile to FOO3::named instead of main::named.
*/
public void setCompilePackage(String packageName) {
symbolTable.setCurrentPackage(packageName, false);
}

/**
* Helper: Get all variable names in all scopes (for closure detection).
*/
Expand Down Expand Up @@ -536,7 +545,8 @@ public InterpretedCode compile(Node node, EmitterContext ctx) {
errorUtil, // Pass error util for line number lookup
strictOptions, // Strict flags for eval STRING inheritance
featureFlags, // Feature flags for eval STRING inheritance
warningFlags // Warning flags for eval STRING inheritance
warningFlags, // Warning flags for eval STRING inheritance
symbolTable.getCurrentPackage() // Compile-time package for eval STRING name resolution
);
}

Expand Down Expand Up @@ -2686,7 +2696,7 @@ void compileVariableReference(OperatorNode node, String op) {

// Add package prefix if not present
if (!varName.contains("::")) {
varName = "main::" + varName;
varName = getCurrentPackage() + "::" + varName;
}

// Allocate register for glob
Expand Down Expand Up @@ -2729,6 +2739,16 @@ void compileVariableReference(OperatorNode node, String op) {
} else if (op.equals("\\")) {
// Reference operator: \$x, \@x, \%x, \*x, etc.
if (node.operand != null) {
// Special case: \&name — CODE is already a reference type.
// Emit LOAD_GLOBAL_CODE directly without CREATE_REF, matching JVM compiler.
if (node.operand instanceof OperatorNode operandOp
&& operandOp.operator.equals("&")
&& operandOp.operand instanceof IdentifierNode) {
node.operand.accept(this);
// lastResultReg already holds the CODE scalar — no wrapping needed
return;
}

// Compile operand in LIST context to get the actual value
// Example: \@array should get a reference to the array itself,
// not its size (which would happen in SCALAR context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1558,7 +1558,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int rd = bytecode[pc++];
int listReg = bytecode[pc++];

RuntimeList list = (RuntimeList) registers[listReg];
// registers[listReg] may be a RuntimeList (from CREATE_LIST) or a
// RuntimeScalar (from LOAD_UNDEF when an empty ListNode is compiled).
RuntimeBase listBase = registers[listReg];
RuntimeList list = (listBase instanceof RuntimeList rl)
? rl : listBase.getList();
RuntimeScalar result = IOOperator.select(list, RuntimeContextType.SCALAR);
registers[rd] = result;
break;
Expand Down Expand Up @@ -1907,7 +1911,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
}

case Opcodes.GLOB_SLOT_GET: {
// Glob slot access: rd = glob.hashDerefGetNonStrict(key, "main")
// Glob slot access: rd = glob.hashDerefGetNonStrict(key, pkg)
// Format: GLOB_SLOT_GET rd globReg keyReg
pc = SlowOpcodeHandler.executeGlobSlotGet(bytecode, pc, registers);
break;
Expand Down Expand Up @@ -2020,6 +2024,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.SYSSEEK:
case Opcodes.TRUNCATE:
case Opcodes.READ:
case Opcodes.OPENDIR:
case Opcodes.READDIR:
case Opcodes.SEEKDIR:
pc = MiscOpcodeHandler.execute(opcode, bytecode, pc, registers);
break;

Expand Down Expand Up @@ -3035,25 +3042,25 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc,
RuntimeBase[] registers) {
switch (opcode) {
case Opcodes.CHOWN:
return SlowOpcodeHandler.executeChown(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.CHOWN, bytecode, pc, registers);
case Opcodes.WAITPID:
return SlowOpcodeHandler.executeWaitpid(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.WAITPID, bytecode, pc, registers);
case Opcodes.FORK:
return SlowOpcodeHandler.executeFork(bytecode, pc, registers);
case Opcodes.GETPPID:
return SlowOpcodeHandler.executeGetppid(bytecode, pc, registers);
case Opcodes.GETPGRP:
return SlowOpcodeHandler.executeGetpgrp(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.GETPGRP, bytecode, pc, registers);
case Opcodes.SETPGRP:
return SlowOpcodeHandler.executeSetpgrp(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.SETPGRP, bytecode, pc, registers);
case Opcodes.GETPRIORITY:
return SlowOpcodeHandler.executeGetpriority(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.GETPRIORITY, bytecode, pc, registers);
case Opcodes.SETPRIORITY:
return SlowOpcodeHandler.executeSetpriority(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.SETPRIORITY, bytecode, pc, registers);
case Opcodes.GETSOCKOPT:
return SlowOpcodeHandler.executeGetsockopt(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.GETSOCKOPT, bytecode, pc, registers);
case Opcodes.SETSOCKOPT:
return SlowOpcodeHandler.executeSetsockopt(bytecode, pc, registers);
return MiscOpcodeHandler.execute(Opcodes.SETSOCKOPT, bytecode, pc, registers);
case Opcodes.SYSCALL:
return SlowOpcodeHandler.executeSyscall(bytecode, pc, registers);
case Opcodes.SEMGET:
Expand Down
88 changes: 23 additions & 65 deletions src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -856,25 +856,23 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode

int rd = bytecodeCompiler.allocateRegister();

if (node.operand != null && node.operand instanceof ListNode) {
// select FILEHANDLE or select() with arguments
// Compile the operand (ListNode containing filehandle ref)
boolean hasArgs = node.operand instanceof ListNode ln && !ln.elements.isEmpty();
if (hasArgs) {
// select FILEHANDLE or select(RBITS,WBITS,EBITS,TIMEOUT) with arguments
node.operand.accept(bytecodeCompiler);
int listReg = bytecodeCompiler.lastResultReg;

// Emit SELECT opcode
bytecodeCompiler.emitWithToken(Opcodes.SELECT, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(listReg);
} else {
// select() with no arguments - returns current filehandle
// Create empty list
bytecodeCompiler.emit(Opcodes.CREATE_LIST);
// select() with no arguments (or empty ListNode from print-without-filehandle)
// Must emit CREATE_LIST so SELECT receives a RuntimeList, not a RuntimeScalar
int listReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.CREATE_LIST);
bytecodeCompiler.emitReg(listReg);
bytecodeCompiler.emit(0); // count = 0

// Emit SELECT opcode
bytecodeCompiler.emitWithToken(Opcodes.SELECT, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(listReg);
Expand Down Expand Up @@ -2591,62 +2589,6 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emitWithToken(Opcodes.GETPPID, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.lastResultReg = rd;
} else if (op.equals("getpgrp")) {
// getpgrp($pid) - returns process group
// Format: GETPGRP rd pidReg
if (node.operand != null) {
node.operand.accept(bytecodeCompiler);
int pidReg = bytecodeCompiler.lastResultReg;
int rd = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emitWithToken(Opcodes.GETPGRP, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(pidReg);
bytecodeCompiler.lastResultReg = rd;
} else {
bytecodeCompiler.throwCompilerException("getpgrp requires an argument");
}
} else if (op.equals("setpgrp")) {
// setpgrp($pid, $pgrp) - sets process group
// Format: SETPGRP pidReg pgrpReg
if (node.operand instanceof ListNode) {
ListNode list = (ListNode) node.operand;
if (list.elements.size() >= 2) {
list.elements.get(0).accept(bytecodeCompiler);
int pidReg = bytecodeCompiler.lastResultReg;
list.elements.get(1).accept(bytecodeCompiler);
int pgrpReg = bytecodeCompiler.lastResultReg;
bytecodeCompiler.emitWithToken(Opcodes.SETPGRP, node.getIndex());
bytecodeCompiler.emitReg(pidReg);
bytecodeCompiler.emitReg(pgrpReg);
bytecodeCompiler.lastResultReg = -1; // No return value
} else {
bytecodeCompiler.throwCompilerException("setpgrp requires two arguments");
}
} else {
bytecodeCompiler.throwCompilerException("setpgrp requires two arguments");
}
} else if (op.equals("getpriority")) {
// getpriority($which, $who) - returns process priority
// Format: GETPRIORITY rd whichReg whoReg
if (node.operand instanceof ListNode) {
ListNode list = (ListNode) node.operand;
if (list.elements.size() >= 2) {
list.elements.get(0).accept(bytecodeCompiler);
int whichReg = bytecodeCompiler.lastResultReg;
list.elements.get(1).accept(bytecodeCompiler);
int whoReg = bytecodeCompiler.lastResultReg;
int rd = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emitWithToken(Opcodes.GETPRIORITY, node.getIndex());
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(whichReg);
bytecodeCompiler.emitReg(whoReg);
bytecodeCompiler.lastResultReg = rd;
} else {
bytecodeCompiler.throwCompilerException("getpriority requires two arguments");
}
} else {
bytecodeCompiler.throwCompilerException("getpriority requires two arguments");
}
} else if (op.equals("atan2")) {
// atan2($y, $x) - returns arctangent of y/x
// Format: ATAN2 rd rs1 rs2
Expand Down Expand Up @@ -2729,7 +2671,12 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
op.equals("sysopen") || op.equals("socket") || op.equals("bind") ||
op.equals("connect") || op.equals("listen") || op.equals("write") ||
op.equals("formline") || op.equals("printf") || op.equals("accept") ||
op.equals("sysseek") || op.equals("truncate") || op.equals("read")) {
op.equals("sysseek") || op.equals("truncate") || op.equals("read") ||
op.equals("chown") || op.equals("waitpid") ||
op.equals("setsockopt") || op.equals("getsockopt") ||
op.equals("getpgrp") || op.equals("setpgrp") ||
op.equals("getpriority") || op.equals("setpriority") ||
op.equals("opendir") || op.equals("readdir") || op.equals("seekdir")) {
// Generic handler for operators that take arguments and call runtime methods
// Format: OPCODE rd argsReg ctx
// argsReg must be a RuntimeList
Expand Down Expand Up @@ -2795,6 +2742,17 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
case "sysseek" -> Opcodes.SYSSEEK;
case "truncate" -> Opcodes.TRUNCATE;
case "read" -> Opcodes.READ;
case "chown" -> Opcodes.CHOWN;
case "waitpid" -> Opcodes.WAITPID;
case "setsockopt" -> Opcodes.SETSOCKOPT;
case "getsockopt" -> Opcodes.GETSOCKOPT;
case "getpgrp" -> Opcodes.GETPGRP;
case "setpgrp" -> Opcodes.SETPGRP;
case "getpriority" -> Opcodes.GETPRIORITY;
case "setpriority" -> Opcodes.SETPRIORITY;
case "opendir" -> Opcodes.OPENDIR;
case "readdir" -> Opcodes.READDIR;
case "seekdir" -> Opcodes.SEEKDIR;
default -> throw new IllegalStateException("Unexpected operator: " + op);
};

Expand Down
Loading