Download Latest Version v0.31.0 source code.zip (4.4 MB)
Home / v0.31.0
Name Modified Size InfoDownloads / Week
Parent folder
acton-0.31.0-1.aarch64.rpm 2026-10-08 93.5 MB
acton-0.31.0-1.x86_64.rpm 2026-10-08 90.8 MB
acton_0.31.0_amd64.deb 2026-10-08 83.4 MB
acton_0.31.0_arm64.deb 2026-10-08 82.1 MB
acton-linux-aarch64-0.31.0.tar.xz 2026-10-08 107.2 MB
acton-linux-x86_64-0.31.0.tar.xz 2026-10-08 109.7 MB
acton-macos-aarch64-0.31.0.tar.xz 2026-10-08 118.1 MB
acton-macos-x86_64-0.31.0.tar.xz 2026-10-08 100.2 MB
README.md 2026-10-08 29.2 kB
v0.31.0 source code.tar.gz 2026-10-08 3.6 MB
v0.31.0 source code.zip 2026-10-08 4.4 MB
Totals: 11 Items   793.1 MB 0

This release brings major performance improvements across the compiler, runtime, and standard library. Iteration is faster, many operations allocate less, and actor scheduling and timers have lower overhead. Acton now uses its own fork of the Boehm garbage collector, with performance improvements and opt-in tuning options. The release also adds immutable collections, compact numeric and bit containers, and a matrix module, alongside better performance tools, cleaner project builds, and many other fixes and improvements.

Notable changes

Faster programs

Iteration now ends through ordinary control flow: next() returns a maybe value instead of throwing an exception at the end. This makes some iteration-heavy applications more than twice as fast. Range loops, numeric conversions, hashing, and builtin operations allocate less, while actor scheduling avoids unnecessary queue and synchronization work. Linux and macOS now wait for timed messages on kernel timers instead of spinning, reducing CPU time for timer-heavy workloads by more than 90%.

Garbage collection

Acton now uses its own fork of the Boehm garbage collector, with improvements to parallel marking, thread-local allocation, and memory tracking. Improved defaults apply automatically, while opt-in options let applications tune heap growth, allocation budgets, mark storage, and dirty-page tracking through Build.act. acton.rts.get_gc_info() reports the active mode and settings to help inspect and compare configurations.

Linux and macOS applications can also choose mimalloc for C and native-library allocations. This is separate from garbage collection of Acton objects.

Performance benchmarks: acton test perf

acton test perf measures individual benchmarks at a fixed or calibrated workload size. Reports include timing, allocation and memory use, GC statistics, and optional hardware counters. Benchmarks mark their measured operations with t.loop(), so setup and cleanup around the loop do not count toward operation time or allocation totals.

Measurements can be compared with a saved report or another Git revision. For example, acton test perf --name my_test --compare git:main builds both project versions and measures main against the current working tree, including uncommitted changes, at a shared workload size. Runs use fresh processes and balanced pairs to reduce noise. Both versions use the running compiler and runtime; utils/perf-compare supports measuring changes to the compiler, runtime, or builtins themselves.

Scaling studies: acton test scale

acton test scale shows how a benchmark's cost grows with its workload. The benchmark defines what scale means, such as the number of elements to sort, and reads the chosen size with t.scale(). As with performance benchmarks, only the body of the t.loop() loop contributes to operation time and allocation totals.

The study increases the scale, usually doubling it, and measures each size in fresh processes until the observed growth settles over a broad range or a time or memory limit stops it. The report shows a table of growth between sizes and terminal charts of time, time per unit of scale, and allocated bytes.

A study can be compared with a saved study or a Git revision. By default, acton test scale --compare git:main measures main and the current working tree at the same completed workload sizes and overlays their curves. Explicit end bounds allow both versions to explore independently.

New collections

Immutable ilist, idict, and iset values provide collection APIs without mutation, with freeze() converting mutable collections into their immutable forms. array, bitarray, and bitset offer compact storage for numeric and Boolean workloads, while matrix adds numeric matrices with views that share the underlying storage.

Language

  • Add after now delay: call() for delays that start when the statement runs, such as retry backoffs and pauses between requests. Plain after continues to use the current message's scheduling baseline, keeping periodic work on a steady schedule and catching up after stalls. [#3162]
  • Add first-class immutable collection types by splitting collection protocols into read-only and mutable parts. IIndexed, ISliceable, ISequence, IMapping, and ISet expose access without mutation; ilist, idict, and iset implement these read-only protocols, while list, dict, and set add the corresponding mutation operations. The generic Freeze[A] protocol and freeze() consume mutable collections into their immutable forms, with hashable iset values usable as dictionary keys or nested sets. [#3122] [#3152]
  • Make augmented assignment use mutating protocol methods when a type overrides them. set applies |=, &=, -=, and ^= in place; list and bytearray apply += and *= in place; and bitset and bitarray update their packed storage and size. Aliases observe these changes, while immutable types retain value semantics and sum() leaves mutable start values unchanged. [#3226] [#3230]
  • End iteration through ordinary control flow instead of exceptions. Iterator.__next__() and next() now return maybe[A], using just(value) for items and nothing() for exhaustion. This makes some iteration-heavy real-world applications more than twice as fast. [#3137]
  • Treat mut and pure effects as temporarily interchangeable, allowing APIs written with either effect to compose while immutable local mutation rules are being redesigned. [#3084]

Compiler & Build

  • Compile direct synchronous for loops over range() without allocating the range object on the heap, and pass statically known range bounds as unboxed integers. Nested range loops keep their private iterators in stack storage, while range values that may escape the loop remain heap allocated. [#3189] [#3224]
  • Compile fixed tuple-literal assignments into scalar temporaries and stores, avoiding a temporary tuple allocation while preserving left-to-right evaluation and simultaneous-assignment semantics. [#3223]
  • Compile conversions between statically known fixed-width numeric types without boxing, while retaining range checks for narrowing conversions, and keep builtin sequence indices unboxed across lookup and mutable operations where supported. The reported nsieve 12 benchmark fell from 4.2 seconds to 1.9 seconds. [#3177]
  • Generate allocation-free code for explicit next() and __next__() calls even when producing and consuming the result are separated or the result is tested inside a larger condition, while preserving ordinary maybe values when they are passed elsewhere. [#3164]
  • Compile protocols that inherit another protocol with fixed type arguments without passing those type arguments to the wrong witness constructor, fixing C generation for nested protocol inheritance. [#3098]
  • Keep imported protocol witnesses in import and definition order, so broad imported extensions no longer override more specific builtin witnesses and change inferred types. [#3198]
  • Reuse statically known witnesses reached through multiple inherited protocols instead of constructing them repeatedly, including for list indexing and mutation. This makes affected benchmark programs run at three to six times their previous speed. [#3155]
  • Compile augmented <<= and >>= assignments with an int shift count for types such as bigint, matching the right-hand type accepted by ordinary shift expressions. [#3158]
  • Type check augmented ^= assignments for builtin and user-defined Logical types instead of crashing the compiler. [#3163]
  • Stop passing project-only module selection options to distribution packages such as std, eliminating spurious Zig invalid option messages in verbose build output and manually run build commands. [#3168]
  • Allow Zig to use the active macOS SDK again with current macOS 26 Command Line Tools instead of forcing its bundled headers through an obsolete compatibility workaround. [#3029]
  • Preserve unchanged generated root stubs and Zig build files when their contents match, avoiding timestamp-only rewrites that invalidate otherwise reusable warm-build output. [#3099]

CLI & Project Workflow

  • Generate build.zig and build.zig.zon under out/zig/ instead of the project root, keeping generated build files out of source trees and removing the need for project-specific ignore rules. Files left in project roots by earlier versions are no longer used and can be deleted. [#3166]
  • Select an Acton project within a dependency archive with subdir in Build.act or acton pkg add --subdir, so monorepos can provide multiple packages from one archive while preserving sibling path dependencies. Package upgrades retain the selection, and local overrides point directly to the selected project root. [#3153]
  • Allow applications to pass literal Zig build options through build_options in Build.act, including GC mark layout, dirty-page tracking backend, and page-hash table size controls. The root application's choices apply across its build, invalidate cached tests when changed, and are recorded with performance results. Explicit Linux backends fail instead of silently falling back when unavailable. [#3140] [#3146]
  • Add GC build options for heap growth and allocation budgets, heap block and mark-stack sizes, parallel range stealing, thread-local allocation, object end padding, and moved GC_realloc storage. Acton now defaults to range stealing, a larger initial mark stack, immediate thread-local free lists, and collector-reclaimed small realloc copies; each default can be restored to the previous behavior in Build.act, with runtime environment overrides where supported. [#3212]
  • Allow Linux and macOS applications to select mimalloc for ordinary C and native-library allocation with build_options = {"malloc": "mimalloc"}. The system allocator remains the default, and the option does not replace Boehm GC or change Acton object lifetimes. [#3188]
  • Allow Linux applications to set gc_disable_thp in Build.act to keep GC memory on ordinary pages, including newly allocated and reused heap memory. The updated bundled collector applies the setting without changing the default page policy. [#3142] [#3143]
  • Add dependency follows declarations in Build.act so a project can import a package selected through another dependency, while requiring every directly imported package to be declared by the importing project. [#3091]
  • Limit acton test --module and --name builds to selected modules and their imports, including list and watch modes, so focused test runs avoid compiling unrelated test modules and ignore production libraries groups. [#3086]
  • Refresh dependency build files even when selected test builds do not compile sources from that dependency, so switching from a full build to acton test --module or --name does not leave stale modules in Zig build plans. [#3097]
  • Stop obsolete acton test --watch compiler and test process groups on cancellation or rebuild completion, carry pending edits into the next generation, and avoid running stale tests after a failed final compile. [#3088]
  • Report phase timings with --timing for build planning, Acton compilation, build preparation, Zig work, and test execution/cache handling, including watch mode and Zig cache summaries. [#3085]

Runtime & Standard Library

  • Add compact fixed-size containers for numeric and Boolean workloads: array[int] and array[float] store unboxed values, bitarray packs Boolean values, and bitset represents bounded sets of non-negative integers. The new matrix module provides fixed-size numeric matrices. Rows and columns are mutable vector_view values, and rectangular, transposed, and reshaped views are matrices; all views share storage with the original. [#3202]
  • Reduce scheduler overhead and prevent lost wakeups when many runtime workers are active. Workers keep short runs of ready work on the same actor to avoid repeated queue operations and core migration, while yielding when other actors wait. They also avoid the global sync-pause mutex when no pause is requested, wait longer between retries on contended ready-queue spinlocks, and use matching memory fences to keep newly queued actors from being left asleep on Arm, including pinned-actor and synchronized-pause wakeups. Ready queues, worker statistics, pause state, and per-worker actor-ID sequences occupy separate cache lines so workers no longer contend on frequently updated shared state. Actor IDs remain unique but are no longer allocated from one global sequence. Fewer clock reads and direct hardware timer reads on supported aarch64 and x86_64 CPUs further reduce the cost of short actor work and improve sub-microsecond runtime timing statistics on macOS. Other CPUs retain the monotonic clock fallback, and --rts-verbose reports the worker clock in use. [#3205] [#3206] [#3208] [#3210] [#3214] [#3215] [#3218] [#3219] [#3221]
  • Read and write ready-queue heads and counts atomically, including where workers check a queue without taking its lock, removing data races that ThreadSanitizer reports at no added cost on x86_64 or aarch64. A queue's count is lowered only when a worker takes an actor, so it no longer drifts below the number of queued actors when workers race for the same actor. [#3232] [#3233]
  • Run timed messages on monotonic clocks, so wrong or adjusted wall clocks no longer move pending delays. Linux and macOS use suspend-aware clocks and kernel timers instead of spinning through the last fraction of each delay, cutting CPU use by more than 90% in dense timer workloads. This trades busy-wait precision for normal OS wakeup precision, particularly on macOS; other platforms use libuv's monotonic clock and timer. [#3222] [#3231]
  • Flush messages produced by a turn before an actor begins waiting for an awaited result, preventing another worker from resuming the actor concurrently and causing crashes, reordered messages, or incorrect timer baselines. [#3209]
  • Use 64-bit lengths for str, bytes, and bytearray, allowing their core operations to produce values larger than 2 GiB. Large widths, counts, and totals are checked without integer overflow; values beyond the supported limit raise ValueError, failed allocations raise MemoryError, and bytearrays can grow beyond 1 GiB without looping. bytes() also rejects integers outside 0 through 255 before narrowing them. [#3191] [#3193]
  • Fix correctness and crash bugs in str and shared sequence operations, including Unicode-aware tab expansion, empty-substring counting and replacement, separator splitting, negative search and slice bounds, negative replacement counts, quote escaping, large case conversions and slices, and negative-step list slice deletion. Numeric constructors now consume the whole input and consistently raise ValueError for invalid arguments, while chr() rejects surrogate code points. [#3185] [#3186]
  • Make concurrent xml.decode() calls safe when parsing malformed documents and reduce parser pressure on the GC by keeping libxml2 allocations outside the GC heap, using per-parse error state, and parsing input in place. Each failed call now reports its own error and position instead of risking a double-free crash; the bundled libxml2 is updated to 2.15.4. [#3170] [#3175]
  • Fix correctness and crash bugs across bytes and bytearray: ASCII-only case conversion and classification, representation quoting, tab expansion, suffix bounds, NUL-aware comparisons, separator splitting, partition results, stepped slice deletion, membership, and mutation range checks. Separator splitting no longer crashes with maxsplit=0 or repeatedly copies the remaining input, and derived bytearrays no longer alias their inputs. bytearray.hex() now returns exactly two characters per byte, and environment byte APIs reject embedded NULs instead of truncating names or values. [#3171] [#3173] [#3174]
  • Reuse fixed exceptions and their messages across builtin and standard-library failures, and reuse immutable builtin strings for None, True, False, and exact str values. Fixed-error benchmarks allocate 15-19% fewer bytes and run 5-7% faster while preserving exception types and diagnostics. [#3182] [#3183] [#3184]
  • Add explicit C extension APIs for shared versus copied str and bytes storage, including length-taking variants that preserve embedded NULs and validate UTF-8. Existing conversion names retain their behavior during the migration. [#3179]
  • Enable mprotect dirty-page tracking for incremental and generational GC on arm64 macOS by building the bundled collector with its maintained build configuration. Runtime input buffers now stay outside protected GC pages, so process, TCP, and UDP reads remain reliable when this mode is enabled; x86_64 macOS retains the previous fallback. [#3172]
  • Hash values from their existing byte representation instead of allocating a bytes wrapper, and hash tuple components with one hasher while preserving component boundaries. hash() also bypasses the stateful hasher allocation for int, u64, str, and bytes. This removes per-value and per-component allocations, making short values about 25% faster, two-component tuples roughly 45-50% faster, and 64 KiB strings almost ten times faster to hash. [#3160] [#3161] [#3216]
  • Add acton.rts.get_gc_info() for inspecting the collector's mode, configured and active dirty-tracking backends, marking and collection policy, heap and mark-stack layout, thread-local allocation behavior, and current heap, free, and unmapped byte counts. [#3146] [#3212]
  • Add allocation-free time.monotonic_ns() for reading the monotonic clock as an integer nanosecond count in deadline checks and other hot loops. [#3217]
  • Allocate bigint limb storage only when it is needed, and compute % without boxing a discarded quotient and divmod() tuple, reducing heap work for zero values and remainder-heavy arithmetic. [#3229]
  • Add math.ldexp(x, exp) and std.math.ldexp(x, exp) for computing x * 2**exp across the full Acton int exponent range. [#3190]
  • Update the bundled collector so unlimited generational collection can use parallel marking, idle userfaultfd monitoring sleeps, write protection handles Linux memory-mapping boundaries, and custom stop callbacks remain installed. Further fixes prevent missed userfaultfd writes, heap corruption in newly enabled thread-local and mark-stack modes, and allocation failure when a full heap can recover through collection. [#3144] [#3212]
  • Include bytes and complex in atom, allowing APIs that accept immutable builtin values to receive them. Unsupported atom inputs to numeric constructors now raise ValueError instead of terminating the process. [#3156] [#3159]
  • Return exactly two hexadecimal characters per input byte from bytes.hex(), preventing unrelated memory from being included in the result and corrupting hash strings. [#3133]
  • Preserve embedded NUL characters in str and bytes literals, % formatting and interpolated strings, print(), regular-expression patterns and matches, and when decoding bytes or bytearray to str. Comparisons, hashing, iteration, prefix and suffix checks, and JSON keys and values use the complete string; decoding rejects invalid UTF-8 or trailing JSON input after a NUL instead of accepting a truncated prefix, and json.decode() rejects non-object roots. File operations reject paths containing a NUL with ValueError instead of operating on the prefix before it. Where libc is available, string comparisons use its optimized memory comparison rather than Zig's bytewise fallback. [#3125] [#3126] [#3128] [#3196] [#3199] [#3200]
  • Allow list equality and inequality when elements implement only Eq, while ordering comparisons continue to require Ord. [#3120]
  • Hash bool, narrow integers, and float by value, so equal values in separate objects work reliably as set elements and dictionary keys. Positive and negative floating zero now produce the same hash. [#3141]
  • Add immutable iset values whose hashable elements make the set hashable as a whole, allowing them to be used as dictionary keys and nested inside other sets while mutable set values remain unhashable, and format them as iset({...}) in str() and repr() output. [#3103] [#3109] [#3135]
  • Return a valid empty string from str.rstrip() and str.strip() when they remove every character, including multibyte characters and custom strip sets. [#3138]
  • Report re.Match.start_pos and end_pos and accept re.match()'s start_pos in character positions rather than UTF-8 byte offsets, keeping regular-expression indexes consistent with Acton string indexing for non-ASCII text. [#3165]
  • Speed up == and != for str, bytes, bytearray, bigint, list, dict, and tuple values by returning immediately when both operands refer to the same object instead of comparing their contents. This avoids unnecessary work for large values and containers, and means lists, dictionaries, and tuples compare equal to themselves even when they contain NaN. [#3106]
  • Keep range() empty when its step points away from the stop value instead of yielding an erroneous element. [#3157]
  • Fix del and pop() on small dictionaries, which hold at most two entries. Removing a key could corrupt the dictionary's entry count, so str() showed the dictionary as empty, serialization dropped its entries, and comparisons with the dictionary as the left operand checked only lengths, so d == other was true for any other of the same length. [#3236]
  • Use libc's optimized memory fill where available, reducing time and CPU use in GC-heavy workloads while retaining Zig's implementation on platforms that need it. [#3139]
  • Implement builtin helpers such as enumerate, filter, map, max, min, sum, and zip in Acton and streamline collection iteration to avoid allocation-heavy iterator handling in common builtin operations. [#3104] [#3131]
  • Join str, bytes, and bytearray parts from list and ilist values without first materializing an identical temporary list, while preserving support for arbitrary iterables. [#3224]
  • Drain queued writes before closing server-side TCP connections, preventing large response payloads from being truncated when TCPListenConnection.close() follows write(). [#3110]
  • Use a stable min-heap for runtime timers, making large batches of after callbacks with increasing or equal deadlines scale logarithmically while preserving FIFO order for equal deadlines. [#3092]
  • Write each chunk passed to file.WriteFile.write() at the current position instead of at offset 0, so later writes no longer overwrite earlier ones, and write all of the data when the system writes only part of it. [#3167]
  • Add append to file.WriteFile to keep the content of a file and write at its end, and add file.FS.rename() to rename a file or directory. [#3167]

Packages & Distribution

  • Use Ubuntu 20.04 for Linux x86_64 CI and release artifacts to preserve glibc 2.31 compatibility after Debian 11 package downloads stopped working. [#3089] [#3096]
  • Fix bundled native dependency inputs for ActonDB and TLSuv by removing the backend dependency symlink, resolving backend packages through sibling dependencies, and matching TLSuv's active mbed TLS and keychain source set. [#3093] [#3094]

Documentation

  • Document division and remainder semantics for bounded signed and unsigned integers and bigint, including quotient rounding and remainder signs for negative operands. [#3114]

Testing & CI

  • Expand performance testing into a repeatable workflow for measuring individual benchmarks and how they scale with workload size. [#3105] [#3107] [#3112] [#3113] [#3115] [#3117] [#3118] [#3121] [#3124] [#3127] [#3130] [#3134] [#3139] [#3203] [#3204] [#3207] [#3220] [#3222]
  • acton test perf calibrates opt-in t.loop() benchmarks within a configurable time budget, warms up and measures fresh invocations, accepts explicit or recorded workload scales, and includes dedicated builtin and collection benchmarks.
  • acton test perf --compare accepts saved measurements or a Git revision; for example, acton test perf --name my_test --compare git:main compares a selected benchmark with main. Git comparisons build both versions, measure interleaved balanced pairs of fresh processes at a shared workload scale, and report uncertainty from the paired results while including uncommitted current-tree changes.
  • utils/perf-compare compares changes to Acton's compiler, runtime, or builtins against a Git revision using identical benchmark sources and a shared workload scale. It runs balanced process pairs, reuses cached baseline builds, supports selecting another benchmark project, and saves the source snapshot and measurements.
  • GC benchmarks measure collection under temporary allocations while retaining and updating an inventory of records, supporting comparisons of runtime and collector settings at explicit workload sizes.
  • Reports compare recorded baselines and show timing distributions, GC, allocation and memory statistics, peak RSS, and optional CPU, instruction, cycle, and IPC counters, with consistent elapsed-time measurement on macOS.
  • acton test scale varies t.scale() workloads across configurable start and end bounds, samples until growth stabilizes or resource limits stop the study, and supports three terminal charts for time, time per scale, and allocated bytes with readable binary units, plus journals and replay. Comparisons can use a recorded study or a Git revision, measuring the selected revision and the current working tree, including uncommitted changes, at the same completed workload sizes. Explicit end bounds apply to both revisions independently, and macOS scaling accounts for reclaimable memory before stopping.
  • Scaling charts default to zero-based linear axes, with --log available for wide ranges; compared curves share axes and show both final values.
  • Comparison summaries and legends list the baseline before the current run, color each label to match its curve, and keep wrapped headings aligned.
  • acton test --rts-wthreads N runs ordinary, performance, scaling, and stress test processes with a fixed worker count, includes it in cache and baseline identities, and enables reproducible scheduler scaling studies. The fleet benchmark measures a representative actor-and-timer workload, while the scheduling suite covers ring and independent-pair handoffs, deep mailboxes, fan-out, pipelines, latency under load, and work queued after replies. A timer-precision suite measures one-shot and periodic firing accuracy and CPU cost.
  • The cross-runtime xlang runner builds and interleaves equivalent Acton, Go, and Tokio scheduling workloads, then produces median summaries and interactive HTML charts for throughput, CPU cost, and latency.
  • Recorded performance and scaling baselines from different machines now still show deltas with an explicit warning instead of rejecting the comparison. Their live integration tests now run with the default make test suite and in CI.
  • Stop running full baseline performance comparisons on every pull request; use acton test perf --compare or utils/perf-compare when a change needs targeted measurement. [#3145]
  • Update standard-library tests to the supported testing signatures, restoring discovery and execution of tests that used the legacy callback form. [#3111]

Compatibility Notes

  • The buffers backing bytes and bytearray now contain exactly nbytes bytes, with no trailing NUL. C extensions must use the length, and code that needs a C string must reject embedded NULs and make a terminated copy. Serialized values remain compatible. [#3180]
  • Generic code that only reads indexed, sliced, sequential, or mapping values should use IIndexed, ISliceable, ISequence, or IMapping. str and bytes implement ISliceable, ilist implements ISequence, and idict implements IMapping rather than the corresponding mutable protocols. [#3152]
  • Custom iterators must return just(value) for each item and nothing() at exhaustion. Code that calls next() or __next__() directly must inspect the returned maybe value. [#3137]
  • Require predicates passed to filter() to return bool; code that returned another truthy value must convert it explicitly. [#3131]
Source: README.md, updated 2026-10-08