diff --git a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java index 27c4c3925..1604956e6 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java @@ -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); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java index 7389af20b..ca645fe0a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileBinaryOperatorHelper.java @@ -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); diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java index c865e32b7..d208a6076 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileOperator.java @@ -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; diff --git a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java index fbfd281aa..5673e65d8 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Disassemble.java @@ -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++]; diff --git a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java index 83e089e45..edf1ba260 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java +++ b/src/main/java/org/perlonjava/backend/bytecode/InlineOpcodeHandler.java @@ -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 diff --git a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java index 300c677a8..d47a32f9b 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java +++ b/src/main/java/org/perlonjava/backend/bytecode/Opcodes.java @@ -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 } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java index 330961f97..15b300a65 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java @@ -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); } diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index e707a84bc..3b20f5ed9 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -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). @@ -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() { diff --git a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java index a1f34bd6a..1571a84f1 100644 --- a/src/main/java/org/perlonjava/runtime/operators/MathOperators.java +++ b/src/main/java/org/perlonjava/runtime/operators/MathOperators.java @@ -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); + } + } } \ No newline at end of file diff --git a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java index 0a072b155..296917bf1 100644 --- a/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java +++ b/src/main/java/org/perlonjava/runtime/operators/OperatorHandler.java @@ -39,6 +39,16 @@ public record OperatorHandler(String className, String methodName, int methodTyp put("**_warn", "powWarn", "org/perlonjava/runtime/operators/MathOperators", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); put("unaryMinus_warn", "unaryMinusWarn", "org/perlonjava/runtime/operators/MathOperators", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); + // NoOverload variants - used when 'no overloading' pragma is in effect + // These bypass overload dispatch entirely (blessed refs -> refaddr-like numify) + put("+_noOverload", "addNoOverload", "org/perlonjava/runtime/operators/MathOperators"); + put("-_noOverload", "subtractNoOverload", "org/perlonjava/runtime/operators/MathOperators"); + put("*_noOverload", "multiplyNoOverload", "org/perlonjava/runtime/operators/MathOperators"); + put("/_noOverload", "divideNoOverload", "org/perlonjava/runtime/operators/MathOperators"); + put("%_noOverload", "modulusNoOverload", "org/perlonjava/runtime/operators/MathOperators"); + put("**_noOverload", "powNoOverload", "org/perlonjava/runtime/operators/MathOperators", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); + put("unaryMinus_noOverload", "unaryMinusNoOverload", "org/perlonjava/runtime/operators/MathOperators", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); + put("^^", "xor", "org/perlonjava/runtime/operators/Operator", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); put("xor", "xor", "org/perlonjava/runtime/operators/Operator", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;"); @@ -425,6 +435,18 @@ public static OperatorHandler getWarn(String operator) { return warnHandler != null ? warnHandler : operatorHandlers.get(operator); } + /** + * Retrieves the NoOverload variant of an OperatorHandler if available. + * For operators like + and -, returns the noOverload variant (addNoOverload, ...) + * that bypasses overload dispatch. Used when {@code no overloading} is in effect. + * + * @param operator The operator symbol. + * @return The NoOverload variant OperatorHandler, or null if no variant exists. + */ + public static OperatorHandler getNoOverload(String operator) { + return operatorHandlers.get(operator + "_noOverload"); + } + /** * Gets the class name containing the method associated with the operator. * diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java index 5217bb584..b935eca25 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java @@ -243,6 +243,18 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { Mro.initialize(); // mro functions available without 'use mro' Vars.initialize(); Subs.initialize(); + // Register XSLoader first: several modules below (and pragmas loaded during + // their .pm compilation such as 'use strict' inside Exporter.pm) call + // XSLoader::load at BEGIN time. Defining it up-front avoids an "Undefined + // subroutine &XSLoader::load" failure in the interpreter backend, where + // require() actually executes the required file's top-level (including + // the XSLoader::load call in strict.pm) before XSLoader.initialize() would + // otherwise have run. + // Note: XSLoader MUST be initialized before DynaLoader.initialize() because + // DynaLoader's initializeExporter() triggers require(Exporter.pm), which + // does `use strict;`, which (re)compiles strict.pm, which calls XSLoader::load. + XSLoader.initialize(); + DynaLoader.initialize(); Builtin.initialize(); Base.initialize(); Symbol.initialize(); @@ -275,8 +287,6 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { IOHandle.initialize(); // IO::Handle methods (_sync, _error, etc.) Version.initialize(); // Initialize version module for version objects Attributes.initialize(); // attributes:: XS-equivalent functions (used by attributes.pm) - DynaLoader.initialize(); - XSLoader.initialize(); // XSLoader will load other classes on-demand // Filter::Util::Call will be loaded via XSLoader when needed // Reset method cache after initializing UNIVERSAL diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java index 25e46d5fc..c66e7113a 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java @@ -390,6 +390,34 @@ public RuntimeScalar getNumberLarge() { }; } + /** + * Convert to number without triggering overload dispatch. + * Used by {@code no overloading} pragma to bypass the {@code 0+} overload. + * For blessed references, returns the identity hash code as an integer. + */ + public RuntimeScalar getNumberNoOverload() { + return switch (type) { + case INTEGER, DOUBLE -> this; + case STRING, BYTE_STRING -> NumberParser.parseNumber(this); + case UNDEF -> scalarZero; + case VSTRING -> NumberParser.parseNumber(this); + case BOOLEAN -> (boolean) value ? scalarOne : scalarZero; + case GLOB -> scalarOne; + case JAVAOBJECT -> value != null ? scalarOne : scalarZero; + case TIED_SCALAR -> this.tiedFetch().getNumberNoOverload(); + case READONLY_SCALAR -> ((RuntimeScalar) this.value).getNumberNoOverload(); + case DUALVAR -> ((DualVar) this.value).numericValue(); + default -> { + // For references (blessed or not), return the identity hash code + // of the referent, matching Scalar::Util::refaddr semantics. + if (value != null) { + yield new RuntimeScalar(System.identityHashCode(value)); + } + yield scalarZero; + } + }; + } + /** * Converts scalar to number with uninitialized value warning. * Called when 'use warnings "uninitialized"' is in effect.