Skip to content

fix(overload): honor no overloading for arithmetic ops; load XSLoader early#520

Merged
fglock merged 1 commit intomasterfrom
fix/hash-ordered-no-overloading
Apr 21, 2026
Merged

fix(overload): honor no overloading for arithmetic ops; load XSLoader early#520
fglock merged 1 commit intomasterfrom
fix/hash-ordered-no-overloading

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 21, 2026

Summary

Three related fixes surfaced while investigating ./jcpan -t Hash::Ordered:

1. Honor no overloading for arithmetic operators

Hash::Ordered defines its numification overload as:

sub _numify { no overloading; 0+$_[0] }

PerlOnJava honored HINT_NO_AMAGIC (the compile-time flag set by no overloading) only for string concatenation and join. Arithmetic ops (+, -, *, /, %, **, unary -) still went through the overload dispatch machinery, so 0+$_[0] recursed into the 0+ overload and blew the JVM stack. Hash::Ordered's t/basic.t died with Deep recursion on subroutine "Hash::Ordered::_numify" / StackOverflowError.

Mirrors the existing stringConcatNoOverload / joinNoOverload pattern:

  • RuntimeScalar.getNumberNoOverload() — bypasses Overload.numify, returning refaddr-style integer for blessed refs.
  • MathOperators.{add,subtract,multiply,divide,modulus,pow,unaryMinus}NoOverload.
  • OperatorHandler.getNoOverload(op) + registered _noOverload variants.
  • EmitOperator.emitOperator prefers the NoOverload variant when HINT_NO_AMAGIC is set in scope.

2. Bytecode-interpreter parity

Added NoOverload opcodes so the interpreter backend honors no overloading for arithmetic too:

  • New opcodes ADD_NO_OVERLOAD..POW_NO_OVERLOAD, NEG_NO_OVERLOAD.
  • CompileBinaryOperatorHelper / CompileOperator emit them when isNoOverloadingEnabled().
  • InlineOpcodeHandler / BytecodeInterpreter dispatch; Disassemble prints.

3. XSLoader::load undefined at startup on the interpreter backend

Every ./jperl --interpreter ... invocation died with:

Undefined subroutine &XSLoader::load called at jar:PERL5LIB/strict.pm line 12

Root cause: in GlobalContext.initializeGlobals(), DynaLoader.initialize() triggered require(Exporter.pm)use strict;require(strict.pm), whose top-level calls XSLoader::load(...). On the interpreter backend that top-level actually runs (the JVM backend caches code refs differently and masks the issue), but XSLoader.initialize() was scheduled after DynaLoader — so the global codeRef was only a symbolic-reference stub with no method handle. defined() returned true while apply() threw "Undefined subroutine".

Fix: move XSLoader.initialize() ahead of DynaLoader.initialize() (and before Builtin.initialize()), with a comment explaining why.

Test plan

  • ./jcpan -t Hash::Ordered — before: 1/4 test programs failed (StackOverflowError); after: all 112 tests pass.
  • ./jperl --interpreter -e '...' — now runs; previously died on startup.
  • ./jperl --interpreter /tmp/test_no_overloading.pl — exercises the new NEG_NO_OVERLOAD / arithmetic NoOverload opcodes on the interpreter.
  • make — full unit test suite passes.

Generated with Devin

…er early

Three related fixes uncovered while investigating `./jcpan -t Hash::Ordered`:

1. `no overloading` was only honored for string concatenation and `join`.
   Arithmetic ops (`+`, `-`, `*`, `/`, `%`, `**`, unary `-`) still dispatched
   through the overload machinery, so `sub _numify { no overloading; 0+$_[0] }`
   recursed into its own `0+` overload and blew the JVM stack. This caused
   Hash::Ordered's t/basic.t to die with `Deep recursion on subroutine
   "Hash::Ordered::_numify"` / `StackOverflowError`.

   Fix: mirror the existing `stringConcatNoOverload` / `joinNoOverload` pattern
   for arithmetic.
   - `RuntimeScalar.getNumberNoOverload()` — returns refaddr-style integer for
     blessed refs, bypassing `Overload.numify`.
   - `MathOperators.{add,subtract,multiply,divide,modulus,pow,unaryMinus}NoOverload`.
   - `OperatorHandler.getNoOverload(op)` + registered `_noOverload` variants for
     `+ - * / % ** unaryMinus`.
   - `EmitOperator.emitOperator` prefers the NoOverload variant when
     `HINT_NO_AMAGIC` is set in the current lexical scope (same mechanism
     already used for concat/join).

2. Bytecode-interpreter parity for the above. The interpreter had no
   equivalent of the JVM-backend's NoOverload dispatch for arithmetic.
   - New opcodes `ADD_NO_OVERLOAD`..`POW_NO_OVERLOAD`, `NEG_NO_OVERLOAD`.
   - `CompileBinaryOperatorHelper` and `CompileOperator` emit them when
     `isNoOverloadingEnabled()` is true.
   - `InlineOpcodeHandler` implements the runtime handlers; `BytecodeInterpreter`
     dispatches them; `Disassemble` prints them.

3. `XSLoader::load` was undefined at startup on the interpreter backend,
   which made *every* interpreter invocation die with
   `Undefined subroutine &XSLoader::load called at jar:PERL5LIB/strict.pm line 12`.
   Root cause: in `GlobalContext.initializeGlobals()`, `DynaLoader.initialize()`
   triggered `require(Exporter.pm)` → `use strict;` → `require(strict.pm)`,
   whose top-level calls `XSLoader::load(...)`. On the interpreter backend that
   top-level actually runs (the JVM backend caches the code ref differently and
   masks the issue). But `XSLoader.initialize()` was scheduled *after* DynaLoader,
   so the global codeRef was only a symbolic-reference stub with no method
   handle — `defined()` returned true while `apply()` threw
   `Undefined subroutine &XSLoader::load`.

   Fix: move `XSLoader.initialize()` ahead of `DynaLoader.initialize()` (and
   before `Builtin.initialize()`), with a comment explaining the ordering
   requirement.

Verification:
- `./jcpan -t Hash::Ordered` — before: 1/4 test programs failed with
  `StackOverflowError`; after: all 112 tests pass.
- `./jperl --interpreter -e '...'` — now runs (previously died on startup).
- `./jperl --interpreter /tmp/test_no_overloading.pl` — exercises the new
  `NEG_NO_OVERLOAD` / arithmetic NoOverload opcodes in the interpreter.
- `make` — full unit test suite passes.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit ef6eaab into master Apr 21, 2026
2 checks passed
@fglock fglock deleted the fix/hash-ordered-no-overloading branch April 21, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant