# pperl 0.6.19 *2026-09-17 to 2026-09-27, 526 commits.* ## Language and runtime - **Modules compiled into pperl** are loaded the way perl5 loads a file it finds on `@INC`. Such a module no longer sees the lexical variables and pragmas of the code that requires it: `my $VERSION = "mine"; require Guard` overwrote the caller's `$VERSION`. `%INC` and `__FILE__` name the module `./pperl-native/` instead of `native`. ## JIT and parallelization - **mandelbrot**: the Benchmarks Game's mandelbrot compiles its per-pixel loop as one piece. A labelled `next` or `last` out of an inner loop is a jump inside compiled code, an append to a fresh `my $line` no longer leaves it, and the loop's floating-point values stay in registers instead of being reloaded from the stack; at size 16,000 the program runs 362.6G instructions, from 1,160.8G. The range checks on floating-point results are combined into one test per statement, which takes 28.9% of the instructions off mandelbrot at size 2,000. - **Independent loop iterations**: a counted `foreach` whose iterations do not depend on each other runs four of them side by side in SIMD vectors, and overflow checks that the loop's own tests make unnecessary are proved away and dropped. mandelbrot at size 16,000 takes 19.36 s of CPU time with the vectors, from 31.08 s, and runs 164.3G instructions with the checks dropped, from 217.4G. In the release build it takes 12.13 s. - **binary-trees**: a recursive subroutine that walks a tree through array references, or builds and returns new arrays, compiles as a function. When one such call builds a tree that the next call in the same statement only walks, the tree is built in a private area that is freed in one step; any failure throws the area away and runs the statement again interpreted. With the private area, the Benchmarks Game's binary-trees at depth 21 takes 4.29 s of CPU time instead of 26.1 s. - **fannkuch-redux**: a block inside a compiled loop no longer ends it, so the Benchmarks Game's fannkuch-redux keeps its whole permutation step in compiled code; at n=9 it runs 1,301M instructions, from 2,824M. Statements of its flip loop no longer write perl's stack pointer, `@a = @b` between two integer arrays copies the values directly, a two-index `while ($lo < $hi)` loop checks its bounds once, and loop tests are placed next to their bodies. At n=12 the program takes 53.53 s of CPU time, where Node.js takes 57.42 s. - **fasta**: a constant that an earlier division left carrying a floating-point value no longer forces its expression into doubles, so the Benchmarks Game's fasta computes `($seed * IA + IC) % IM` in integers, as perl5 does: 7.52 s of CPU time at 25,000,000 becomes 3.64 s. A modulo by a literal compiles to a multiplication (18.09 s to 14.83 s), and short appends and `substr($s, $i, 1)` copy their bytes inline. - **spectral-norm**: a small numeric function called from a loop, such as `eval_A` in the Benchmarks Game's spectral-norm, no longer pays per call for a signal check, a stack probe and stack resets it cannot need, and a loop counter or read-only integer is checked against the 1e15 limit once when the loop is entered instead of at every use. spectral-norm at N=5,500 takes 2.39 s of CPU time, from 3.04 s; Node.js takes 2.76 s. - **Memoized functions in compiled code**: the compiled cache probe of a memoized function covers one to four numeric arguments, recursive functions such as `ackermann` and `tak`, and calls in `return f(...)` position. A cache hit is 64.3 times faster than on perl5 with CPAN Memoize for `fib`, 51.1 times for a two-argument `dist` and 64.2 times for `ackermann(2, $k)`. Deep memoized recursion stays compiled where the recursion warning is off, to a depth sized from the C stack that is actually left. A memoized function whose argument is a string no longer warns `Argument "..." isn't numeric`. - **Nested loops** compile as one unit in more shapes: a `foreach` over an array or an array reference (`for (@a)`, `for my $x (@$ref)`) inside a compiled loop keeps its state in registers, as do inner loops under a `do {} while` loop, inside one branch of an `if`, or three levels deep. Whether a nest is worth compiling is judged by the work of all its trips, not by its outer body alone. fannkuch-redux at n=12 runs 25.1% fewer instructions. - **Recursive subroutines** compiled by the JIT no longer fall back to the interpreter beyond 90 levels: they recurse in compiled code as deep as the C stack allows and issue perl's "Deep recursion" warning at level 100 themselves, naming the line perl names. Where a `__WARN__` handler, a fatal warning or a tied `STDERR` could see the call frames, the call is run again interpreted. - **Subroutines that update outer variables**: a loop that calls a short subroutine writing a variable of an enclosing scope or a package variable, such as `sub add { my ($d) = @_; $acc += $d; 0 }`, compiles together with the call. The writes are made when the call returns, after every check, so a call that has to run again interpreted never writes twice. - **JIT compile time**: a loop the JIT would reject after compiling it is rejected before its machine code is generated. Short runs gain most: mandelbrot at size 120 runs 11.5% fewer instructions and spectral-norm at N=500 8.1% fewer. - **Regular expressions**: ReJIT compiles a pattern anchored at the start of the string, such as `/^bankrupt[a-z]+/`, once its interpreted matches have cost as much as a compile, instead of as soon as the subject is long. Such a pattern matched 500 times against a 71 KB string runs 3.52M instructions instead of 5.31M. - **Number flags after compiled loops**: a compiled loop that copies integers (`my $t = $p[$i]`, `@p[$i, $j] = @p[$j, $i]`) leaves them integers instead of floating-point numbers, and `--` of an integral float keeps it a float. The values were right before; the flags, visible through `Devel::Peek`, were not. - **Compiled loops** no longer give wrong answers in these cases: `@c = ($c[0] + 1, $c[1])` died with "Can't coerce ARRAY to integer in foreach loop iterator"; `$a[$i] += 1` past the largest integer wrapped to a negative number; `defined(wantarray)` was true in void context; and a second loop over the same non-`my` variable, `for $i (-3 .. -1)` after `for $i (0 .. 3)`, read the first loop's counter. A loop whose computed bound is NaN dies with "Range iterator outside integer range" instead of aborting pperl, and an array a compiled loop iterates is no longer kept alive past its scope, which had stopped a blessed array's `DESTROY` from running. - **Compiled subroutines** no longer lose an update to an array element, as in `sub bump { my $i = shift; $acc[$i % 4] += $i; $i }`, or an assignment whose operator writes straight into a lexical, as in `$t = sin($i)`. - **Warnings from compiled loops**: under `use warnings`, `$x--` from -1e20 in a compiled loop warns "Lost precision when decrementing" on every iteration as perl does, where it warned 65 times in 50,000; the same holds for `++` and for array and hash elements. A warning raised in a loop's condition names the line perl 5.44 names. ## Modules - **Coro** 6.57 runs, with Coro::State native and the distribution's own Perl modules; its 20 upstream test files pass on the assembler and the `setjmp` context-switch backends alike. Hot loops inside a coroutine compile, because the JIT now compiles on a stack of its own instead of the coroutine's 128 KiB one. Coro is part of builds with the `native-max` feature. Two cases in which perl5 crashes - a Coro::State call on a blessed hash that is not a coroutine, and the `:cede` layer without an interval - croak or fall back to the default interval instead. - **EV and Event**: EV 4.37 with libev 4.33 and Event 1.28 are native, with Coro::EV and Coro::Event on top, in `native-max` builds; EV's 11 upstream test files pass. With EV present, AnyEvent picks it as its event loop, as on a perl5 that has EV installed. `EV::Periodic`'s `reschedule_cb` keeps its callback alive, where perl5 frees it after the first call of the getter. - **AnyEvent and Guard**: AnyEvent 7.17 ships with pperl and Guard is native, both in default builds. `Guard::cancel` on a reference to a plain value croaks, where perl5 sometimes crashes. - **Native modules**: each module that is XS on perl5 and native in pperl ships its distribution's own `.pm` unchanged, among them B, POSIX, Socket, Storable, Encode, IO, Data::Dumper, Scalar::Util, List::Util, Time::HiRes, Fcntl, File::Glob, Clone and PadWalker, and none of them is loaded from the host perl's library any more. The native part is installed when the `.pm` calls `XSLoader::load`, as in perl5, so `ref \$Socket::{AF_INET}` is `GLOB` and `B::svref_2object(\&List::Util::sum)->FILE` is `ListUtil.c`. A module left out of a build fails with perl5's own "Can't locate ..." message. - **Peta::Dict** is new: `tie my %h, 'Peta::Dict'` binds a hash to an open-addressed table in which an integer and its decimal string are one key and `"05"` is another, as in a Perl hash; on perl5 the same source runs on a pure-Perl Peta::Dict with identical output. A compiled loop reads and writes such a hash through an in-register probe of the table instead of a method call per access, and `$h{$k}++` increments an integer stored in the table in place. The result of a bitwise operator is stored as an integer, as in perl, so a key built with `<<`, `|` and `&` stays one. The Benchmarks Game's k-nucleotide, counting packed keys in a Peta::Dict, takes 42.86 s of CPU time at 25,000,000 with the in-place increment, from 51.05 s. - **Peta::WebGPU** draws as well as computes: surfaces, render pipelines and render passes with vertex and index buffers, uniforms, depth buffers and face culling, into an SDL2 window or into a texture whose pixels can be read back without a display. A vertex layout written as a list of formats, `['float32x3', 'float32x3']`, gets its offsets and stride computed, and enum names are WebGPU's own spellings. SDL2 windows report the window-system handles this needs through `$win->wm_info`. - **Encode**'s Perl half is upstream Encode 3.24's, and its encoding tables are generated at build time from the distribution's `.ucm` files, matching upstream's for all 103 encodings. `encode_utf8(undef)` returns undef and `encode_utf8(42)` returns `"42"`, where both returned an empty string, and Encode's functions have perl5's prototypes, so `encode_utf8 $a, "\n"` is no longer one call with two arguments. - **Crashes in native modules**: error messages containing a `%` sign no longer crash the native modules that raised them. Digest::SHA no longer crashes on a foreign object or computes a wrong digest past 512 MiB of input, Storable no longer passes a class name to a string `eval`, and GD and SDL2 no longer free an image twice or crash on surfaces. - **Memoize**: a memoized recursive function no longer crashes pperl at depth; without the JIT it segfaulted at 20,000 levels, where perl5 runs 500,000. A numeric function such as `eval_A` memoized before it was ever called no longer crashes pperl on its first call. - **PDL operators**: `+=`, `-=`, `*=` and the other assignment operators modify an ndarray in place, so aliases and slices see the change, where they built a new ndarray; `PDL::mult($a, $b, $c, 0)` writes its result into `$c`. `.=` broadcasts over dimensions, keeps 64-bit values exact, wraps bytes as C does and refuses a read-only target. `xchg`, `transpose` and `dummy` return views that write through to their parent and keep its type, where they returned double-precision copies. - **PDL exports and loading**: `use PDL` exports what upstream exports, each name from the sub-module that owns it - 228 names, where a hand-written list exported about 58 that perl5 does not and lacked about 122 that it does; `glue` and `topdl` are no longer exported. PDL::Exporter and PDL::IO::Storable ship with pperl instead of being loaded from the host perl. `use PDL; use PDL::Lite` under `-w` no longer warns that `main::pdl` was redefined, a regression of 0.6.18, and `\&PDL::Ufunc::sumover` is the same subroutine as `\&PDL::sumover`. - **PDL broadcasting**: `broadcast`, `broadcast1`, `broadcast2` and `broadcastI` reorder dimensions as upstream does instead of returning the same ndarray, and `unbroadcast` puts them back, where it used to drop trailing size-1 dimensions. A reduction given an output ndarray, `maximum($src, $out)`, writes into it, and PDL error messages no longer print garbage. - **PDL**: methods called on a result not yet computed, such as `isempty`, `copy` or `transpose` on `$a + $b`, no longer treat it as empty, and `dims` in scalar context returns the number of dimensions. `svd` and `ylinvals` work on slices, `tritosquare` fills the lower triangle, and `pctover`, `oddpctover`, `pct`, `oddpct` and `inner` compute as upstream, broadcasting and bad values included. Element reads and writes of 64-bit, unsigned and complex ndarrays no longer pass through a double. - **List::Util and Scalar::Util**: `sum`, `sum0` and `product` switch to floating point when an integer total would overflow, where `sum(IV_MAX, 1)` wrapped to a negative number, and they honour tied and overloaded arguments. Scalar::Util's `isvstring`, `openhandle` and `looks_like_number`, and List::Util's `pairs` and `mesh`, behave as upstream's own tests expect. - **Math::GMP** functions called with a plain number as the first argument, such as `Math::GMP::bsqrt(16)` or `stringify(42)`, return the answer instead of crashing, and every function checks its argument count as perl5's does. `2 ** Math::GMP->new(3)` is 8, not 9. - **File::Glob**: under `-T`, every name returned by `glob`, `<*.c>`, `bsd_glob` and `csh_glob` is tainted, as in perl5; all of them came back untainted. - **GD**: `copyRotate90`, `copyRotate180`, `copyRotate270`, the `copyFlip` methods, `crop`, `contrast` and `smooth` work as in the GD distribution. - **Cwd**: `fast_abs_path` follows Cwd.pm, so for an empty string or a symbolic-link loop it returns undef with `$!` set to ENOENT. ## Command line - **The debugger** starts with `pperl -d`, `-de0` and `-d:Module` as with perl5, where `-d` was accepted and ignored and the other forms were rejected. The default Term::ReadLine::Gnu front end no longer aborts at the first prompt, and a piped debugger session matches perl 5.44's byte for byte. - **Command-line switches**: `-T` and `-t` combine with other switches (`-Tw`), `-W` and `-X` are perl5's and take effect in command-line order, and `-s` is perl5's switch parsing again; pperl's own statistics are `--stats` only. A program given with `-e` no longer gains an empty last line, so a warning at its end names line 1, as in perl5. ## Documentation - **Translations**: the guides, tutorials and errata, and the module reference pages, were brought to complete translation in all 41 languages, except the pages held for rewrite. Shipped translations were reviewed in every language, and wrong terms, headings and link labels left in English, double-encoded text and bold text that rendered as literal asterisks were repaired. - **Coroutines guide**: a new chapter of the concurrency guide, Coroutines and event loops, covers Coro, EV, Event and AnyEvent: starting and switching coroutines, channels, semaphores and signals, blocking I/O through Coro::AnyEvent and Coro::Handle, and how AnyEvent picks its loop. It is translated into all 41 languages. - **Graphics guides**: the 2D guide has a new chapter on raster drawing with GD, and examples in its other chapters were corrected after running them. The 3D guide gains its GPU part on Peta::WebGPU - a device and window, the render pipeline, a 3D scene with depth buffer, culling, instancing and lighting - and two capstones, a modeller and a 3D game. - **Corrections**: about 250 statements in the guides about what pperl supports were run against pperl and perl5, and the false ones corrected: the one-liner switches `-n`, `-p`, `-a`, `-F`, `-i` and `-l` work, threads are not available, and `-d` starts the debugger. Errors that translators reported in the regular-expression guide, the tutorials and the perlfunc pages were checked on perl 5.44 and fixed, among them the bound of `{n,m}` and the version that introduced `any` and `all`. The introduction's architecture diagram shows the runtime pperl has. - **Module reference**: the module reference pages state what each function does, checked against perl 5.44, with the differences from upstream listed where there are any, and no longer carry implementation notes. Mistakes that translators reported were fixed, among them the precedence in the Fcntl `S_IS*` examples and the behaviour of Hash::Util's `lock_hash`.