Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5cb9de8
chore(types): format cleanup to mach skill
octalide Jul 4, 2026
ce1bb66
chore(collections): format cleanup to mach skill
octalide Jul 4, 2026
9b58142
chore(allocator): format cleanup to mach skill
octalide Jul 4, 2026
800ef35
chore(chrono): format cleanup to mach skill
octalide Jul 4, 2026
bddcc33
chore(crypto): format cleanup to mach skill
octalide Jul 4, 2026
fd93075
chore(data): format cleanup to mach skill
octalide Jul 4, 2026
dc9b4b2
chore(encoding): format cleanup to mach skill
octalide Jul 4, 2026
14ddaa6
chore(io): format cleanup to mach skill
octalide Jul 4, 2026
347f617
chore(math): format cleanup to mach skill
octalide Jul 4, 2026
e36b427
chore(net): format cleanup to mach skill
octalide Jul 4, 2026
7cd8558
chore(process): format cleanup to mach skill
octalide Jul 4, 2026
5a834d2
chore(sync): format cleanup to mach skill
octalide Jul 4, 2026
cd5aa78
chore(system): format cleanup to mach skill
octalide Jul 4, 2026
2221587
chore(text): format cleanup to mach skill
octalide Jul 4, 2026
a77c6f7
chore(runtime): format cleanup to mach skill
octalide Jul 4, 2026
777f3b0
chore(std): format cleanup to remaining top-level modules
octalide Jul 4, 2026
65a2573
Merge pull request #363 from briar-systems/chore/362-format-cleanup
octalide Jul 4, 2026
60b3823
chore: normalize test labels to the topic: behavior convention
octalide Jul 4, 2026
14a464c
Merge pull request #365 from briar-systems/chore/364-test-labels
octalide Jul 4, 2026
b579480
release: v0.17.0
octalide Jul 4, 2026
69d9d3b
Merge pull request #366 from briar-systems/chore/release-0.17.0
octalide Jul 4, 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
44 changes: 43 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.17.0] - 2026-07-04

Harden no-libc `--pie` linux binaries with static-PIE self-relocation and fatal
`PT_GNU_RELRO` re-protection, add process-parallelism primitives, and grow a
unified JSON stack — streaming NDJSON emit, float parsing, and RFC 8259
escaping. Makes `[target.linux-arm64]` permanent behind a qemu CI lane, and
fixes the critical arm64 `--pie` startup crash and windows mixed-DLL link
correctness. Built with mach 2.14.1.

### Added

Expand Down Expand Up @@ -51,6 +58,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
verbatim) for `emit`, and `ESCAPE_ENSURE_ASCII` (RFC 3629 decode to `\uXXXX`,
astral surrogate pairs, U+FFFD on invalid input) for the stream. Both surfaces
are byte-identical to their pre-unification output (#338).
- data/json: float number parsing — numbers now accept the full RFC 8259 grammar
(`int frac? exp?`); a number carrying a fraction or exponent parses as a float
(correctly rounded through `std.text.parse`'s bignum decimal→f64 machinery over
the zero-copy source span), while integer-looking numbers still yield an `i64`
on the byte-identical path. `Value` gains `value_is_float` and `value_float`
(an integer widens to `f64`); `value_number` is unchanged, and `emit` writes
floats in shortest round-trippable form (#349).
- data/json: `value_string_decode(v, buf, len) -> Result[usize, str]` — resolves a
string value's raw on-wire escapes into logical bytes in a caller buffer, the
inverse of the emit escaper. Handles `\" \\ \/ \b \f \n \r \t` and `\uXXXX` (a
high+low surrogate pair combines into one astral code point via the validated
`std.text.utf8` encoder), and errors on malformed escapes. The parser keeps its
zero-copy raw-bytes representation; decoding is paid only at the consumption
point, resolving the parse-vs-emit representation asymmetry (#340).
- build/ci: a permanent `[target.linux-arm64]` (aarch64/linux/aapcs64) makes std
cross-buildable to aarch64 out of the box, and a `cross-arm64` CI lane
cross-builds the unit suite and runs it under `qemu-aarch64` for an automatic
aarch64 regression signal over the full suite (#280).

### Fixed

Expand All @@ -64,6 +89,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
panics naming the violated invariant in each case — the glibc stance, matching
`os.page_size`'s `AT_PAGESZ` precedent (#336) — so a `--pie` binary either runs
fully hardened or dies loudly; no silent-unhardened path remains (#347).
- runtime/linux: `_rt_relocate` now re-protects the RELRO segment's actual mapped
extent — taken from its backing `PT_LOAD` and rounded up to `AT_PAGESZ` — rather
than the ELF writer's page-padded `p_memsz`. On a 4 KiB-page aarch64 kernel the
padded `PT_GNU_RELRO` `p_memsz` (64 KiB, mach#1845) spanned an unmapped gap, so
the `mprotect` failed with `ENOMEM` and, being fatal (#347), crashed every
`--pie` binary at startup. Std half of the arm64 startup fix (mach#1885).
- system: `os.page_size` on linux now returns the runtime `AT_PAGESZ` the
entrypoint captures from the auxiliary vector at startup, instead of a
hardcoded 4096 — correct on aarch64 kernels configured for 16 KiB or 64 KiB
Expand All @@ -72,6 +103,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
source of truth; `std.runtime.linux.reloc` keeps its own pre-relocation read
for the RELRO mprotect. `AT_PAGESZ` is mandatory on linux, so page_size()
panics when it is unavailable rather than fabricating a default (#336).
- data/json: `emit` now escapes string values and object keys per RFC 8259 —
`"` → `\"`, `\` → `\\`, control bytes (0x00–0x1F) via the short escapes
`\b \t \n \f \r` or else `\u00xx`, all other bytes (including valid UTF-8)
verbatim. Values or keys holding a quote, backslash, or control byte previously
emitted invalid JSON (#337).
- system/os/windows: every `ext fun` (61 kernel32 imports plus the windows
runtime entrypoint) now carries an explicit `#[library]` decorator naming its
providing DLL. The unattributed imports previously relied on the COFF fallback
binding them to dependency 0 — correct only while `kernel32.dll` sorted first —
so the first mixed-DLL link (e.g. also linking `glfw3.dll`) mis-bound whole DLL
sets and hit `STATUS_ENTRYPOINT_NOT_FOUND` at load. Purely additive (#334).

## [0.16.2] - 2026-06-28

Expand Down
2 changes: 1 addition & 1 deletion mach.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
id = "std"
name = "Mach Standard Library"
version = "0.16.2"
version = "0.17.0"
src = "src"
dep = "dep"
target = "native"
Expand Down
18 changes: 9 additions & 9 deletions src/allocator.mach
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# std.allocator: generic allocator interface and utilities
# generic allocator interface and utilities
#
# this is intended to be the glue between platform memory primitives and higher
# level containers/types
Expand All @@ -19,7 +19,7 @@ use std.types.string.str;

use mem: std.memory;

# Allocator: backend-agnostic allocator interface
# backend-agnostic allocator interface
# ---
# ctx: opaque context passed to callbacks.
# fn_allocate: allocate (ctx, size, align) -> ptr (nil on OOM).
Expand All @@ -32,7 +32,7 @@ pub rec Allocator {
fn_deallocate: fun(ptr, ptr, usize, usize) i64;
}

# allocate_raw: allocate raw bytes using this allocator
# allocate raw bytes using this allocator
# ---
# a: allocator to use for this allocation.
# size: number of bytes (0 returns nil).
Expand All @@ -51,7 +51,7 @@ pub fun allocate_raw(a: *Allocator, size: usize, align: usize) Result[ptr, str]
ret ok[ptr, str](unwrap[ptr](opt_fn_allocate));
}

# reallocate_raw: reallocate an allocation in-place if possible, otherwise may return a new pointer
# reallocate an allocation in-place if possible, otherwise may return a new pointer
# ---
# a: allocator to use for this operation
# p: existing pointer (or nil to allocate new)
Expand All @@ -77,7 +77,7 @@ pub fun reallocate_raw(a: *Allocator, p: ptr, old_size: usize, new_size: usize,
ret ok[ptr, str](unwrap[ptr](opt_fn_reallocate));
}

# deallocate_raw: deallocate an allocation previously returned by allocate_raw or reallocate_raw
# deallocate an allocation previously returned by allocate_raw or reallocate_raw
# ---
# a: Allocator to use for this operation
# p: pointer previously returned by allocate_raw or reallocate_raw
Expand All @@ -88,7 +88,7 @@ pub fun deallocate_raw(a: *Allocator, p: ptr, size: usize, align: usize) i64 {
ret a.fn_deallocate(a.ctx, p, size, align);
}

# allocate: allocate count elements of type T
# allocate count elements of type T
# ---
# a: Allocator to use for this allocation
# count: number of elements to allocate
Expand All @@ -113,7 +113,7 @@ pub fun allocate[T](a: *Allocator, count: usize) Result[*T, str] {
ret ok[*T, str](unwrap_ok[ptr, str](res_allocate_raw)::*T);
}

# zallocate: allocate and zero-initialize count elements of T
# allocate and zero-initialize count elements of T
# ---
# a: Allocator to use for this allocation
# count: number of elements to allocate
Expand All @@ -132,7 +132,7 @@ pub fun zallocate[T](a: *Allocator, count: usize) Result[*T, str] {
ret ok[*T, str](p);
}

# reallocate: reallocate an allocation of T elements
# reallocate an allocation of T elements
# ---
# a: Allocator to use for this operation
# p: existing pointer
Expand All @@ -156,7 +156,7 @@ pub fun reallocate[T](a: *Allocator, p: *T, old_count: usize, new_count: usize)
ret ok[*T, str](unwrap_ok[ptr, str](res_reallocate_raw)::*T);
}

# deallocate: deallocate memory previously returned by alloc/zalloc for count
# deallocate memory previously returned by alloc/zalloc for count
# elements.
# ---
# a: Allocator to use for this operation
Expand Down
28 changes: 14 additions & 14 deletions src/allocator/arena.mach
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# std.allocator.arena: arena allocator implementation
# arena allocator implementation
#
# bump allocator with chunked growth. allocation is O(1) amortized.
# deallocate is a no-op except for the most recent allocation which
Expand All @@ -25,7 +25,7 @@ use allo: std.allocator;
use page: std.allocator.page;
use mem: std.memory;

# Chunk: header for each backing allocation.
# header for each backing allocation
# ---
# prev: previous chunk in the linked list
# cap: data capacity in bytes (data follows the header)
Expand All @@ -36,7 +36,7 @@ pub rec Chunk {

val CHUNK_HDR: usize = 16;

# Arena: bump allocator state with chunked growth.
# bump allocator state with chunked growth
# ---
# backing: allocator used for chunk allocations
# buf: current chunk data pointer (cached for fast-path)
Expand Down Expand Up @@ -77,12 +77,12 @@ fun max_usize(x: usize, y: usize) usize {
ret y;
}

# chunk_data: get data pointer for a chunk (data follows header).
# get data pointer for a chunk (data follows header)
fun chunk_data(c: *Chunk) ptr {
ret (c::usize + CHUNK_HDR)::ptr;
}

# alloc_chunk: allocate a new chunk with at least min_cap data bytes.
# allocate a new chunk with at least min_cap data bytes
fun alloc_chunk(ar: *Arena, min_cap: usize) *Chunk {
val total: usize = CHUNK_HDR + min_cap;
val res: Result[ptr, str] = allo.allocate_raw(?ar.backing, total, CHUNK_HDR);
Expand All @@ -94,13 +94,13 @@ fun alloc_chunk(ar: *Arena, min_cap: usize) *Chunk {
ret c;
}

# free_chunk: deallocate a chunk through the backing allocator.
# deallocate a chunk through the backing allocator
fun free_chunk(ar: *Arena, c: *Chunk) i64 {
val total: usize = CHUNK_HDR + c.cap;
ret allo.deallocate_raw(?ar.backing, c::ptr, total, CHUNK_HDR);
}

# grow: allocate a new chunk and make it current.
# allocate a new chunk and make it current
fun grow(ar: *Arena, min_size: usize) bool {
var new_cap: usize = max_usize(ar.cap * 2, min_size);
new_cap = max_usize(new_cap, ar.default_cap);
Expand All @@ -121,7 +121,7 @@ fun grow(ar: *Arena, min_size: usize) bool {
ret true;
}

# init: initialize an Arena with a backing allocator and optional initial capacity.
# initialize an Arena with a backing allocator and optional initial capacity
# ---
# ar: Arena to initialize
# backing: allocator used to provide backing storage
Expand Down Expand Up @@ -158,7 +158,7 @@ pub fun init(ar: *Arena, backing: *allo.Allocator, cap: usize) Option[str] {
ret none[str]();
}

# dnit: free all chunks and zero the Arena state.
# free all chunks and zero the Arena state
# ---
# ar: Arena to destroy
# ret: 0 if all backing frees succeeded, or first negative errno
Expand Down Expand Up @@ -189,7 +189,7 @@ pub fun dnit(ar: *Arena) i64 {
ret first_err;
}

# make: wire up an Allocator backed by this Arena.
# wire up an Allocator backed by this Arena
# ---
# a: Allocator to initialize
# ar: initialized Arena (must outlive the Allocator)
Expand All @@ -205,7 +205,7 @@ pub fun make(a: *allo.Allocator, ar: *Arena) Option[str] {
ret none[str]();
}

# reset: reset the Arena to empty, freeing all chunks except the first.
# reset the Arena to empty, freeing all chunks except the first
#
# existing allocations become invalid after reset.
# ---
Expand All @@ -231,7 +231,7 @@ pub fun reset(ar: *Arena) {
ar.last_size = 0;
}

# allocate: arena bump allocation callback.
# arena bump allocation callback
fun allocate(ctx: ptr, size: usize, align: usize) Option[ptr] {
if (ctx == nil) { ret none[ptr](); }

Expand Down Expand Up @@ -269,7 +269,7 @@ fun allocate(ctx: ptr, size: usize, align: usize) Option[ptr] {
ret some[ptr]((ar.buf::usize + start)::ptr);
}

# reallocate: arena resize callback. in-place for the most recent allocation if space permits.
# arena resize callback. in-place for the most recent allocation if space permits
fun reallocate(ctx: ptr, p: ptr, old_size: usize, new_size: usize, align: usize) Option[ptr] {
if (ctx == nil) { ret none[ptr](); }

Expand Down Expand Up @@ -307,7 +307,7 @@ fun reallocate(ctx: ptr, p: ptr, old_size: usize, new_size: usize, align: usize)
ret some[ptr](q);
}

# deallocate: arena free callback. rolls back the most recent allocation if matched.
# arena free callback. rolls back the most recent allocation if matched
fun deallocate(ctx: ptr, p: ptr, size: usize, align: usize) i64 {
if (ctx == nil) { ret -1; }

Expand Down
16 changes: 8 additions & 8 deletions src/allocator/bump.mach
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# std.allocator.bump: brk-based bump allocator
# brk-based bump allocator
#
# monotonically advances a brk pointer for small allocations (< 128KB),
# falls back to mmap for large allocations. deallocate is a no-op for
# brk-backed memory individual small allocations cannot be freed, only
# brk-backed memory, individual small allocations cannot be freed, only
# reclaimed on program exit. use an arena allocator for bulk-freeable
# allocation patterns, or a real heap allocator for general-purpose use.

Expand All @@ -21,7 +21,7 @@ use pmem: std.system.os;
val MMAP_THRESHOLD: usize = 131072;
val MIN_BRK_EXTEND: usize = 65536;

# BumpState: brk bump allocator tracking state.
# brk bump allocator tracking state
# ---
# current: current bump pointer position
# end: current end of the brk region
Expand All @@ -30,7 +30,7 @@ pub rec BumpState {
end: usize;
}

# make: initialize a bump-backed Allocator and its BumpState.
# initialize a bump-backed Allocator and its BumpState
# ---
# a: Allocator to initialize
# state: BumpState to initialize (must outlive the Allocator)
Expand All @@ -50,13 +50,13 @@ pub fun make(a: *allo.Allocator, state: *BumpState) Option[str] {
ret none[str]();
}

# align_up: align a value up to the given alignment (must be a power of two).
# align a value up to the given alignment (must be a power of two)
fun align_up(value: usize, align: usize) usize {
val mask: usize = align - 1;
ret (value + mask) & ~mask;
}

# allocate: allocate size bytes from the brk heap, or fall back to mmap for large allocations.
# allocate size bytes from the brk heap, or fall back to mmap for large allocations
# ---
# ctx: BumpState pointer
# size: number of bytes to allocate
Expand Down Expand Up @@ -99,7 +99,7 @@ fun allocate(ctx: ptr, size: usize, align: usize) Option[ptr] {
ret some[ptr](aligned::ptr);
}

# reallocate: reallocate a heap allocation, copying data to a new location if needed.
# reallocate a heap allocation, copying data to a new location if needed
# ---
# ctx: BumpState pointer
# p: pointer to existing allocation
Expand Down Expand Up @@ -141,7 +141,7 @@ fun reallocate(ctx: ptr, p: ptr, old_size: usize, new_size: usize, align: usize)
ret some[ptr](new_p);
}

# deallocate: deallocate a heap allocation. no-op for brk-backed memory.
# deallocate a heap allocation. no-op for brk-backed memory
# ---
# ctx: BumpState pointer
# p: pointer to deallocate
Expand Down
10 changes: 5 additions & 5 deletions src/allocator/page.mach
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# std.allocator.page: page allocator adapter
# page allocator adapter
#
# wraps platform page allocation functions for the Allocator interface.
# alignment is ignored since page allocations are always page-aligned.
Expand All @@ -12,7 +12,7 @@ use std.types.string.str;
use allo: std.allocator;
use pmem: std.system.os;

# make: initialize a page-backed Allocator.
# initialize a page-backed Allocator
# ---
# a: Allocator to initialize
pub fun make(a: *allo.Allocator) Option[str] {
Expand All @@ -26,7 +26,7 @@ pub fun make(a: *allo.Allocator) Option[str] {
ret none[str]();
}

# allocate: allocate size bytes via platform page allocator.
# allocate size bytes via platform page allocator
# ---
# ctx: unused context pointer
# size: number of bytes to allocate
Expand All @@ -40,7 +40,7 @@ fun allocate(ctx: ptr, size: usize, align: usize) Option[ptr] {
ret some[ptr](p);
}

# reallocate: reallocate a page-backed allocation.
# reallocate a page-backed allocation
# ---
# ctx: unused context pointer
# p: pointer to existing allocation
Expand All @@ -56,7 +56,7 @@ fun reallocate(ctx: ptr, p: ptr, old_size: usize, new_size: usize, align: usize)
ret some[ptr](result);
}

# deallocate: deallocate a page-backed allocation.
# deallocate a page-backed allocation
# ---
# ctx: unused context pointer
# p: pointer to deallocate
Expand Down
Loading