# pperl 0.6.17 *2026-09-06 to 2026-09-09, 96 commits.* ## Language and runtime - **`__END__` in bundled modules** no longer opens a `DATA` handle. A module shipped inside pperl that ends in `__END__` followed by POD, `strict` and `warnings` among them, had a `DATA` handle reading that POD; as in perl5, only `__DATA__` gives a module a `DATA` handle. ## JIT and parallelization - **nbody**: the Benchmarks Game's nbody at 200,000 steps runs 293M instructions, from 2,778M. At its official 50,000,000 steps it runs 47.65G instructions in 6.36 s of CPU time, where Node.js takes 49.66G and 6.0 s. Besides the call and array work below, compiled loops keep floating-point values in registers instead of reloading them from memory, check a loop's constant bound once instead of on every iteration, and skip checks that cannot fail, such as overflow on `$j + 1` of a loop counter and the domain test on `sqrt` of a sum of squares. Placing the arrays' buffers so that loads and stores to different arrays no longer stall the processor cut nbody's CPU cycles by 18%. - **Calls to subroutines that loop**: a compiled loop that calls a subroutine with loops of its own, such as nbody's `advance()`, now compiles together with the called body, where before each call left compiled code and entered it again twice. The subroutine's `my` variables are held in registers, and its call frame is pushed and popped inside compiled code. `$_[0]` in a subroutine called from one place with a numeric literal argument reads the literal directly. - **Arrays of numbers** whose elements are all floating-point numbers, or all integers, when a compiled loop starts are read and written as flat memory, without a type check per element. For the loop's duration such an array's values are kept in a buffer and written back when the loop is left, and an index that is a loop counter is checked against the array's size once, at the loop's start. On nbody the buffer alone saves 15.3% of the instructions. - **Calls to small numeric subroutines** no longer leave compiled code: a loop calling a function such as `eval_A` in the Benchmarks Game's spectral-norm, a `use integer` body that reads `$_[0]` and `$_[1]` directly, passes the arguments in registers, and the function's own `my` variables are registers too. spectral-norm at N=500 runs 440.0M instructions, from 776.5M, and at its official N=5,500 takes 3.25 s of CPU time, from 5.33 s. - **Nested `while` and C-style `for` loops** inside a compiled loop compile as part of it, and an unlabelled `last` or `next` out of them is a jump. The Benchmarks Game's fannkuch-redux at n=9 runs 30.7% fewer instructions (6,650M to 4,609M). - **Number types after compiled loops** match perl5's. `$v = $i * 0.5` and `sqrt(4)` stay floating-point numbers even when the result is integral, an integer literal or a `use integer` result stays an integer, and an integer variable such as `$t = ($i <=> 250)` no longer carries a floating-point copy perl5 would not have. The values were right before; the flags, visible through `Devel::Peek`, were not. - **Loops left half-compiled by 0.6.16** compile fully again: a loop whose variables are all assigned before they are read (`$t = ($i <=> 250)`), one that updates a package variable (`$G = $G + 1`), and one whose counter was used as a float during its first iterations (`sin($i)`). They had fallen back to a slower, checked mode; for the package-variable loop that was 2.2 times the instructions. - **JIT compile time**: a subroutine the JIT would compile and then reject as a numeric function is rejected before machine code is generated. nbody's fixed compile cost falls from 157M to 107M instructions, and the Benchmarks Game's mandelbrot at size 120, a run that is mostly compilation, executes 9.5% fewer instructions. ## Modules - **Peta::WebGPU** is new: GPU compute on the wgpu library, in builds with the `webgpu` feature. `Peta::WebGPU::Device->new` opens a device with the adapter's own limits, compiles WGSL shaders, creates storage and uniform buffers and pipelines, and runs compute passes; `have_gpu()` and `adapters()` report what the machine has. A shader or validation error is a Perl exception with wgpu's message, and a method with no natural result returns its object, so `or die` works and calls chain. A PDL ndarray uploads to a buffer directly (`$gpu->storage_buffer(float(1 .. 8))`); a double-typed one is refused with the conversion to use, since WGSL has only 32-bit numbers. - **SDL2**: 25 object methods that die on failure, among them `clear`, `copy`, `draw_line`, `fill_rect`, `present`, the `save_*` methods and every `set_*`, returned nothing on success, so `$ren->clear or die` died when it worked. They now return a true value, the object where there is no natural result, and calls chain: `$ren->set_draw_color(30, 30, 40, 255)->clear`. The direct `SDL_*` functions still return what the C functions do. - **Loading modules under `-w`** prints no warnings perl5 does not print. `use POSIX ()`, and with it `use Imager`, printed 27: constant subroutines shared with Fcntl reported as redefined, and two "Ambiguous call resolved as CORE::open()". `use PDL; use PDL::MatrixOps` printed 48 redefinitions, `use PDL::Lite; use PDL` 532 and `use PDL::LiteF; use PDL` 631, because PDL's sub-modules were loaded twice. - **PDL printing** follows upstream PDL's rules. Elements print as perl prints numbers, so `pdl(1.0) - pdl(0.88096)` shows `0.11904`, not `0.11904000000000003`; `$PDL::doubleformat` and `$PDL::floatformat` exist and take effect, and `$PDL::toolongtoprint` is honoured. A 3-D ndarray prints as nested blocks instead of one flat list, `PDL->null` as `Null`, an empty ndarray as `Empty[0]`, and a 2-D ndarray ends with its newline. - **PDL**: `append` on a slice no longer aborts pperl and `glue` no longer fails with "Not physical but doesn't have parent"; neither needs `->sever` first. `-=`, `+=`, `*=`, `/=`, `**=` and `%=` on an ndarray no longer warn "Use of uninitialized value in subroutine entry". `pdl('BAD')` is a 0-dimensional ndarray, `pdl('[[5]]')` stays 2-dimensional, and `norm` normalises a vector as upstream's does instead of dividing the whole ndarray by its Frobenius norm. `use PDL ()` no longer defines `main::pdl`. ## Documentation - **Module reference**: the Imager and GD pages include documentation that was written but never published: Imager's colour classes, its kinds of image and the channel mask, and `GD::Image` and `GD::Font`. - **Graphics guides**: the 2D graphics guide is fully translated again in all 41 languages after its move to SDL2. The 3D guide's chapter on opening a window with an OpenGL context is withdrawn, since pperl's GPU binding is Peta::WebGPU, and the guides' examples save their images with `$ren->save_png`.