Skip to content

Implement dead code elimination for constant conditionals#369

Open
fglock wants to merge 12 commits intomasterfrom
feature/dead-code-elimination
Open

Implement dead code elimination for constant conditionals#369
fglock wants to merge 12 commits intomasterfrom
feature/dead-code-elimination

Conversation

@fglock
Copy link
Owner

@fglock fglock commented Mar 24, 2026

Summary

This PR implements dead code elimination for constant conditionals, enabling CPAN modules like IPC::System::Simple and File::ShareDir to work correctly.

Problem

CPAN modules often use patterns like:

use constant WINDOWS => 0;
if (WINDOWS) {
    my $x = UNDEFINED_BAREWORD;  # Windows-specific code
}

Native Perl optimizes away the dead if branch at compile time, so undefined barewords in dead code don't cause errors. PerlOnJava was compiling both branches, causing "Bareword not allowed" errors.

Solution

  1. constant.pm: Updated to store constants directly in the stash as references ($stash->{$name} = \$value) like native Perl's constant.pm, instead of creating subroutines. This ensures RuntimeStashEntry sets constantValue on the RuntimeCode.

  2. EmitStatement.emitIf(): Added getConstantConditionValue() method to detect compile-time constant conditions (literal numbers, strings, or constant subroutine calls) and skip dead branches entirely.

  3. BytecodeCompiler.visit(IfNode): Same dead code elimination for the bytecode interpreter backend.

  4. POSIX.pm: Export additional constants (O_RDWR, O_CREAT, WNOHANG, WUNTRACED) needed by File::ShareDir.

  5. Internals::SvREADONLY: Fixed corruption of array/hash references when called with SvREADONLY($arrayref, 1). Now properly handles ARRAYREFERENCE, HASHREFERENCE, REFERENCE, CODE, and GLOBREFERENCE types by leaving them unchanged.

Test Results

  • All 163 unit tests pass (100% pass rate)
  • Works for if, unless, elsif
  • Works for numeric constants (0, 1), string constants ("", "yes"), and hash-style use constant
  • File::ShareDir: All 54 subtests now pass (was failing on POSIX constants)
  • use constant ARR => [1,2,3] now works correctly

Test plan

  • make passes
  • Unit tests pass
  • Manual testing with IPC::System::Simple-like patterns
  • File::ShareDir tests pass

Generated with Devin

fglock and others added 12 commits March 24, 2026 18:58
This enables CPAN modules like IPC::System::Simple to work correctly
when they use patterns like:

  use constant WINDOWS => 0;
  if (WINDOWS) {
      my $x = UNDEFINED_BAREWORD;  # No longer causes compile error
  }

Changes:
- constant.pm: Store constants directly in stash as references (like
  native Perl) instead of creating subroutines. This ensures
  RuntimeStashEntry sets constantValue on the RuntimeCode.
- EmitStatement.emitIf(): Add getConstantConditionValue() to detect
  compile-time constant conditions and skip dead branches entirely.
- BytecodeCompiler.visit(IfNode): Same dead code elimination for the
  bytecode interpreter backend.
- POSIX.pm: Export additional constants (O_RDWR, O_CREAT, WNOHANG,
  WUNTRACED) needed by File::ShareDir.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When Internals::SvREADONLY was called on a constant with a DOUBLE value,
the value was being lost because the svReadonly method did not handle the
DOUBLE type, causing it to fall through to the undef case.

- Added handling for RuntimeScalarType.DOUBLE in Internals.svReadonly()
- Added new RuntimeScalarReadOnly(double) constructor
- Also handle BYTE_STRING type like STRING

Fixes: use constant PI => 3.14; print PI; # now prints 3.14

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When SvREADONLY was called on a scalar containing an array or hash
reference, it would fall through to the undef case and corrupt the
reference. Now properly handles ARRAYREFERENCE, HASHREFERENCE,
REFERENCE, CODE, and GLOBREFERENCE types by leaving them unchanged.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…terals

The RuntimeList copy constructor was sharing the elements list with the
original instead of making a copy. When a constant subroutine returned
its value via 'new RuntimeList(constantValue)', and that value was then
consumed by addToArray() (which clears elements after adding them), the
original constant's value was destroyed.

This caused 'use constant FOO => -1' to return empty after
'use constant BAR => [FOO()]' was defined, because FOO()'s constantValue
elements were cleared.

Fix: Change RuntimeList(RuntimeList) constructor to create a shallow copy
of the elements ArrayList instead of sharing it.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The systemCommand method for backticks was shifting the exit code
by 8 bits again even though executeCommand already returns the
wait status (which is already shifted via waitForProcessWithStatus).

This caused $? to have a value like 8323072 (127 << 16) instead of
32512 (127 << 8) when a shell command returned exit code 127.

This also includes improvements to system/exec for indirect object
syntax (e.g., system { $program } @Args).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1. Backticks (qx) now bypass shell for simple commands without metacharacters,
   matching native Perl behavior. This allows proper detection of command not
   found errors (exit -1) instead of shell exit code 127.

2. Fix kill() to handle numeric string signals (e.g., 9 from @argv).
   Previously, string signals like 9 were incorrectly treated as named
   signals and failed lookup.

3. Fix Config.pm sig_name to include ZERO at index 0, matching Perl signal
   numbering where signal 0 is the null signal for process existence checks.

These fixes allow IPC::System::Simple tests t/03_signal.t, t/04_capture.t,
and t/06_fail.t to pass.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The isNonInterpolatingCharacter() method was incorrectly blocking
interpolation of valid Perl special punctuation variables like $?,
$|, $%, $", $\, and $#. These are all valid Perl special variables
that should interpolate in double-quoted strings.

The fix removes the incorrect character blocking - these special
variables are already handled correctly by IdentifierParser.parseComplexIdentifier()
which recognizes punctuation characters as valid variable names.

Added test cases for special punctuation variable interpolation in
string_interpolation.t to verify $?, $|, $%, $\, $(, and $) all
interpolate correctly.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When a negative offset overshoots the string start:
- If adjusted length is negative: warn and return undef
  Example: substr("hello", -10, 1) -> warn + undef
- If adjusted length is >= 0: clip to start, return substring (no warning)
  Example: substr("a", -2, 1) -> "" (no warning)
  Example: substr("a", -2, 2) -> "a" (no warning)

This fixes the substr warnings appearing in Test::More skip() calls.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1. Fix -e flag to accept code immediately after the flag (e.g., -e1)
   - Matches Perl behavior where -e1 means -e "1"
   - Also fixes -E flag with the same pattern

2. Add WINDOWS and VMS constants to bundled IPC::System::Simple
   - Required for compatibility with CPAN test suite
   - Allows tests like t/internal.t to run correctly

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1. RuntimeIO: Track 3-arg open form to bypass shell for single-element
   command lists. This ensures capturex("cmd with args") properly fails
   instead of invoking shell (matching Perl behavior for the x variants)

2. IPC::System::Simple: Add _spawn_or_die function and FAIL_INTERNAL
   constant for Windows-only operations that throw Internal error on
   non-Win32 platforms (required for t/internal.t)

3. Fix FAIL_INTERNAL capitalization to match expected Internal error

Test results: t/12_systemx.t now passes all 7 tests, t/internal.t passes
all 3 tests. Taint mode tests still fail/hang as expected since
PerlOnJava does not support taint mode.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The separateMode tracking in RuntimeIO.openPipe was too aggressive -
it treated ALL 3-arg pipe opens as no-shell mode, but open($fh, "-|", $cmd)
with a single command string should still use shell interpretation.

The proper fix for capturex shell bypass requires fork+exec which
PerlOnJava doesn't support. Document this as a known limitation.

This fixes regressions:
- io/crlf_through.t: 0/942 -> 942/942
- io/through.t: 0/942 -> 942/942
- io/open.t: 165/216 -> 186/216

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This enables capturex to properly bypass shell for single-arg commands.

Usage: open($fh, "-|:noshell", $cmd) treats $cmd as a literal program
name rather than a shell command. This emulates the behavior of native
Perl's fork+exec { $cmd } $cmd without requiring fork support.

IPC::System::Simple capturex now uses :noshell for single-arg calls,
making t/12_systemx.t pass all 7 tests while maintaining compatibility
with normal pipe open operations (io/open.t, io/through.t still pass).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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