Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 14 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1609,9 +1609,21 @@ jobs:
# had to pass. When it was finally run on 2026-09-24 it was RED:
# int_width_boundary, added to the parity suite for a different question,
# differs by construction on a 32-bit target.
- name: rv_exec (the RISC-V backend against the C backend, hosted side)
- name: rv_exec_partition (all-mode and CI partition invocations agree)
if: ${{ !cancelled() && matrix.group == 'containers' }}
run: python3 scripts/rv_exec_partition_check.py

- name: rv_exec contracts (hosted boundaries at both widths)
if: ${{ !cancelled() && matrix.group == 'riscv' }}
run: MEMU="$RUNNER_TEMP/downstream/memu" sh scripts/rv_exec_check.sh
run: MEMU="$RUNNER_TEMP/downstream/memu" sh scripts/rv_exec_check.sh --contracts

- name: rv_exec corpus32 (the full corpus against C)
if: ${{ !cancelled() && matrix.group == 'coroutines' }}
run: MEMU="$RUNNER_TEMP/downstream/memu" sh scripts/rv_exec_check.sh --corpus32

- name: rv_exec corpus64 (the full corpus against C)
if: ${{ !cancelled() && matrix.group == 'riscv-ownership' }}
run: MEMU="$RUNNER_TEMP/downstream/memu" sh scripts/rv_exec_check.sh --corpus64

- name: rv_vec_owner (logical threads own Vecs, RV32 and RV64 against C)
if: ${{ !cancelled() && matrix.group == 'riscv-ownership' }}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ source tree is needed to build or run an app.

It **self-hosts** — the Mere-in-Mere compiler, compiled by itself and run as Wasm, emits byte-identical output to the reference. The RISC-V backend (RV32IM, and RV64IM since v0.1.394) emits a flat binary with no external assembler or linker, and `--bare` hands the program the machine instead of a host; on that there is a kernel, a scheduler and a shell written in Mere, and the self-hosted compiler runs there as a user process — on a RISC-V CPU also written in Mere — emitting WAT byte-identical to the native one. See [docs/bare-metal.md](docs/bare-metal.md).

- **2946 tests passing** (`dune test` with the full CI toolchain), plus cross-backend parity (248 programs) and integration and differential checks against external oracles in CI. The test total includes optional Wasm checks and depends on the installed tools. The full-suite and parity counts are re-derived by `scripts/first_run_check.sh`.
- **2946 tests passing** (`dune test` with the full CI toolchain), plus cross-backend parity (250 programs) and integration and differential checks against external oracles in CI. The test total includes optional Wasm checks and depends on the installed tools. The full-suite and parity counts are re-derived by `scripts/first_run_check.sh`.
- **🏆 Bootstrap fixpoint — truly self-hosting** (v0.1.10, 2026-07-12): the Mere-in-Mere compiler, **compiled by itself and run as wasm, produces byte-identical output to the reference** — and that output runs correctly. Compiling a program with (A) the compiler under the interpreter and (B) the compiler compiled by itself yields the same WAT, byte for byte (CI-verified by a permanent fixpoint regression test). Getting there required guaranteed tail calls in the self-host codegen (`return_call_indirect`, Stage 55f) and fixing three latent self-compilation bugs (an eager pattern-check payload dereference, pointer-equality string compares in the untyped self-host codegen, and a missing `\r` lexer escape — Stage 55g). See [docs/changelog.md](docs/changelog.md).
- **🏗️ A fifth backend, and an operating system on top of it** (v0.1.134–198): `mere -rv` emits a flat RV32IM binary with no external assembler or linker, and `--bare` hands the program the machine instead of a host — raw memory arrives as an unforgeable window capability, traps as ordinary Mere closures. On that: a preemptive scheduler, a shell, a syscall boundary, and finally **Mere's own self-hosted compiler running as a user process on a Mere kernel, on a CPU written in Mere**, emitting WAT byte-identical to the native interpreter. `mere -rvg` then emits a debug map beside the binary — from the same item list the assembler consumes, so it describes the bytes that actually ran — which is what lets [riscv-dbg](https://github.com/284km/memu/tree/main/riscv-dbg) break on Mere **source lines**, print a backtrace, and step **backwards**. `mere -rv64` (v0.1.394) is the same backend at twice the width: an RV64IM binary from the same code generator. See [docs/bare-metal.md](docs/bare-metal.md).
- **🛰️ gRPC and GraphQL, against real clients** (v0.1.280–283): `contrib/proto` (protobuf wire both ways, `FileDescriptorSet`, and a code generator from `.proto`), `contrib/http2` (framing + HPACK + an h2c server with flow control in both directions and a worker pool), and `contrib/graphql` (lexer / parser / printer / SDL / executor with null propagation, introspection, and 21 of the specification's 32 validation rules). `grpcurl` and `graphql-js` are the oracles, and interfaces and unions resolve from `__typename` the way graphql-js's default `resolveType` does. See [contrib/proto/README.md](contrib/proto/README.md), [contrib/http2/README.md](contrib/http2/README.md), [contrib/graphql/README.md](contrib/graphql/README.md).
Expand Down
12 changes: 11 additions & 1 deletion RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,19 @@ This covers direct source entries and the runner's `apt-mirrors.txt` or
Repository suites, components, signing keys and other mirrors are preserved.
The setup action's opam cache remains enabled. Compare actual setup step
durations and cache-hit messages when measuring changes: a cache hit does not
avoid the action's system-package installation. All 188 gates and all four
avoid the action's system-package installation. All 191 gates and all four
build configurations remain required.

The hosted RISC-V execution sweep has three required partitions:
`rv_exec_check.sh --corpus32` in coroutines, `--corpus64` in RISC-V ownership,
and `--contracts` in RISC-V. The default `--all` still runs both corpora and
every boundary case. Split modes require their emulator runners. The
partition audit in the containers group compares compiler invocations with
all-mode, including a refused C reference and a refused RV32 program. This is
a routing check; the three real emulator jobs retain the output comparisons,
known-difference freshness checks, bounds and named refusals. Compare complete
job durations when measuring the split; setup time is part of the result.

## Binary releases

`release-build.yml` is shared by the distribution CI jobs and tagged
Expand Down
29 changes: 29 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ Major implementation milestones recorded per-slice (newest first). See `git log`

---

## Unreleased

Wasm now specializes a factory value whose uses settle on one concrete return
type, including aliases and nested scopes. Native C emits factory adapters
before the function bodies that reference them, and `with` bindings use the
same mangled name and lexical type environment as their body references.
The new factory parity fixture covers Vec, Map and OwnedVec factories.

`snapshot value` obtains an independent immutable plain value through the
existing region-result copier. Arguments run once before the scratch region
opens. Nested containers, functions, borrows and capabilities are refused;
unresolved wrapper types remain monomorphic so their bound cannot be bypassed.
Parity covers Map/Vec compaction and recycling, nested data, binary bytes,
argument effects and shadowing. The lifetime gate also checks ASan/UBSan,
removes a copy to expose use-after-free, and checks plain-value refusals on
the interpreter, checker and all four compiled targets.

The hosted RISC-V sweep is split into RV32 and RV64 corpora and a shared
boundary partition. All cases, known-difference freshness checks and refusal
checks remain in the default all-mode; an invocation audit checks their union
against the three CI modes. Required CI checks increase from 188 to 191:
two additional execution partitions and the partition audit. The nine groups,
four build configurations and existing required-check name are preserved.

The resumed-pinner lifetime negative control now forces reclamation under its
recorded running pinner. Merely removing the running-pinner guard could leave
the arena protected by incidental conservative pins and miss the intended
fault; the revised control tests the reclamation boundary directly.

## v0.1.711 — 2026-11-07

Native C and LLVM programs gain addressed UDP send and receive through
Expand Down
5 changes: 3 additions & 2 deletions docs/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,9 @@ Builtin values are supported when the backend implements the operation and
its use has a concrete callable type. Factory values such as `vec_new` and
`map_new` are supported when their return type is concrete, as in
[factory_value.mere](../examples/factory_value.mere). An unconstrained
`let make = vec_new` may retain a polymorphic return type; annotate it or
pass the builtin to a concretely typed parameter. Wrapping a call in `fn`
`let make = vec_new` is specialized on unreleased main when it has one concrete use type,
including through aliases; unresolved uses still need an annotation or a
concretely typed parameter. Wrapping a call in `fn`
remains useful when an operation has a backend-specific restriction. See the
standard-library reference for individual APIs.

Expand Down
1 change: 1 addition & 0 deletions docs/host-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ machine rather than a host.
| `sign` | yes | yes | yes | yes |
| `sin` | yes | yes | yes | yes |
| `sleep_ms` | yes | yes | yes | yes |
| `snapshot` | yes | yes | yes | yes |
| `spawn` | yes | yes | yes | yes |
| `sqrt` | yes | yes | yes | yes |
| `square` | yes | yes | yes | yes |
Expand Down
22 changes: 22 additions & 0 deletions docs/stdlib-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,28 @@ protects stopped stacks separately. The
[suffix fixture](../test/parity/map_set_suffix.mere) checks a copied list
snapshot across replacement and compaction.

**`snapshot : 'a -> 'a` (unreleased) copies an immutable plain value** into the enclosing
allocation scope, using the same deep copy as a region result. It supports
scalars, strings, bytes, SIMD values, tuples, and records/variants/lists whose
contents are also plain. Containers, functions, borrows and capabilities are
refused, including when hidden in a record or variant. The argument is
evaluated once in the caller's scope before copying; ordinary container read
ownership checks still apply.

```mere
let saved = snapshot (map_get m key);
let _ = map_compact m;
// saved's immutable contents remain usable here
```

Use it similarly with `vec_get`. Direct calls may snapshot different types.
A wrapper or alias whose snapshot argument type is unresolved is kept
monomorphic, so later uses cannot bypass the plain-value bound. Use separate
wrappers when several argument types are needed. The interpreter keeps its
immutable GC-managed value; C, LLVM, Wasm and RISC-V use region-result copying.
The [snapshot fixture](../test/parity/snapshot.mere) checks copies across
compaction and recycling, nested data, argument effects and lexical shadowing.

**`OwnedVec`**: `owned_vec_get : OwnedVec[T] -> int -> T` joins `owned_vec_new` /
`push` / `len` / `to_vec`, which are in language-reference.

Expand Down
9 changes: 6 additions & 3 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,9 +657,12 @@ node scripts/run_wasm.js v.wasm
Builtin factories can also be used as values on interpreter, C, LLVM and
Wasm when the factory has a concrete return type, for example
`let make = (vec_new : unit -> Vec[__heap, int]) in ...`.
An unconstrained `let make = vec_new` can retain a polymorphic return type
and be refused by codegen; use an annotation or pass it to a concretely typed
parameter, as in [factory_value.mere](../examples/factory_value.mere).
On unreleased main, `let make = vec_new` also works when all uses settle on one concrete return
type, including aliases. An annotation or a concretely typed parameter remains
useful when the return type would otherwise stay unresolved, as in
[factory_value.mere](../examples/factory_value.mere). The
[factory inference fixture](../test/parity/factory_inference.mere) checks the
unannotated form across interpreter, C, LLVM and Wasm.
Other builtin values follow the operation's backend support;
[builtin_as_value.mere](../test/parity/builtin_as_value.mere) is an executable example.

Expand Down
41 changes: 41 additions & 0 deletions lib/ast.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1473,6 +1473,47 @@ let rec rv_map_scoped ~(shadow : string list) (f : string list -> expr -> expr o
| Field_get (a, fl) -> { e with node = Field_get (g shadow a, fl) }
| Record_update (a, fs) -> { e with node = Record_update (g shadow a, List.map (fun (fl, x) -> (fl, g shadow x)) fs) }

(* Lower a typed snapshot to the existing region-result copier before backend
analysis. Evaluate the argument once, outside the scratch region, so an
argument's allocation and mutation effects stay in their original scope.
A value-position use becomes an ordinary closure with the same operation. *)
let snapshot_region_name = "__snapshot_region"

let lower_snapshots (prog : program) : program =
let copied loc ty value =
let name = "__snapshot_value" in
let read = { loc; ty; node = Var name } in
let block = { loc; ty; node = Region_block (snapshot_region_name, read) } in
{ loc; ty; node = Let ({ ploc = loc; pnode = P_var name }, value, block) }
in
let rec rewrite shadow e = rv_map_scoped ~shadow replace e
and replace shadow e =
match e.node with
| App ({ node = Var "snapshot"; _ }, arg) when not (List.mem "snapshot" shadow) ->
Some (copied e.loc e.ty (rewrite shadow arg))
| Var "snapshot" when not (List.mem "snapshot" shadow) ->
(match Option.map walk e.ty with
| Some (TyArrow (a, b)) ->
let name = "__snapshot_arg" in
let arg = { loc = e.loc; ty = Some a; node = Var name } in
Some { e with node = Fun (name, Some a, copied e.loc (Some b) arg) }
| _ -> None)
| _ -> None
in
let shadow = ref [] in
let decls = List.map (function
| Top_let (p, v) ->
let v = rewrite !shadow v in
shadow := pattern_vars p @ !shadow;
Top_let (p, v)
| Top_let_rec bs ->
shadow := List.map rb_name bs @ !shadow;
Top_let_rec (List.map (fun (n, l, v) -> n, l, rewrite !shadow v) bs)
| (Top_forward (n, _, _) | Top_extern (n, _)) as d ->
shadow := n :: !shadow; d
| d -> d) prog.decls in
{ decls; main = rewrite !shadow prog.main }

(* A structural copy with every `ty` cleared: `expr.ty` is mutable and set by
the typer, so a subtree the pass places twice (the bound in a guard, an
atomic argument in both branches of a dispatch) gets its own nodes. *)
Expand Down
18 changes: 16 additions & 2 deletions lib/codegen_c.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2615,7 +2615,16 @@ let rec emit_expr (e : Ast.expr) : string =
The close field is a `unit -> unit` closure; dispatch via the
closure struct's `.fn(.env, 0)`. *)
let value_c = emit_expr value in
let body_c = emit_expr body in
let prev_types = !current_var_types in
let prev_subst = !current_env_subst in
current_var_types := VtMap.add name (Option.value value.Ast.ty ~default:Ast.TyUnit) prev_types;
current_env_subst := List.filter (fun (n, _) -> n <> name) prev_subst;
let body_c =
Fun.protect (fun () -> emit_expr body) ~finally:(fun () ->
current_var_types := prev_types;
current_env_subst := prev_subst)
in
let name = c_safe_name name in
let close_call =
match value.Ast.ty with
| Some t ->
Expand Down Expand Up @@ -5033,6 +5042,8 @@ let rec emit_expr (e : Ast.expr) : string =
in
"((" ^ struct_name ^ "){" ^ String.concat ", " init_fields ^ "})"
| Ast.Region_block (name, body) ->
if name = Ast.snapshot_region_name then
Option.iter (Typer.check_snapshot_bound e.Ast.loc) e.Ast.ty;
(* Allocate a region buffer, evaluate body, free. The region's C
local name is `__region_<name>` — accessed by Ref/`&R v` when
emitting `R.alloc(...)` calls inside body. Default cap 1 MB.
Expand Down Expand Up @@ -15873,6 +15884,7 @@ let emit_c_header (prog : Ast.program) : string =
String.concat "\n" lines ^ "\n"

let emit_program ?(main_ty = Ast.TyInt) (prog : Ast.program) : string =
let prog = Ast.lower_snapshots prog in
(* Q-127: SETTLE EVERY UNDECIDED CONTAINER REGION BEFORE ANYTHING READS ONE.
A slot can hold a variable up to here, so that a call site inside a `region` block
can decide it; what is left means nobody did, and it becomes `__heap`.
Expand Down Expand Up @@ -18007,7 +18019,6 @@ let emit_program ?(main_ty = Ast.TyInt) (prog : Ast.program) : string =
@ (if top_global_decls = [] then []
else "/* Phase 30.2: top-level non-fn let values as file-scope globals */"
:: top_global_decls @ [""])
@ (if fn_defs = [] then [] else fn_defs @ [""])
@ (let eta_lines =
Hashtbl.fold (fun adapter (builtin, ret_ty) acc ->
(* Generate static adapter fn + const value for the eta-wrapped
Expand Down Expand Up @@ -18052,6 +18063,9 @@ let emit_program ?(main_ty = Ast.TyInt) (prog : Ast.program) : string =
if eta_lines = [] then []
else "/* Phase 35.1: nullary factory builtins as first-class values */"
:: eta_lines @ [""])
(* Lifted function bodies can reference a factory value. Its declaration
must precede those bodies, just as its runtime helpers precede it. *)
@ (if fn_defs = [] then [] else fn_defs @ [""])
@ (if !lib_mode then lib_boundary_parts else
let run_lines =
[ (* the stack bounds are read here, where the program's first frame is
Expand Down
3 changes: 3 additions & 0 deletions lib/codegen_llvm.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8721,6 +8721,8 @@ let rec emit_expr (env : env) (e : Ast.expr) : string =
emit_instr (Printf.sprintf " call void @__lang_region_block_release(ptr %s)" rv);
out
| Ast.Region_block (name, body) ->
if name = Ast.snapshot_region_name then
Option.iter (Typer.check_snapshot_bound e.Ast.loc) e.Ast.ty;
(* Allocate a fresh region locally, run body within it, free at exit.
The region's SSA ptr is pushed onto current_regions so Ref / view
constructions inside body find it by name. *)
Expand Down Expand Up @@ -18574,6 +18576,7 @@ let rewrite_of_json (prog : Ast.program) : Ast.program =
{ Ast.main; decls = decls0 @ List.rev !decls }

let emit_program ?(main_ty = Ast.TyInt) (prog : Ast.program) : string =
let prog = Ast.lower_snapshots prog in
let prog = rewrite_read_lines prog in
let prog = rewrite_of_json prog in
(* Q-127: SETTLE EVERY UNDECIDED CONTAINER REGION BEFORE ANYTHING READS ONE.
Expand Down
6 changes: 5 additions & 1 deletion lib/codegen_riscv.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2724,7 +2724,10 @@ and compile_node (env : env) (e : Ast.expr) : unit =
emit (LoadAddr (a0, label)) (* a0 = &block *)
(* v0.1.658: see compile_region *)
| Ast.Region_loop (_, x, body) -> compile_region env e body (Some x)
| Ast.Region_block (_, body) -> compile_region env e body None
| Ast.Region_block (name, body) ->
if name = Ast.snapshot_region_name then
Option.iter (Typer.check_snapshot_bound e.Ast.loc) e.Ast.ty;
compile_region env e body None
| Ast.Float_lit f ->
let b = Int64.bits_of_float f in
(* v0.1.656: on RV64 the halves are stored as the arithmetic stores them
Expand Down Expand Up @@ -8666,6 +8669,7 @@ let prepare_main (prog : Ast.program) : Ast.expr =
| Monomorph.Error (loc, msg) -> err loc msg

let build_items_sized (prog : Ast.program) : item list =
let prog = Ast.lower_snapshots prog in
let full = prepare_main prog in
code_span := 0x200000;
let rec settle round items =
Expand Down
Loading
Loading