pperl 0.6.21#

2026-09-28 to 2026-10-06, 271 commits.

Language and runtime#

  • Unicode 17 reaches \N{...}, charnames::viacode and Unicode::UCD: they are perl 5.44’s own, built into pperl, where pperl had read them from the host perl (5.42, Unicode 16) and did not know Unicode 17’s characters. A program using one \N{NAME} no longer spends most of its run compiling the 1.2 MB character-name table (34 ms against a 4 ms baseline); the table is stored in the binary.

  • glob and <*> match files with perl5’s own matcher instead of the C library’s glob: brace alternatives come back in the order written, GLOB_NOCASE no longer sorts by case, ^ inside [...], \* and an empty {} mean what they mean in perl5. A directory error stops a glob as in perl5, and File::Glob::GLOB_ERROR reports the last glob’s error code instead of always 0.

JIT and parallelization#

  • Parallel loops: compiled loops run on all cores. A counted foreach, while or C-style for loop whose body only computes - integer sums, bitwise reductions, sums of floats whose terms are whole numbers, reads of a hash with integer keys - is split into chunks whose result is bit for bit the sequential one, and a chunk that reaches a call or an exit resumes sequentially at its first iteration, so perl dies or warns where it would. On 4 physical cores a 400-million-trip addition loop takes 0.13 s instead of 0.58 s, and the Benchmarks Game’s mandelbrot written as a loop takes 4.13-4.26 s at N=16,000 instead of 10.95 s. Loops too short to repay the split stay sequential, and --no-parallel turns splitting off.

  • Floating-point loops over arrays, also arrays reached through references, no longer leave compiled code at every float element: a matrix-vector product takes 9.5 ns per multiply-add instead of 248 (79 ns without the JIT), a 4096-element dot product 9.4 ns instead of 70. A return \@y elsewhere in the sub, or a ?: that picks an array element ($x[$i] > 0 ? $x[$i] : 0.0), no longer keeps a loop interpreted; a small neural network trains at 16,000 samples per second with the JIT against 2,500 without, where it had run slower with the JIT (1,800 against 4,000). A float loop such as $sum += ($x > $y) no longer moves its sum through memory on every trip: 5.2 cycles per trip instead of 10.1.

  • Regular expressions compiled by ReJIT: a successful match of a pattern without capture groups records its result itself instead of running the interpreter’s matcher over the match again, and the Benchmarks Game’s regex-redux (single-threaded, 5M) runs 32% fewer instructions and cycles; each compiled pattern reaching its state through its own engine saved a further 3% there. Scans for a literal probe 64 bytes per step and hand long stretches without a candidate to the C library’s memchr: /aab./ over 64K of a went from 0.14 to 1.02 times perl5’s speed, and the other literal and character-class scans measured run 1.7 to 1.9 times as fast.

  • String operations in compiled loops: 4-argument and lvalue substr replacement, vec read and assigned, the x repetition of a string and join over scalars compile, and so do string results stored into package variables and into array or hash elements, and truth tests of strings. A substr loop over a 4 MB string runs in 1.49 s instead of 3.65 s (perl5 4.59 s), a vec bit sieve in 0.56 s instead of 3.75 s (perl5 4.41 s). Loops that rebuild strings and then test or store them, such as $s += length $g over a package variable or $w = $i & 1 ? "a" : "", no longer fall back to the interpreter on every trip.

  • Array references in compiled loops: element reads, stores, ++ and += through $r->[...], scalar(@$r), $#$r, and push, unshift, pop and shift through a reference compile, as do shift @a and a sub’s implicit shift of @_. Compiled code now honours a tied scalar holding the reference and a class that overloads @{}, which it had bypassed, and a loop that reads an array through a reference while updating it by name sees its own updates. An object removed by pop in a compiled loop is destroyed at the end of its statement, as in perl, not after the next one.

  • Hashes, pushes and new arrays in compiled loops: a plain hash element is looked up inline, an integer key without first being turned into a string, and the Benchmarks Game’s k-nucleotide at 25M runs 19.2% fewer instructions. A short-lived array built in a loop (my $v = [$i, $i + 1]) compiles, and one that never leaves the loop is kept in registers: such a loop runs 20.3 times as fast. A loop that pushes computed values onto an array, such as a sieve’s prime extraction, runs 2.93 times as fast.

  • List foreach loops such as for my $v (@a, @b), for ($x, $y) and for (1, 5, 9) compile, also nested in another compiled loop. A benchmark of such loops over two arrays runs in 0.276 s instead of 0.715 s (perl5 0.711 s).

  • Loop conditions that combine comparisons with &&, || or ?:, and while ($x) or until ($done) on a plain value, compile; such conditions had kept 133 loops in 14 of 21 real programs interpreted. A sub whose last statement is a compiled while loop no longer returns a wrong value in list context.

  • More loops compile. defined $v is tested when the loop runs instead of assumed when it compiles, and ref $x as a condition, <=> under use integer, a C-style for that stores into array elements (for (my $k = 0; $k < $n; $k += $q) { $a[$k] = 1 }) and a call to a sub defined further down the file no longer keep a loop interpreted.

  • Fewer compiled loops give up. Loops that compiled but left compiled code at every entry until pperl stopped trying now stay compiled: a foreach over constants, a foreach my $q (@q), whose loop variable is a value the array holds as well, a copy of a string or of undef into a my, $b[$i] += 1 over whole-number floats from a division, and a my $last = '' written on a rare branch. A modifier if as a loop’s last statement no longer grows perl’s stack: a 10-million-trip loop that took 27.1 MB against the interpreter’s 10 MB takes 14.6 MB and 227.8M instructions instead of 507.8M. A loop that keeps leaving compiled code for any other reason is given up after about 4,096 entries instead of retried forever.

  • JIT overhead: a program that compiles nothing pays less for the JIT; on a module-loading test file the cost over --no-jit fell from 32.5M to 14.5M instructions. An s/// with an expression replacement is no longer taken for a loop to compile, which had made one module’s test file 61% slower with the JIT (now 4.3%), and short foreach loops over lists and arrays are no longer compiled when they cannot repay the compile.

  • Integers stay integers. Values written by compiled loops keep the integer or floating-point form perl gives them: an integral result stored into an array element, by +=, or computed through int() stays an integer where it had become a float, as Devel::Peek and B showed, and later integer reads of it stay compiled. A loop that pperl tried and failed to compile no longer turns an integer variable into a float, which had left 14 string-function benchmarks about 10% slower with the JIT than without.

  • Compiled stores no longer run a tied variable’s STORE or an object’s DESTROY in the middle of compiled code: a store to a tied destination could leave a loop reading a reallocated array or crash it, and an object whose last reference was overwritten was destroyed when the loop ended instead of at the next statement. A lexical written both by a loop and by subs it calls, such as a variable a closure captures, keeps every write.

  • Memoize: the cache compiled code uses for a memoized function of numbers no longer makes small integer arguments evict each other. A memoized fib over the arguments 0 to 31 made 1.25M avoidable calls in 20M; its loop now also runs in parallel.

Modules#

  • The perl 5.44 core library ships inside pperl: every .pm and .pl file perl 5.44 installs is embedded, 733 more than before, and pperl no longer takes core modules from the host perl. On a host without perl the previous binary could not load Encode, File::Temp, Test::More, Math::BigInt, JSON::PP, Unicode::UCD or autodie, and on a host with perl 5.42 it loaded 5.42’s versions. threads and threads::shared ship their 5.44 modules too.

  • Net::SSLeay and IO::Socket::SSL: Net::SSLeay 1.96 is native in the full build, over OpenSSL 3 loaded at run time, with the 709 functions its distribution provides for that OpenSSL; a function the loaded library lacks stays undefined, as in a perl5 build against that library, so feature tests such as defined &Net::SSLeay::X answer correctly. IO::Socket::SSL 2.099 ships beside it. Both distributions’ test suites, and AnyEvent’s including its TLS test, match perl 5.44 running the original modules.

  • DB_File, GDBM_File, NDBM_File and ODBM_File are native and keep their databases in LMDB files, through liblmdb loaded at run time, instead of Berkeley DB or gdbm; DB_File supports Hash, Btree and Recno, and a gdbm or Berkeley DB file is recognised and refused, never written. GDBM_File writes and reads gdbm’s ASCII and binary dump formats, so gdbm 1.26 loads pperl’s dumps and pperl loads gdbm’s, which is how existing gdbm files move over. By default writes are held in one transaction until sync, untie or close; $Peta::LMDB::DBM_WRITE = 'through', or GDBM_File’s sync mode, commits each write, and a crash loses writes but never the file.

  • Peta::LMDB is new: LMDB’s environments, transactions, databases and cursors for Perl, with key comparators written in Perl and errors raised as Peta::LMDB::Error objects. The DBM modules above are built on it.

  • LMDB_File 0.14 is native over the same library, and its distribution’s tests match perl 5.44 with the original module. Closing an environment explicitly, closing it twice, or using a transaction of a closed environment no longer crashes; perl5 with the original module aborts or segfaults there.

  • Class::XSAccessor 1.19 is native, including the faster call path it gives its accessors. Moo uses it for attribute accessors; before, Moo found the host perl’s build, could not load it, and warned “Class::XSAccessor exists but failed to load” before falling back to pure Perl.

  • XML::Parser 2.59 is native in the full build, over libexpat 2.6.0 or newer loaded at run time, so XML::Dumper and the other modules built on XML::Parser work. Its distribution’s tests match perl 5.44 with the original module built against the same libexpat.

  • Unicode::LineBreak and HTML::Parser are native. Unicode::LineBreak 2019.001, with Unicode::GCString and Text::LineFold, breaks lines and grapheme clusters by the Unicode 17 rules and passes Unicode 17’s LineBreakTest and GraphemeBreakTest; Thai, Lao, Khmer and Myanmar text is segmented through the system’s ICU. HTML::Parser 3.86 matches perl5 with the original module on its whole test suite.

  • Unicode::Normalize 1.32 is native and passes its distribution’s tests and Unicode 17’s NormalizationTest, 400,680 cases.

  • Devel::Size 0.87 is native, and its distribution’s tests match perl 5.44 with the original module.

  • File::DosGlob, Sys::Syslog and Math::BigInt::FastCalc are native: File::DosGlob 1.12, Sys::Syslog 0.36 and Math::BigInt::FastCalc 0.5020.

  • PDL loads as on perl5: PDL.pm, PDL::Lite and PDL::LiteF are upstream’s, so require PDL loads nothing until import, and use PDL gives upstream’s exports, now including rcols, wcols, rfits, wfits, rpic, wpic, rim and wim, less 22 functions pperl does not have yet. PDL::IO::Misc, PDL::IO::Pnm, PDL::IO::FITS, PDL::IO::Pic, PDL::ImageRGB, PDL::Image2D and PDL::Compression load, PDL::Types and PDL::Constants are upstream’s, and use PDL::LiteF no longer leaves every operation dying with “PDL core struct is NULL”. A pperl built with PDL loads libquadmath when PDL::Core loads, no longer at startup, where it slowed every number-to-string conversion of every program.

  • PDL computations that gave wrong answers now match PDL 2.104: median and oddmedian of a multi-dimensional ndarray, avg, sums of byte and 64-bit ndarrays, stats, dim(-1), nbad and approx; seeded random numbers equal perl5’s bit for bit. In-place operations on a converted type ($d &= 0; $d |= 1 on doubles) no longer die and leak, cat of ndarrays of different types no longer writes garbage, and clip, hclip and lclip take ->inplace, an output and undef bounds as upstream does (lclip died).

  • PDL errors and operators follow upstream: errors go through PDL::barf with its backtrace, operations upstream does not run in place refuse ->inplace (a reduction had written its result over its input), and ne, lt, gt, le, ge and cmp on ndarrays die as on perl5 instead of comparing element by element. matmult and x report upstream’s dimension-mismatch message and promote vectors as upstream does, -$x keeps an unsigned type, and abs of a complex value is its modulus. Loading PDL under -w no longer warns about redefined subroutines, and the binary operators read their swap flag as an integer, running get-magic. An out-of-range dimension given to broadcast croaks at the first computation, where perl5 reads memory it does not own.

  • PDL multithreading: as in PDL 2.104, an operation on an ndarray of at least 1 Mi elements is split across threads by default, with upstream’s controls (set_autopthread_targ, set_autopthread_size, online_cpus and the rest); the thread count follows --threads, and --no-parallel turns the splitting off. Warnings and errors raised in the threads arrive after the join through PDL::cluck and PDL::barf, as upstream’s do. Reading ASCII and PNM data is not split, where upstream’s threaded readers crash.

  • PDL subclasses and redefinitions are honoured by the functions pperl runs natively for speed - new, null, the type constructors, flat, whole-array reductions such as sum and max, at, set and string conversion: each takes its shortcut only for a plain PDL while the subs upstream’s Perl would call are unchanged, and runs upstream’s Perl otherwise. A list assignment to flat’s result no longer clobbers the caller’s variable, a format argument to string conversion is no longer ignored, and string conversion clears $@ as upstream’s does.

  • Imager follows Imager 1.036, including its paletted images: every palette has 256 entries, and an index past the colours in use is rejected or read as black. Imager::Font::FT2 is native, over FreeType loaded at run time, and the Imager and FT2 tests match perl5 with the original Imager 1.036.

  • Exporter, Carp and File::Temp are perl 5.44’s own modules (Exporter 5.79, Carp 1.54) instead of pperl’s replacements, and the modules built into pperl export through the real Exporter as their originals do: Errno->can('import') is \&Exporter::import, and Config no longer inherits from Exporter.

  • List::Util: reduce with a block that branches, such as reduce { $a > $b ? $a : $b } 1 .. 500, no longer hangs with the JIT on, and it compiles: 2000 reductions over 2000 elements take 314M cycles against 363M on perl5. min and max over more than 65,536 numbers return the element itself, as perl5 does, not a new number: max of numeric strings returns "0300000", not 300000.

  • EV and Event: an EV periodic watcher’s callback no longer leaks two values per call, which had kept the watcher from ever being freed, and setting its reschedule callback takes effect, where EV 4.37 never calls it or fails every fire. EV’s signal names IOT, CLD and POLL mean their signals instead of out-of-range numbers. Event::queue with a wrong number of arguments dies with a usage message instead of crashing, and destroying an Event::generic::Source that still has watchers no longer frees it twice.

  • Cairo checks its methods’ arguments before reading them and reports a wrong call as the Cairo module does (Usage: Cairo::Matrix::translate(matrix, tx, ty)), and the MIME_TYPE_* and OUTLINE_ROOT constants are constants folded at compile time.

  • Native modules in release builds are part of the profile-guided optimisation training: List::Util, Scalar::Util, Encode, MIME::Base64, Digest::MD5, Digest::SHA, Storable, Data::Dumper, Coro and others had been built as code the training never ran. Digest::SHA’s final padding, Encode’s single-byte encoder and Cwd’s abs_path also no longer do work their C originals skip.

  • %Config describes what pperl provides: nonxs_ext, extensions and known_extensions name its pure-Perl and native extensions as Configure would, and bin, scriptdir, sitebin, sitescript, vendorbin, vendorscript and their *exp forms are defined, as in perl 5.44. Test suites that check $Config{extensions} no longer skip, and Pod::Find no longer warns about an uninitialised scriptdir.

  • Peta modules are versioned 0.2.0, below 1.0.0 until their interfaces are final; Peta::XS, which was 1.0, moves down with them. Peta::FFI::UUID’s uuid_unparse and uuid_is_null die on anything but a 16-byte UUID instead of padding or cutting it.

  • Smaller module changes. Sub::Exporter::Progressive 0.001013 ships, for File::Map. POSIX functions that return a system call’s status give a modifiable undef on failure, Config::config_re leaves a bad pattern’s error in $@, Digest::SHA leaves its argument downgraded as perl5 does, Coro::State::nice returns the previous priority, Math::GMP::_gmp_build_version exists, and Errno reports its version as 1.39. GD and Term::ReadLine::Gnu without their C library fail at require, and GD’s writers croak on failure, interlaced(0) turns interlacing on and alphaBlending and saveAlpha return nothing, all as the GD module does.

Command line#

  • CPU features: pperl -V lists the CPU extensions the binary was compiled for and those the JIT uses on the machine it runs on; --jit-stats prints them as a cpu= line.

Distribution#

  • Minimal builds: a pperl built without the standard native modules (native-min) answers require Imager, GD::Simple, Memoize::Expire or SDL2::Image with “Can’t locate …” naming the missing capability, instead of loading the Perl half and failing later.

Documentation#

  • Changelog: this changelog, with one page per version from 0.5.6 on. Every change of a version is accounted for, either in an entry or as internal work.

  • Index: one page per initial letter, plus one for symbols, under a letter overview. Every entry is the name itself and links to its page; a function also appears under its own name with its module in parentheses, as in access (POSIX), and a pragma as open (pragma) beside the built-in open. Besides modules and functions it now lists perl’s special variables and operators under perl’s own index terms, the command-line options and environment variables, the topics of every guide and tutorial, and every version of this changelog, with each module under the versions that changed it.

  • ^^ and ^^=: the operator reference documents perl’s high-precedence logical exclusive or; it said there was no ^^.

  • Errata: the chapter on errors in perl’s own documentation is withdrawn. A page removed from the documentation is now also removed from the published site, where it had stayed reachable by its address.

  • Module reference: the native modules’ pages document what a user calls, with synopses whose output is the same on perl 5.44 and pperl, the upstream version implemented, and a section on differences from upstream citing the tests that pin them. Every function is published under its Perl name (B::CV::GV, not cv_gv); PDL, Net::SSLeay, EV, Event, Storable, POSIX, Coro, Compress::Raw::Zlib, Unicode::Normalize, Peta::LMDB and the DBM modules gained full references, and Cairo’s pages moved from Peta::FFI::Cairo to Cairo.

  • Bundled modules: the 755 pure-Perl modules embedded in pperl have reference pages made from their own POD, with highlighted code and corrections where the original text does not apply to pperl; text about building XS modules is left out. XSLoader’s page describes how it starts native modules under pperl.

  • Graphics guides: the 2D and 3D graphics guides are translated into all 41 languages of the documentation, and factual errors in their English were corrected.

  • Translations: the German translation covers the native module reference pages, SDL2, Peta::WebGPU and the debugging guide, and every language’s catalogues follow the reference pages to their new places.