Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
eecc892
fix: SCOPE_EXIT_CLEANUP type guards and GlobalDestruction CME
fglock Apr 10, 2026
df00d08
docs: update DBIx::Class plan with Phase 9 re-baseline after DESTROY/…
fglock Apr 10, 2026
3b9bb81
fix: prevent premature DESTROY by tracking named container local bind…
fglock Apr 10, 2026
a598143
fix: bundle Devel::GlobalDestruction with plain Exporter
fglock Apr 11, 2026
e0b7db7
feat: bundle DBI::Const::GetInfoType and related modules
fglock Apr 11, 2026
5f75cf4
docs: add Phase 10 full-suite re-baseline to DBIx::Class plan
fglock Apr 11, 2026
46eb4cd
docs: refine Phase 10 categorization with detailed failure analysis
fglock Apr 11, 2026
d34d2bc
fix: suppress MortalList flush during setFromList materialization
fglock Apr 11, 2026
61af173
docs: trim Phase 11 to focus on actionable next steps
fglock Apr 11, 2026
fe7d072
docs: update DBIx::Class plan with Step 11.2 failure analysis and rev…
fglock Apr 11, 2026
4f1ed14
fix: cascade hash/array cleanup for blessed objects without DESTROY
fglock Apr 11, 2026
088898a
docs: update DBIx::Class plan with Step 11.4 investigation and fix de…
fglock Apr 11, 2026
439bbcb
docs: update DBIx::Class plan with Step 11.4 findings and next steps
fglock Apr 11, 2026
43ea1d3
docs: Phase 12 plan — all DBIx::Class tests must pass
fglock Apr 11, 2026
a4c18ce
fix: DBI numeric formatting, DBI_DRIVER env, HandleError callback
fglock Apr 11, 2026
87de4f4
docs: add resource management warning to AGENTS.md
fglock Apr 11, 2026
82fb4b7
fix: suppress mortal flush on do-block scope exit
fglock Apr 11, 2026
56f1b38
fix: fire DESTROY for blessed objects when die unwinds through eval
fglock Apr 11, 2026
a5a1214
feat: DESTROY-on-die for blessed my-variables (Phase 13)
fglock Apr 11, 2026
4c375f0
fix: flush deferred DESTROY in void-context sub calls
fglock Apr 11, 2026
b77dc88
fix: DBI handle lifecycle — localBindingExists, finish, circular refs
fglock Apr 11, 2026
4a74280
fix: mortal mark/flush system for DESTROY timing and method chain tem…
fglock Apr 12, 2026
96c34a2
fix: release captures for ephemeral grep/map/sort/all/any block closures
fglock Apr 12, 2026
4e0d8e7
fix: defer refCount decrements in local array/hash restore
fglock Apr 12, 2026
e0a94c7
fix: clean up my-variable refCounts when method calls die in callCached
fglock Apr 12, 2026
05e4a32
fix: birth-track anonymous arrayrefs to prevent blessed object leaks
fglock Apr 12, 2026
2cf0f15
test: add regression tests for anonymous container DESTROY behavior
fglock Apr 12, 2026
30fb463
fix: prevent splice on @_ from prematurely destroying caller's objects
fglock Apr 12, 2026
db846e6
fix: prevent premature weak ref clearing for stash-installed closures
fglock Apr 12, 2026
ef424f7
fix: increase test JVM heap to 1g for code_too_large.t
fglock Apr 12, 2026
412ac26
docs: update DBIx::Class fix plan with Phase 15 issues
fglock Apr 12, 2026
0680977
fix: correct caller() frame counting when CORE::GLOBAL::caller is ove…
fglock Apr 12, 2026
f08d437
fix: caller() returns correct package for use/import through interpreter
fglock Apr 12, 2026
dddab71
feat: add wait operator to bytecode interpreter backend
fglock Apr 12, 2026
d52b345
docs: restructure DBIx::Class fix plan with three work directions
fglock Apr 12, 2026
d32de1d
fix: deferred capture cleanup enables DESTROY for captured blessed refs
fglock Apr 12, 2026
13a260e
fix: begin_work nested-txn check, $INC cleanup, selectcol_arrayref
fglock Apr 12, 2026
aed90fe
docs: update DBIx::Class fix plan with detailed analysis of remaining…
fglock Apr 12, 2026
e02e0f9
fix: implement DESTROY rescue detection for Schema self-save pattern
fglock Apr 12, 2026
bca73bd
fix: scope exit cleanup now follows Perl 5 reverse declaration order …
fglock Apr 13, 2026
aaae9ce
docs: compact DBIC design doc, add detailed GC fix plan
fglock Apr 13, 2026
4eb7632
fix: deferred weak-ref clearing for DESTROY-rescued objects
fglock Apr 13, 2026
7df81dc
fix: eliminate all DBIC GC failures + DBI RootClass support
fglock Apr 13, 2026
c2fdddb
docs: update DBIC design doc with final results (99.77%, 0 GC failures)
fglock Apr 13, 2026
beebccd
fix: next::method always uses C3 linearization (matches Perl 5)
fglock Apr 13, 2026
d6dd158
fix: clear weak refs on stash delete + fix B::REFCNT inflation
fglock Apr 13, 2026
17bd9e4
docs: update DBIC design doc with Fix 7 results (99.90%, 12 remaining)
fglock Apr 13, 2026
1d2128c
fix: DBI BYTE_STRING for DB strings + utf8::decode conditional UTF-8 …
fglock Apr 13, 2026
4b05e45
fix: release s///eg replacement closure captures to unblock DESTROY
fglock Apr 13, 2026
abcbb5b
fix: release interpreter closure captures at frame exit for DESTROY
fglock Apr 13, 2026
c65974e
fix: DBI UTF-8 round-trip and filehandle dup of closed handles
fglock Apr 13, 2026
cd5bbb5
docs: update DBIx::Class design doc with Fix 9 results
fglock Apr 13, 2026
d7a435d
fix: handle JVM VerifyError with interpreter fallback
fglock Apr 14, 2026
f6627da
fix: prevent premature DESTROY of hash element aliases during excepti…
fglock Apr 14, 2026
ad72557
fix: local $hashref->{key} now restores correctly after hash reassign…
fglock Apr 14, 2026
a13d6a3
fix: populate @DB::args in non-debug mode for caller() from package DB
fglock Apr 14, 2026
dbe9b87
docs: update design doc with F2-F5 completion and broad DBIC test res…
fglock Apr 14, 2026
2b2f7c7
fix: DESTROY rescue refCount tracking and glob stash hash access
fglock Apr 14, 2026
ff7ce8b
fix: document DESTROY rescue semantics and refCount inflation impact
fglock Apr 14, 2026
96be4a6
fix: DESTROY rescue protection for phantom chain in t/52leaks.t
fglock Apr 14, 2026
73739dd
fix: clear weak refs at refCount 0 even when localBindingExists block…
fglock Apr 14, 2026
10b7595
fix: add createAnonymousReference() for Storable/deserializer anonymo…
fglock Apr 18, 2026
3f13826
fix: cascade scope-exit cleanup when weak refs exist (not just blesse…
fglock Apr 18, 2026
5179414
docs: update dbix_class.md with Fix 10e/10f and remaining 52leaks ana…
fglock Apr 18, 2026
4da6ede
fix: base.pm considers package loaded if @ISA or $VERSION set (not ju…
fglock Apr 18, 2026
ed20075
docs: document Fix 10g (base.pm loaded-check), hri.t fix, and non-bug…
fglock Apr 18, 2026
29af4a7
fix: flock() allows multiple shared locks on same file from same JVM
fglock Apr 18, 2026
c8c38ca
fix: fork() doesn't emit "1..0 # SKIP" after tests have run
fglock Apr 18, 2026
afbc081
fix: DBI stores mutable scalars for user-writable attributes
fglock Apr 18, 2026
d254c09
fix: stringify overload self-reference falls back to default ref form
fglock Apr 18, 2026
a2efcd9
fix: preserve @_ snapshot so @DB::args survives shift() in callees
fglock Apr 18, 2026
c4a1571
docs: compress dbix_class.md with session's fixes 10h-10l and current…
fglock Apr 18, 2026
ced1821
fix: eq/ne throw "no method found" when overload fallback not permitted
fglock Apr 18, 2026
e5a006b
docs: update dbix_class.md with Fix 10m (eq/ne fallback) and DESTROY …
fglock Apr 18, 2026
ab299f0
docs(dbic): document why t/52leaks and txn_scope_guard#18 are blocked
fglock Apr 19, 2026
5830a34
docs(design): refcount alignment plan for Perl-compatible DESTROY/weaken
fglock Apr 19, 2026
746e36f
Phase 0: diagnostic infrastructure for refcount alignment
fglock Apr 19, 2026
c021b1b
Phase 1: verify scope-exit decrement parity with Perl
fglock Apr 19, 2026
9cec177
Phase 2: @DB::args as aliased array (non-counting refs)
fglock Apr 19, 2026
f371be2
Phase 3: DESTROY state machine with resurrection detection
fglock Apr 19, 2026
b6fb1c3
Phase 4: opt-in reachability walker for leak-tracer compatibility
fglock Apr 19, 2026
3e71db4
Phase 5: refine Internals::SvREFCNT to report 1 for fresh refs
fglock Apr 19, 2026
518c654
Phase 6: CPAN validation snapshot
fglock Apr 19, 2026
d435381
Phase 7: interpreter backend parity
fglock Apr 19, 2026
635d424
Follow-up: DBIC 52leaks fully passes (0 real failures)
fglock Apr 19, 2026
b39c327
Broader CPAN validation: 269/270 DBIC test files pass
fglock Apr 19, 2026
90de05a
docs(weaken-destroy): bring architecture guide up to date
fglock Apr 19, 2026
7083189
docs(design): final plan for DBIC t/52leaks.t tests 12-20
fglock Apr 19, 2026
5813ea6
Phase B1: ScalarRefRegistry for lexical-aware reachability walker
fglock Apr 19, 2026
5e40615
Phase B2: revert auto-trigger attempts; update plan
fglock Apr 19, 2026
e326fdc
docs(weaken-destroy): document Phase B1 (ScalarRefRegistry) and B2 st…
fglock Apr 19, 2026
28bd736
Phase B2a: module-init-aware auto-trigger for weak-ref sweep
fglock Apr 19, 2026
da301ca
RuntimeCode.apply: iterative trampoline for goto &func chains
fglock Apr 19, 2026
349e820
docs: Phase C trampoline fix results + 5s throttle rationale
fglock Apr 19, 2026
ea39d29
Phase D: undef-of-blessed walker trigger (safety net)
fglock Apr 19, 2026
578b4ba
diag: enhance jperl_trace_to with scalar identity + direct-holder dump
fglock Apr 19, 2026
87ed18e
Phase E: unregister block-scoped my-vars on scope exit
fglock Apr 19, 2026
09b4381
Phase E: walker skips refCountOwned=false scalars + doc update
fglock Apr 19, 2026
55b34ea
Revert "Phase E: walker skips refCountOwned=false scalars + doc update"
fglock Apr 20, 2026
8c2d118
docs: Phase E2 investigation results + root cause identification
fglock Apr 20, 2026
1f49a96
docs: Phase F plan — narrow interpreter closure capture
fglock Apr 20, 2026
ad7d329
Phase F: narrow interpreter closure capture to referenced lexicals
fglock Apr 20, 2026
e200d2a
docs: Phase F result + remaining work
fglock Apr 20, 2026
74a2fcb
docs: Phase G plan — close basic result_source_handle leak
fglock Apr 20, 2026
e8cec9a
Phase G: release Storable arg-push refCount bumps
fglock Apr 20, 2026
2746ad3
docs: Phase G result — 52leaks.t unpatched goal achieved
fglock Apr 20, 2026
388b4a5
docs: Phase H plan — close remaining ./jcpan -t DBIx::Class issues
fglock Apr 20, 2026
c23421c
docs: compress plan doc (1688 -> 520 lines); preserve lessons learned
fglock Apr 20, 2026
2e5b853
Phase H: fix t/60core.t hang — walker skips unblessed containers in q…
fglock Apr 20, 2026
6501ddb
Phase H: fix t/cdbi/sweet/08pager.t END-block hang
fglock Apr 20, 2026
3c41546
docs: Phase H H2+H3 result — fixed 60core.t and 08pager.t hangs
fglock Apr 20, 2026
58427ab
Phase H: fix t/storage/error.t test 49 — Schema DESTROY cascade
fglock Apr 20, 2026
87c9d97
docs: Phase H H4 result — 99.92% jcpan pass rate, 1 file failing
fglock Apr 20, 2026
a32e789
Phase H: fix 52leaks.t blessed-object leaks — drain rescuedObjects in…
fglock Apr 20, 2026
f44c19d
docs: Phase H H1 result — 99.985% jcpan pass, 2/13804 subtests failing
fglock Apr 20, 2026
dc0b003
docs: add Phase I plan — close last 2 t/52leaks.t failures
fglock Apr 20, 2026
f28a036
docs: Phase I investigation — recommend deferring 2 residual failures
fglock Apr 20, 2026
1f02e0f
Phase I: fix 52leaks.t ARRAY leak + 60core.t regression
fglock Apr 20, 2026
b627a70
Phase I: fix 52leaks.t ALL tests + 08pager.t END-block regression
fglock Apr 20, 2026
8775b9b
docs: Phase I result — ARRAY leak fixed via targeted walker skip rules
fglock Apr 20, 2026
3da888f
chore: update Configuration.java git commit id
fglock Apr 20, 2026
93eee0c
docs: expand Phase plan scope to DBIx::Class + Moo + Template
fglock Apr 20, 2026
9e7d511
Phase I: walker skips stale lexical-registry seeds (scopeExited, !ref…
fglock Apr 20, 2026
652a766
Phase I: MyVarCleanupStack.isLive() for precise live-lexical filter
fglock Apr 20, 2026
1c6d76c
docs: Phase I final state — 99.9928% jcpan pass, 1 residual Artist leak
fglock Apr 20, 2026
8694f7e
Phase I: walker skip MortalList.deferredCaptures scalars
fglock Apr 20, 2026
aea1b05
Phase I: auto-sweep fires DESTROY — fixes final Artist leak
fglock Apr 20, 2026
99509c6
docs: Phase I COMPLETE — ./jcpan -t DBIx::Class clean run
fglock Apr 20, 2026
18d188a
parser: handle `our`/`state` in statement-modifier-with-declaration
fglock Apr 20, 2026
f8a89ab
fix: suppressFlush in setFromList fast path — fixes Template::Toolkit…
fglock Apr 20, 2026
c8f669b
fix: incref anon-array elements on add — fixes TT directive.t
fglock Apr 20, 2026
6d37287
fix: suppress MortalList.flush during anon literal — fixes CDBI regre…
fglock Apr 20, 2026
02f2ab5
Phase J: performance investigation plan + baseline tooling
fglock Apr 20, 2026
2936484
J2: skip suppressFlush emit for simple anon literals
fglock Apr 20, 2026
556e62f
Phase J: update plan with findings, J2 complete, J3 blocked
fglock Apr 20, 2026
b331c5d
J3a: flush MortalList before scalar snapshot in walker
fglock Apr 20, 2026
b84faef
J4: cache last-seen fileName/info in ByteCodeSourceMapper
fglock Apr 20, 2026
84351b6
Phase J: finalize plan with results — 3.3% DBIC win, J3 deferred
fglock Apr 20, 2026
f4d474a
perf: cache System.getenv() in hot paths as static final
fglock Apr 21, 2026
2fb0bd1
perf: gate ScalarRefRegistry.registerRef() on weakRefsExist
fglock Apr 21, 2026
a7165f7
perf: gate MyVarCleanupStack.liveCounts on weakRefsExist
fglock Apr 21, 2026
17527e8
perf: cache warning bits lookup + empty-args snapshot fast path
fglock Apr 21, 2026
660aa9e
perf: avoid autoboxing in RuntimeScalar(long) + bitwise fast paths
fglock Apr 21, 2026
5611e34
Merge origin/master into feature/refcount-perf-combined
fglock Apr 21, 2026
1869bad
fix(overload): re-add throwIfFallbackDenied after tryTwoArgumentNomethod
fglock Apr 21, 2026
845cbb7
docs: add PR #526 merge validation report
fglock Apr 21, 2026
30a73f2
feat: bundle pure-Perl Class::XSAccessor (silences Moo load warnings)
fglock Apr 21, 2026
c06a554
docs: add Class::C3 follow-up to PR #526 validation report
fglock Apr 21, 2026
49b539c
docs: archive superseded plans; add next-steps plan
fglock Apr 21, 2026
6a2b2a3
docs: flag life_bitpacked perf regression as top priority in plan
fglock Apr 21, 2026
6c6e6ae
docs: §0 — replace bisect with profiling + static diff + counters
fglock Apr 21, 2026
41a105a
docs: §0 — goal is parity with system perl across dev/bench/*
fglock Apr 21, 2026
13741f2
docs: map user-reported DBIC failures to §0 perf gap (not regressions)
fglock Apr 21, 2026
286a873
docs: §0.3 — reproduce discard_changes hang with jprove (not prove)
fglock Apr 21, 2026
12bdc10
bench: §0.1 — COMPARE=perl, life_bitpacked, markdown output
fglock Apr 21, 2026
d071692
Merge pull request #529 from fglock/bench/compare-with-perl
fglock Apr 21, 2026
d3ee4e2
docs: §0 — record measurement findings + failed gate attempt
fglock Apr 21, 2026
fa8df8a
perf: skip RegexState allocation when no regex has matched yet
fglock Apr 21, 2026
1400475
perf: avoid per-lookup CacheKey allocation in NameNormalizer
fglock Apr 21, 2026
b7d05b7
docs: §0.8 — progress log of profiling-guided perf work
fglock Apr 21, 2026
21b583e
docs: §0.8/§0.9 — apply() fast-path split reverted due to life_bitpac…
fglock Apr 21, 2026
0c6fea9
bench: record post-merge baseline results
fglock Apr 21, 2026
078e0b3
docs: deep analysis of life_bitpacked perf vs master
fglock Apr 21, 2026
31caba5
perf: gate MyVarCleanupStack.unregister emission on per-sub analysis
fglock Apr 21, 2026
3e3908f
Merge pull request #533 from fglock/perf/cleanup-needed-analysis
fglock Apr 21, 2026
3c2ca4b
docs: perl5 internals vs PerlOnJava — concrete hot-path comparison
fglock Apr 22, 2026
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
16 changes: 16 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@

---

## ⚠️ Resource Management: Avoid Fork Exhaustion ⚠️

**Do NOT spawn excessive parallel processes.** Running too many background shells, subagents, or parallel builds at once can exhaust the system's process table (fork bomb), forcing a reboot and losing work.

- **Limit parallel operations**: Run at most 2-3 concurrent processes at a time
- **Avoid unnecessary background shells**: Use foreground execution when you don't need parallelism
- **Wait for processes to finish** before starting new ones when possible
- **Never run `make` in parallel with other heavy processes** (builds already use multiple threads internally)
- **Clean up**: Kill background shells when they're no longer needed

---

## Project Rules

### Progress Tracking for Multi-Phase Work
Expand Down Expand Up @@ -60,6 +72,10 @@ Example format at the end of a design doc:
- Keep docs updated as implementation progresses
- Reference related docs and skills at the end

### Sandbox Tests

- `dev/sandbox/destroy_weaken/` — Tests for DESTROY and weaken behavior (cascading cleanup, scope exit timing, blessed-without-DESTROY, etc.). Run with `./jperl` or `perl` for comparison.

### Partially Implemented Features

| Feature | Status |
Expand Down
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,10 @@ tasks.withType(JavaCompile).configureEach {
options.compilerArgs << '-Xlint:deprecation'
}

// Test execution configuration with native access
// Test execution configuration with native access and adequate heap
tasks.withType(Test).configureEach {
jvmArgs += '--enable-native-access=ALL-UNNAMED'
maxHeapSize = '1g'
}

// Enable native access for all Java execution tasks
Expand Down
399 changes: 348 additions & 51 deletions dev/architecture/weaken-destroy.md

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions dev/bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# PerlOnJava microbenchmarks

Tiny workloads to catch per-call / per-op regressions that CPAN test
suites don't surface cleanly. The headline goal is **parity with
system `perl`** on every benchmark here (≤ 1.0× wallclock).

## Run

```bash
# Fast: jperl only (default 3 runs per bench)
dev/bench/run_baseline.sh

# Side-by-side with system perl (gives ratio + parity marker)
COMPARE=perl dev/bench/run_baseline.sh

# Other knobs
BENCH_RUNS=5 dev/bench/run_baseline.sh
PERL=/opt/homebrew/bin/perl COMPARE=perl dev/bench/run_baseline.sh
SKIP_LIFE=1 dev/bench/run_baseline.sh
```

Outputs for `<sha>`:

- `results/baseline-<sha>.json` — machine-readable
- `results/baseline-<sha>.md` — human-readable markdown table

With `COMPARE=perl` the markdown has a `ratio` column and a parity
marker: **✅** (≤1.0×), **≈** (≤1.2×), **❌** (>1.2×).

## Workloads

| File | Measures |
|---|---|
| `benchmark_anon_simple.pl` | anon-sub creation churn (no blessing) |
| `benchmark_closure.pl` | closure capture + invoke |
| `benchmark_eval_string.pl` | `eval "..."` compile+run overhead |
| `benchmark_global.pl` | package-global variable access |
| `benchmark_lexical.pl` | `my` variable access |
| `benchmark_method.pl` | OO method dispatch (inline cache hot) |
| `benchmark_refcount_anon.pl` | anon-sub + refcount traffic (plain refs) |
| `benchmark_refcount_bless.pl` | anon-sub + blessed refs (walker / DESTROY machinery) |
| `benchmark_regex.pl` | regex compile+match on hot path |
| `benchmark_string.pl` | concat / substr / index |
| `benchmark_memory*.pl` | memory footprint (not in the baseline loop) |
| `examples/life_bitpacked.pl` | real workload (Conway bit-packed) — reports Mcells/s instead of wallclock seconds |

## Historical baselines

`results/` keeps per-sha snapshots. Treat anything before 2026-04-21
(PR #526 merge) as the old single-column format (jperl-only, no
`perl` comparison, no markdown).

See [`dev/design/next_steps.md`](../design/next_steps.md) §0 for the
parity plan and current gap analysis.
45 changes: 45 additions & 0 deletions dev/bench/benchmark_anon_simple.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env perl
# benchmark_anon_simple.pl
# Measures the overhead of anon array/hash literal construction for
# SIMPLE elements (no sub/method calls). These paths should skip the
# MortalList.suppressFlush wrapper entirely after the J2 optimization.

use strict;
use warnings;
use Benchmark qw(timethese);

my $N = $ENV{BENCH_N} || 1_000_000;

sub arr_int {
my $a = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
return scalar @$a;
}

sub arr_str {
my $a = [ "one", "two", "three", "four" ];
return scalar @$a;
}

sub arr_var {
my ($x, $y) = (10, 20);
my $a = [ $x, $y, $x + $y, $x * $y ];
return scalar @$a;
}

sub hash_simple {
my $h = { a => 1, b => 2, c => 3, d => 4, e => 5 };
return scalar keys %$h;
}

sub nested_simple {
my $x = { a => [1,2,3], b => [4,5,6], c => { x => 10, y => 20 } };
return 1;
}

timethese($N, {
'arr_int' => \&arr_int,
'arr_str' => \&arr_str,
'arr_var' => \&arr_var,
'hash_simple' => \&hash_simple,
'nested_simple' => \&nested_simple,
});
53 changes: 53 additions & 0 deletions dev/bench/benchmark_refcount_anon.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env perl
# benchmark_refcount_anon.pl
# Stresses anon array/hash literal construction, exercising the
# suppressFlush bytecode added in feature/refcount-alignment.
# Should be sensitive to regressions in EmitLiteral.java's per-literal
# overhead and to changes in MortalList.flush / createReferenceWithTrackedElements.

use strict;
use warnings;
use Benchmark qw(timethese cmpthese);

{ package Obj;
sub new { my ($c,$id)=@_; bless { id => $id, tags => [1,2,3] }, $c }
sub DESTROY {} # keep blessed-with-DESTROY path hot
}

my $N = $ENV{BENCH_N} || 100_000;

sub array_of_scalars {
my $arr = [ 1, 2, 3, 4, 5, 6, 7, 8 ];
return scalar @$arr;
}

sub array_of_blessed {
my $arr = [ Obj->new(1), Obj->new(2), Obj->new(3) ];
return scalar @$arr;
}

sub hash_of_scalars {
my $h = { a => 1, b => 2, c => 3, d => 4 };
return scalar keys %$h;
}

sub hash_of_blessed {
my $h = { a => Obj->new(1), b => Obj->new(2), c => Obj->new(3) };
return scalar keys %$h;
}

sub nested_literal {
my $x = {
list => [ Obj->new(1), Obj->new(2) ],
map => { one => Obj->new(10), two => Obj->new(20) },
};
return 1;
}

timethese($N, {
'array_of_scalars' => \&array_of_scalars,
'array_of_blessed' => \&array_of_blessed,
'hash_of_scalars' => \&hash_of_scalars,
'hash_of_blessed' => \&hash_of_blessed,
'nested_literal' => \&nested_literal,
});
60 changes: 60 additions & 0 deletions dev/bench/benchmark_refcount_bless.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env perl
# benchmark_refcount_bless.pl
# Stresses the bless / DESTROY / MortalList path.
# Sensitive to changes in ReferenceOperators.bless, RuntimeScalar.setLargeRefCounted,
# and MortalList.flush throughput.

use strict;
use warnings;
use Benchmark qw(timethese);

{ package WithDestroy;
sub new { bless { id => $_[1] }, $_[0] }
sub DESTROY {}
}

{ package NoDestroy;
sub new { bless { id => $_[1] }, $_[0] }
}

my $N = $ENV{BENCH_N} || 100_000;

sub bless_and_drop_destroy {
# Blessed temp, never stored — exercises mortal flush immediately.
WithDestroy->new(1);
return 1;
}

sub bless_and_drop_nodestroy {
NoDestroy->new(1);
return 1;
}

sub bless_store_drop {
my $o = WithDestroy->new(1);
return 1;
}

sub bless_hash_store {
my $o = WithDestroy->new(1);
my %h;
$h{obj} = $o;
return 1;
}

sub bless_pass_through_subs {
# Passes a blessed temp through two my-list assignments.
# This is the setFromList fast-path we modified.
my $outer = sub { my ($a) = @_; $a };
my $inner = sub { WithDestroy->new(99) };
my $r = $outer->($inner->());
return 1;
}

timethese($N, {
'bless_drop_destroy' => \&bless_and_drop_destroy,
'bless_drop_nodestroy' => \&bless_and_drop_nodestroy,
'bless_store_drop' => \&bless_store_drop,
'bless_hash_store' => \&bless_hash_store,
'bless_pass_through_subs' => \&bless_pass_through_subs,
});
17 changes: 17 additions & 0 deletions dev/bench/results/baseline-02f2ab55a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"git_sha": "02f2ab55a",
"date": "2026-04-20T20:47:22Z",
"runs": 3,
"jperl": "/Users/fglock/projects/PerlOnJava3/jperl",
"benchmarks": {
"benchmark_closure": [9.952,9.059,8.233],
"benchmark_eval_string": [14.938,14.669,14.736],
"benchmark_global": [14.609,14.519,14.382],
"benchmark_lexical": [4.103,3.961,4.094],
"benchmark_method": [4.725,4.740,4.726],
"benchmark_refcount_anon": [2.534,2.559,2.528],
"benchmark_refcount_bless": [1.370,1.360,1.389],
"benchmark_regex": [2.637,2.706,2.735],
"benchmark_string": [3.969,3.975,3.961]
}
}
21 changes: 21 additions & 0 deletions dev/bench/results/baseline-1400475d3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"git_sha": "1400475d3",
"date": "2026-04-21T18:41:58Z",
"runs": 3,
"jperl": "/Users/fglock/projects/PerlOnJava3/jperl",
"perl": "perl",
"perl_version": "5.042000",
"benchmarks": {
"benchmark_anon_simple": { "unit": "s", "jperl": [7.625,7.526,7.247], "perl": [1.451,1.459,1.459] },
"benchmark_closure": { "unit": "s", "jperl": [10.232,9.053,8.900], "perl": [8.096,8.085,8.053] },
"benchmark_eval_string": { "unit": "s", "jperl": [14.690,14.424,14.569], "perl": [3.325,3.302,3.414] },
"benchmark_global": { "unit": "s", "jperl": [14.939,14.632,14.958], "perl": [11.147,11.210,11.098] },
"benchmark_lexical": { "unit": "s", "jperl": [4.060,4.091,4.043], "perl": [10.672,10.816,10.761] },
"benchmark_method": { "unit": "s", "jperl": [2.636,2.613,2.583], "perl": [1.513,1.526,1.523] },
"benchmark_refcount_anon": { "unit": "s", "jperl": [1.869,1.850,1.776], "perl": [0.451,0.456,0.442] },
"benchmark_refcount_bless": { "unit": "s", "jperl": [1.313,1.315,1.353], "perl": [0.198,0.204,0.198] },
"benchmark_regex": { "unit": "s", "jperl": [2.750,2.757,2.765], "perl": [1.999,2.010,2.060] },
"benchmark_string": { "unit": "s", "jperl": [3.988,4.130,4.132], "perl": [6.896,6.993,6.947] },
"life_bitpacked": { "unit": "Mcells/s", "jperl": [8.03,7.98,8.19], "perl": [20.76,21.08,21.11] }
}
}
23 changes: 23 additions & 0 deletions dev/bench/results/baseline-1400475d3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Benchmark baseline — 1400475d3

**Date:** 2026-04-21T18:41:58Z
**Runs per benchmark:** 3
**jperl:** `/Users/fglock/projects/PerlOnJava3/jperl`
**perl:** `perl` (5.042000)

For "time" benches lower = faster; ratio is `jperl / perl`.
For "Mcells/s" (life_bitpacked) higher = faster; ratio is `perl / jperl`.

| Benchmark | unit | jperl | perl | ratio | parity? |
|---|---|---:|---:|---:|:---:|
| `benchmark_anon_simple` | s | 7.466 | 1.456 | **5.13×** | ❌ |
| `benchmark_closure` | s | 9.395 | 8.078 | **1.16×** | ≈ |
| `benchmark_eval_string` | s | 14.561 | 3.347 | **4.35×** | ❌ |
| `benchmark_global` | s | 14.843 | 11.152 | **1.33×** | ❌ |
| `benchmark_lexical` | s | 4.065 | 10.750 | **0.38×** | ✅ |
| `benchmark_method` | s | 2.611 | 1.521 | **1.72×** | ❌ |
| `benchmark_refcount_anon` | s | 1.832 | 0.450 | **4.07×** | ❌ |
| `benchmark_refcount_bless` | s | 1.327 | 0.200 | **6.63×** | ❌ |
| `benchmark_regex` | s | 2.757 | 2.023 | **1.36×** | ❌ |
| `benchmark_string` | s | 4.083 | 6.945 | **0.59×** | ✅ |
| `life_bitpacked` | Mcells/s | 8.067 | 20.983 | **2.60×** | ❌ |
17 changes: 17 additions & 0 deletions dev/bench/results/baseline-6d37287f1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"git_sha": "6d37287f1",
"date": "2026-04-20T20:06:40Z",
"runs": 3,
"jperl": "/Users/fglock/projects/PerlOnJava3/jperl",
"benchmarks": {
"benchmark_closure": [8.324,9.181,9.147],
"benchmark_eval_string": [14.746,14.986,15.144],
"benchmark_global": [14.636,14.746,14.610],
"benchmark_lexical": [4.168,4.090,4.083],
"benchmark_method": [4.744,4.730,4.778],
"benchmark_refcount_anon": [2.561,2.599,2.617],
"benchmark_refcount_bless": [1.383,1.387,1.396],
"benchmark_regex": [2.636,2.625,2.663],
"benchmark_string": [3.935,3.982,3.946]
}
}
21 changes: 21 additions & 0 deletions dev/bench/results/baseline-b7d05b77e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"git_sha": "b7d05b77e",
"date": "2026-04-21T19:52:51Z",
"runs": 3,
"jperl": "/Users/fglock/projects/PerlOnJava3/jperl",
"perl": "perl",
"perl_version": "5.042000",
"benchmarks": {
"benchmark_anon_simple": { "unit": "s", "jperl": [7.180,7.071,7.069], "perl": [1.417,1.452,1.449] },
"benchmark_closure": { "unit": "s", "jperl": [9.667,9.665,9.723], "perl": [7.794,7.750,7.618] },
"benchmark_eval_string": { "unit": "s", "jperl": [14.481,14.317,14.491], "perl": [3.061,3.086,3.054] },
"benchmark_global": { "unit": "s", "jperl": [14.385,14.523,14.539], "perl": [10.592,9.285,10.525] },
"benchmark_lexical": { "unit": "s", "jperl": [3.923,3.938,3.911], "perl": [10.297,10.158,10.291] },
"benchmark_method": { "unit": "s", "jperl": [2.526,2.528,2.499], "perl": [1.458,1.450,1.447] },
"benchmark_refcount_anon": { "unit": "s", "jperl": [1.735,1.725,1.709], "perl": [0.445,0.450,0.446] },
"benchmark_refcount_bless": { "unit": "s", "jperl": [1.233,1.241,1.234], "perl": [0.197,0.198,0.197] },
"benchmark_regex": { "unit": "s", "jperl": [2.655,2.677,2.678], "perl": [2.044,2.008,2.024] },
"benchmark_string": { "unit": "s", "jperl": [4.016,4.021,4.092], "perl": [6.873,6.707,6.729] },
"life_bitpacked": { "unit": "Mcells/s", "jperl": [8.49,8.49,8.41], "perl": [21.41,21.63,21.60] }
}
}
23 changes: 23 additions & 0 deletions dev/bench/results/baseline-b7d05b77e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Benchmark baseline — b7d05b77e

**Date:** 2026-04-21T19:52:51Z
**Runs per benchmark:** 3
**jperl:** `/Users/fglock/projects/PerlOnJava3/jperl`
**perl:** `perl` (5.042000)

For "time" benches lower = faster; ratio is `jperl / perl`.
For "Mcells/s" (life_bitpacked) higher = faster; ratio is `perl / jperl`.

| Benchmark | unit | jperl | perl | ratio | parity? |
|---|---|---:|---:|---:|:---:|
| `benchmark_anon_simple` | s | 7.107 | 1.439 | **4.94×** | ❌ |
| `benchmark_closure` | s | 9.685 | 7.721 | **1.25×** | ❌ |
| `benchmark_eval_string` | s | 14.430 | 3.067 | **4.70×** | ❌ |
| `benchmark_global` | s | 14.482 | 10.134 | **1.43×** | ❌ |
| `benchmark_lexical` | s | 3.924 | 10.249 | **0.38×** | ✅ |
| `benchmark_method` | s | 2.518 | 1.452 | **1.73×** | ❌ |
| `benchmark_refcount_anon` | s | 1.723 | 0.447 | **3.85×** | ❌ |
| `benchmark_refcount_bless` | s | 1.236 | 0.197 | **6.27×** | ❌ |
| `benchmark_regex` | s | 2.670 | 2.025 | **1.32×** | ❌ |
| `benchmark_string` | s | 4.043 | 6.770 | **0.60×** | ✅ |
| `life_bitpacked` | Mcells/s | 8.463 | 21.547 | **2.55×** | ❌ |
Loading