ADR-014: std in Nikaia, a Real Mapping, and a Parallel Driver

Status: Accepted Closes: fs::map reading rather than mapping, and a parallel section running its pieces in sequence Superseded in part by: ADR-002 D4 (when D1’s lowering runs), ADR-015 D3 (D3’s measurement) Related: ADR-001 D4 (Stage 0 is a transpiler — which is what makes D1 possible at all), ADR-296 D11 and D15

1. Context

Two things the flagship needed — a real mapping and a parallel driver — are dependency decisions rather than compiler ones.

A third question: can more of Nikaia be written in Nikaia? The answer is yes, in a bounded way, and the way matters more than the amount.

2. Decisions

D1 — Parts of nikaia-std are .nika files, compiled by Stage 0.

Changed by ADR-002 D4: std’s Nikaia half is lowered at release time by the nikaia binary and the .rs is committed beside the .nika; there is no build script and no build-dependency on the compiler.

The first such file is one function:

pub fn digit_value(c: char) -> i32 {
    return c as i32 - 48
}

The point is not its size. It is code the project itself runs on that the Nikaia compiler produced, and it establishes the mechanism by which the share grows: what a .nika file in std may contain is exactly what Stage 0 can lower, and each construct the compiler learns is a function that can move.

list.rs (a trait with a generic method) moves when traits and generics are lowered; cli.rs when std::env is reachable. fs.rs does not move: it is unsafe around a memory mapping, and that is the layer a self-hosted language still writes in the host.

D2 — fs::map is a memory mapping.

Read-only, validated as UTF-8 once when the map is made; after that a view of the text is a view of the pages, and the unsafe that says so is a comment pointing at the validation. A zero-length file cannot be mapped and is an empty input rather than an error.

ADR-296 D15’s allowance — leave a large read-only mapping to the OS at process exit — is not taken; the mapping is released on drop.

D3 — The parallel driver runs the pieces in parallel.

Changed by ADR-015 D3: the absolute figures are re-measured there; the ratio below is unchanged.

The backend’s rayon feature is enabled on the workspace dependency rather than on nikaia-std alone, for a build reason rather than a semantic one: cargo would otherwise hold two differently-featured copies of the crate, and the tests that compile emitted programs against the deps directory would have to know which one the runtime meant.

Measured on 8 million lines, 4 cores: wall time divides by the cores while user time is unchanged, which is what a frame plus a monoid buys and what nothing in the program says how to do.

Output is identical byte for byte at either setting — ADR-296 D10’s whole claim.

D4 — Tests read which rlibs belong together from crate metadata, not timestamps.

A deps directory can hold two rlibs of one crate that do not link together, and then “the newest file” is a coin toss.

An rlib’s dependencies can be read out as name-hash, the hash being the one in the file name. The tests anchor on nikaia_std, read the parser backend from it and winnow from that, and hand rustc one consistent set. This is why nikaia-std declares the parser backend as a dependency: the runtime and the parser it runs on are one dependency, and the chain needs somewhere to start.

3. Consequences

The flagship does everything its comments claim. A test checks it on the real file; the timing is not a test, because a test that asserts a speed-up fails on a loaded CI runner. The identical output at either setting is asserted; the speed-up is measured.

This site is open source. Improve this page.