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 @@ -624,6 +624,29 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
pc = InlineOpcodeHandler.executeNegScalar(bytecode, pc, registers);
}

// Arithmetic without overload dispatch (no overloading pragma)
case Opcodes.ADD_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeAddNoOverload(bytecode, pc, registers);
}
case Opcodes.SUB_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeSubNoOverload(bytecode, pc, registers);
}
case Opcodes.MUL_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeMulNoOverload(bytecode, pc, registers);
}
case Opcodes.DIV_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeDivNoOverload(bytecode, pc, registers);
}
case Opcodes.MOD_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeModNoOverload(bytecode, pc, registers);
}
case Opcodes.POW_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executePowNoOverload(bytecode, pc, registers);
}
case Opcodes.NEG_NO_OVERLOAD -> {
pc = InlineOpcodeHandler.executeNegNoOverload(bytecode, pc, registers);
}

// Specialized unboxed operations (rare optimizations)
case Opcodes.ADD_SCALAR_INT -> {
pc = InlineOpcodeHandler.executeAddScalarInt(bytecode, pc, registers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,48 @@ public static int compileBinaryOperatorSwitch(BytecodeCompiler bytecodeCompiler,
int rd = bytecodeCompiler.allocateOutputRegister();

// Emit opcode based on operator
boolean noOverload = bytecodeCompiler.isNoOverloadingEnabled();
switch (operator) {
case "+" -> {
bytecodeCompiler.emit(Opcodes.ADD_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.ADD_NO_OVERLOAD : Opcodes.ADD_SCALAR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "-" -> {
bytecodeCompiler.emit(Opcodes.SUB_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.SUB_NO_OVERLOAD : Opcodes.SUB_SCALAR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "*" -> {
bytecodeCompiler.emit(Opcodes.MUL_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.MUL_NO_OVERLOAD : Opcodes.MUL_SCALAR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "%" -> {
bytecodeCompiler.emit(bytecodeCompiler.isIntegerEnabled() ? Opcodes.INTEGER_MOD : Opcodes.MOD_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.MOD_NO_OVERLOAD
: (bytecodeCompiler.isIntegerEnabled() ? Opcodes.INTEGER_MOD : Opcodes.MOD_SCALAR));
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "/" -> {
bytecodeCompiler.emit(bytecodeCompiler.isIntegerEnabled() ? Opcodes.INTEGER_DIV : Opcodes.DIV_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.DIV_NO_OVERLOAD
: (bytecodeCompiler.isIntegerEnabled() ? Opcodes.INTEGER_DIV : Opcodes.DIV_SCALAR));
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "**" -> {
bytecodeCompiler.emit(Opcodes.POW_SCALAR);
bytecodeCompiler.emit(noOverload ? Opcodes.POW_NO_OVERLOAD : Opcodes.POW_SCALAR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
}
case "." -> {
bytecodeCompiler.emit(bytecodeCompiler.isNoOverloadingEnabled() ? Opcodes.CONCAT_NO_OVERLOAD : Opcodes.CONCAT);
bytecodeCompiler.emit(noOverload ? Opcodes.CONCAT_NO_OVERLOAD : Opcodes.CONCAT);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(rs1);
bytecodeCompiler.emitReg(rs2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.compileNode(node.operand, -1, RuntimeContextType.SCALAR);
int operandReg = bytecodeCompiler.lastResultReg;
int rd = bytecodeCompiler.allocateOutputRegister();
bytecodeCompiler.emit(Opcodes.NEG_SCALAR);
bytecodeCompiler.emit(bytecodeCompiler.isNoOverloadingEnabled() ? Opcodes.NEG_NO_OVERLOAD : Opcodes.NEG_SCALAR);
bytecodeCompiler.emitReg(rd);
bytecodeCompiler.emitReg(operandReg);
bytecodeCompiler.lastResultReg = rd;
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Disassemble.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,32 @@ public static String disassemble(InterpretedCode interpretedCode) {
int rsNeg = interpretedCode.bytecode[pc++];
sb.append("NEG_SCALAR r").append(rd).append(" = -r").append(rsNeg).append("\n");
break;
case Opcodes.ADD_NO_OVERLOAD:
case Opcodes.SUB_NO_OVERLOAD:
case Opcodes.MUL_NO_OVERLOAD:
case Opcodes.DIV_NO_OVERLOAD:
case Opcodes.MOD_NO_OVERLOAD:
case Opcodes.POW_NO_OVERLOAD: {
rd = interpretedCode.bytecode[pc++];
rs1 = interpretedCode.bytecode[pc++];
rs2 = interpretedCode.bytecode[pc++];
String op = switch (opcode) {
case Opcodes.ADD_NO_OVERLOAD -> "ADD_NO_OVERLOAD";
case Opcodes.SUB_NO_OVERLOAD -> "SUB_NO_OVERLOAD";
case Opcodes.MUL_NO_OVERLOAD -> "MUL_NO_OVERLOAD";
case Opcodes.DIV_NO_OVERLOAD -> "DIV_NO_OVERLOAD";
case Opcodes.MOD_NO_OVERLOAD -> "MOD_NO_OVERLOAD";
default -> "POW_NO_OVERLOAD";
};
sb.append(op).append(" r").append(rd).append(" = r").append(rs1).append(", r").append(rs2).append("\n");
break;
}
case Opcodes.NEG_NO_OVERLOAD: {
rd = interpretedCode.bytecode[pc++];
int rsNegNo = interpretedCode.bytecode[pc++];
sb.append("NEG_NO_OVERLOAD r").append(rd).append(" = -r").append(rsNegNo).append("\n");
break;
}
case Opcodes.ADD_SCALAR_INT:
rd = interpretedCode.bytecode[pc++];
int rs = interpretedCode.bytecode[pc++];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,90 @@ public static int executeNegScalar(int[] bytecode, int pc, RuntimeBase[] registe
return pc;
}

/**
* Arithmetic without overload dispatch.
* Used when {@code no overloading} is in effect at compile time.
* Format: OPCODE rd rs1 rs2
*/
public static int executeAddNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.addNoOverload(s1, s2);
return pc;
}

public static int executeSubNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.subtractNoOverload(s1, s2);
return pc;
}

public static int executeMulNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.multiplyNoOverload(s1, s2);
return pc;
}

public static int executeDivNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.divideNoOverload(s1, s2);
return pc;
}

public static int executeModNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.modulusNoOverload(s1, s2);
return pc;
}

public static int executePowNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs1 = bytecode[pc++];
int rs2 = bytecode[pc++];
RuntimeBase v1 = registers[rs1];
RuntimeBase v2 = registers[rs2];
RuntimeScalar s1 = (v1 instanceof RuntimeScalar) ? (RuntimeScalar) v1 : v1.scalar();
RuntimeScalar s2 = (v2 instanceof RuntimeScalar) ? (RuntimeScalar) v2 : v2.scalar();
registers[rd] = MathOperators.powNoOverload(s1, s2);
return pc;
}

public static int executeNegNoOverload(int[] bytecode, int pc, RuntimeBase[] registers) {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
registers[rd] = MathOperators.unaryMinusNoOverload((RuntimeScalar) registers[rs]);
return pc;
}

/**
* Addition with immediate: rd = rs + immediate
* Format: ADD_SCALAR_INT rd rs immediate
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -2279,6 +2279,19 @@ public class Opcodes {
*/
public static final short LOAD_UNDEF_READONLY = 473;

/**
* Arithmetic operators without overload dispatch.
* Used when {@code no overloading} is in effect at compile time.
* Format: OPCODE rd rs1 rs2 (NEG_NO_OVERLOAD: rd rs)
*/
public static final short ADD_NO_OVERLOAD = 474;
public static final short SUB_NO_OVERLOAD = 475;
public static final short MUL_NO_OVERLOAD = 476;
public static final short DIV_NO_OVERLOAD = 477;
public static final short MOD_NO_OVERLOAD = 478;
public static final short POW_NO_OVERLOAD = 479;
public static final short NEG_NO_OVERLOAD = 480;

private Opcodes() {
} // Utility class - no instantiation
}
16 changes: 12 additions & 4 deletions src/main/java/org/perlonjava/backend/jvm/EmitOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,20 @@ static void emitOperator(Node node, EmitterVisitor emitterVisitor) {
throw new PerlCompilerException(node.getIndex(), "Node must be OperatorNode or BinaryOperatorNode", emitterVisitor.ctx.errorUtil);
}

// Check if `no overloading` is active - prefer NoOverload variant when available
ScopedSymbolTable symbolTable = emitterVisitor.ctx.symbolTable;
boolean noOverloading = symbolTable != null &&
symbolTable.isStrictOptionEnabled(Strict.HINT_NO_AMAGIC);
OperatorHandler operatorHandler = noOverloading ? OperatorHandler.getNoOverload(operator) : null;

// Check if uninitialized warnings are enabled at compile time
// Use warn variant for zero-overhead when warnings disabled
boolean warnUninit = emitterVisitor.ctx.symbolTable.isWarningCategoryEnabled("uninitialized");
OperatorHandler operatorHandler = warnUninit
? OperatorHandler.getWarn(operator)
: OperatorHandler.get(operator);
if (operatorHandler == null) {
boolean warnUninit = emitterVisitor.ctx.symbolTable.isWarningCategoryEnabled("uninitialized");
operatorHandler = warnUninit
? OperatorHandler.getWarn(operator)
: OperatorHandler.get(operator);
}
if (operatorHandler == null) {
throw new PerlCompilerException(node.getIndex(), "Operator \"" + operator + "\" doesn't have a defined JVM descriptor", emitterVisitor.ctx.errorUtil);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public final class Configuration {
* Automatically populated by Gradle/Maven during build.
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String gitCommitId = "57b3c2940";
public static final String gitCommitId = "f8e0a8fc1";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand All @@ -48,7 +48,7 @@ public final class Configuration {
* Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at"
* DO NOT EDIT MANUALLY - this value is replaced at build time.
*/
public static final String buildTimestamp = "Apr 21 2026 11:07:07";
public static final String buildTimestamp = "Apr 21 2026 11:24:02";

// Prevent instantiation
private Configuration() {
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/org/perlonjava/runtime/operators/MathOperators.java
Original file line number Diff line number Diff line change
Expand Up @@ -1246,4 +1246,64 @@ public static RuntimeScalar not(RuntimeScalar runtimeScalar) {
default -> getScalarBoolean(!runtimeScalar.getBoolean());
};
}

// =====================================================================
// NoOverload variants - used when 'no overloading' pragma is in effect.
// These skip overload dispatch entirely and treat blessed references
// as if unblessed (using refaddr-style numeric conversion).
// =====================================================================

private static RuntimeScalar arith(RuntimeScalar a, RuntimeScalar b, int op) {
a = a.getNumberNoOverload();
b = b.getNumberNoOverload();
if (a.type == DOUBLE || b.type == DOUBLE) {
double x = a.getDouble();
double y = b.getDouble();
return switch (op) {
case 0 -> new RuntimeScalar(x + y);
case 1 -> new RuntimeScalar(x - y);
case 2 -> new RuntimeScalar(x * y);
case 3 -> new RuntimeScalar(x / y);
case 4 -> new RuntimeScalar(x % y);
case 5 -> new RuntimeScalar(Math.pow(x, y));
default -> throw new IllegalStateException();
};
}
long x = a.getLong();
long y = b.getLong();
try {
return switch (op) {
case 0 -> getScalarInt(Math.addExact(x, y));
case 1 -> getScalarInt(Math.subtractExact(x, y));
case 2 -> getScalarInt(Math.multiplyExact(x, y));
case 3 -> y != 0 && x % y == 0
? getScalarInt(x / y)
: new RuntimeScalar((double) x / (double) y);
case 4 -> y != 0 ? getScalarInt(x % y)
: new RuntimeScalar((double) x % (double) y);
case 5 -> new RuntimeScalar(Math.pow(x, y));
default -> throw new IllegalStateException();
};
} catch (ArithmeticException ignored) {
return new RuntimeScalar((double) x + (double) y);
}
}

public static RuntimeScalar addNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 0); }
public static RuntimeScalar subtractNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 1); }
public static RuntimeScalar multiplyNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 2); }
public static RuntimeScalar divideNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 3); }
public static RuntimeScalar modulusNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 4); }
public static RuntimeScalar powNoOverload(RuntimeScalar a, RuntimeScalar b) { return arith(a, b, 5); }

public static RuntimeScalar unaryMinusNoOverload(RuntimeScalar a) {
RuntimeScalar n = a.getNumberNoOverload();
if (n.type == DOUBLE) return new RuntimeScalar(-n.getDouble());
long v = n.getLong();
try {
return getScalarInt(Math.negateExact(v));
} catch (ArithmeticException ignored) {
return new RuntimeScalar(-(double) v);
}
}
}
Loading
Loading