iter raw-buf.4 rawbuf-payload-termnew-desugar (DONE, drop-call deferred to .5): RawBuf works, prints 60 (refs #7)

Ships RawBuf end-to-end as a consumer of the raw-buf.3 scope-qualified
intrinsic mechanism, plus the general Term::New construction sugar.
Working subset committed; the drop-CALL ratification (no-leak) is
re-carved to raw-buf.5 (see b49f57d) — the flat drop FUNCTION ships here,
its call-insertion needs a codegen resolution mechanism.

What ships (all green):
- raw_buf kernel-tier submodule (crates/ailang-kernel/src/raw_buf/):
  RawBuf TypeDef (param-in {Int,Float,Bool}, ctor B a) + four
  (intrinsic) ops new/get/set/size. parse_raw_buf + workspace injection
  (kernel-tier auto-imported); workspace count 4 -> 5.
- 12 scope-qualified INTERCEPTS entries RawBuf_{new,get,set,size}__{Int,
  Float,Bool} + emit fns: new allocs an @ailang_rc_alloc slab
  (8-byte i64 size header + n*width element bytes), get/set
  getelementptr+load/store at offset 8 + i*width, size loads the header.
  Mechanical on the raw-buf.3 naming + bijection machinery; bijection
  green (4 markers -> 12 entries).
- Term::New desugar (crates/ailang-core/src/desugar.rs): (new T <types>
  <values>) -> (app T.new <values>), runs before check so the
  type-scoped callee flows through the raw-buf.3 scope threading to
  RawBuf_new__T; drops the NewArg::Type (element type inferred from
  use). Both codegen Term::New deferral arms removed (replaced with
  unreachable!). Ratified by new_stubt_builds_and_runs.
- Flat intrinsic-storage drop FUNCTION @drop_raw_buf_RawBuf (single
  @ailang_rc_dec on the slab), emitted for any TypeDef whose new op is
  (intrinsic)-bodied — distinguishes RawBuf (intrinsic new) from StubT
  (real-body new -> generic ADT drop).
- E2E: raw_buf_int (-> 60), raw_buf_float (-> 4.0), raw_buf_bool
  (-> 42), raw_buf_param_in_reject (param-not-in-restricted-set).

In-scope additions beyond the literal plan (both sound, ratified):
- qualify_workspace_term now normalises a monomorphic cross-module
  type-scoped callee (StubT.new) to <home>.f; without it
  new_stubt_builds_and_runs cannot build (StubT.new is monomorphic, so
  the raw-buf.3 poly-mono path mints no symbol). Same-module + polymorphic
  callees carved out.
- diagnostic-behaviour: (new T ..) missing-new-op now surfaces
  type-scoped-member-not-found (desugar runs before check, bypassing
  synth's Term::New arm); new-arg-kind-mismatch obsoleted. Two unit
  tests updated to the new behaviour. param-in reject unaffected.

The 5 .ll snapshots gained @drop_raw_buf_RawBuf (+ partial) — injecting
raw_buf emits its drop fn into every program; benign, regenerated to the
final IR. (The plan's "snapshots stay green" assumption was wrong.)

Verification (orchestrator, this session): cargo test --workspace 676
passed / 0 failed / 2 ignored. raw_buf_int_e2e prints 60; bijection,
round-trip, new_stubt, float/bool/reject all green. The raw_buf_no_leak
test is NOT in this commit — it moves to raw-buf.5 with the drop-call
resolution that makes it pass (the slab currently leaks at scope close;
tracked, fixed next iter; milestone not released until close).
This commit is contained in:
2026-05-30 00:49:51 +02:00
parent b49f57d9c6
commit 8ac8756682
23 changed files with 879 additions and 83 deletions
+70 -4
View File
@@ -95,6 +95,17 @@ fn answer_intrinsic_builds_and_runs_printing_42() {
assert_eq!(out.trim(), "42");
}
/// raw-buf.4: the general `Term::New` desugar. `(new StubT 42)`
/// desugars to `(app StubT.new 42)` (StubT.new has a real
/// `(term-ctor StubT Stub x)` body, so it builds independent of
/// RawBuf), the Int is matched back out and printed. Ratifies the
/// desugar + the removal of both codegen `Term::New` deferral arms.
#[test]
fn new_stubt_builds_and_runs() {
let out = build_and_run("new_stubt_smoke.ail");
assert_eq!(out.trim(), "42");
}
#[test]
fn loop_recur_sum_to_runs_to_value() {
let stdout = build_and_run("loop_sum_to_run.ail");
@@ -864,10 +875,11 @@ fn workspace_lists_imported_modules() {
let modules = v["modules"].as_array().expect("modules must be array");
// the loader auto-injects every built-in kernel-tier module
// (`prelude` and, since prep.3 of the kernel-extension-mechanics
// milestone, the ratifying `kernel_stub`), so the count is the
// user's two modules plus the two built-ins.
assert_eq!(modules.len(), 4, "expected 4 modules: {stdout}");
// (`prelude`; since prep.3 of the kernel-extension-mechanics
// milestone, the ratifying `kernel_stub`; and since raw-buf.4 the
// first real-payload kernel extension `raw_buf`), so the count is
// the user's two modules plus the three built-ins.
assert_eq!(modules.len(), 5, "expected 5 modules: {stdout}");
let names: Vec<&str> = modules
.iter()
@@ -877,6 +889,7 @@ fn workspace_lists_imported_modules() {
assert!(names.contains(&"ws_lib"), "ws_lib missing: {names:?}");
assert!(names.contains(&"prelude"), "prelude missing: {names:?}");
assert!(names.contains(&"kernel_stub"), "kernel_stub missing: {names:?}");
assert!(names.contains(&"raw_buf"), "raw_buf missing: {names:?}");
}
/// Guards Iter 5b: `ail check examples/ws_main.ail.json --json` must
@@ -2210,6 +2223,59 @@ fn build_and_run_with_rc_stats(example: &str) -> (String, u64, u64, i64) {
)
}
/// raw-buf.4 worked consumer: a 3-slot Int RawBuf filled 10/20/30,
/// summed and printed. The end-to-end acceptance criterion — parse →
/// check → desugar `(new RawBuf …)` → mono `RawBuf_{new,set,get}__Int`
/// → intercept-routed codegen (alloc/store/load over the slab) →
/// native `60`.
#[test]
fn raw_buf_int_e2e() {
let out = build_and_run("raw_buf_int.ail");
assert_eq!(out.trim(), "60");
}
/// raw-buf.4 Float variant: a 2-slot Float RawBuf (1.5 + 2.5),
/// exercising the `double` load/store emits. Prints `4.0` (the `%g`
/// whole-double `.0` fallback).
#[test]
fn raw_buf_float_e2e() {
let out = build_and_run("raw_buf_float.ail");
assert_eq!(out.trim(), "4.0");
}
/// raw-buf.4 Bool variant: a 1-slot Bool RawBuf storing `true`, read
/// back and discriminated to an Int. Catches the `i1` store/load
/// syntax and the width-1 element offset.
#[test]
fn raw_buf_bool_e2e() {
let out = build_and_run("raw_buf_bool.ail");
assert_eq!(out.trim(), "42");
}
/// raw-buf.4 must-fail: `(con RawBuf (con Str))` violates the
/// `param-in (a Int Float Bool)` restriction. `ail check` exits
/// non-zero with the prep.3 `param-not-in-restricted-set` diagnostic.
#[test]
fn raw_buf_param_in_reject_e2e() {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap();
let src = workspace.join("examples").join("raw_buf_reject_str.ail");
let output = Command::new(ail_bin())
.args(["check", src.to_str().unwrap()])
.output()
.expect("ail check failed to run");
assert!(
!output.status.success(),
"ail check must reject RawBuf<Str>"
);
let stderr = String::from_utf8(output.stderr).expect("stderr utf8");
assert!(
stderr.contains("param-not-in-restricted-set"),
"expected param-not-in-restricted-set, stderr: {stderr}"
);
}
/// explicit-mode tail-recursive list-sum must
/// not leak the LCons outer cells.
///
+22
View File
@@ -249,6 +249,28 @@ ret:
ret void
}
define void @drop_raw_buf_RawBuf(ptr %p) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define void @partial_drop_raw_buf_RawBuf(ptr %p, i64 %mask) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define i32 @main() {
call i8 @ail_hello_main()
+22
View File
@@ -385,6 +385,28 @@ ret:
ret void
}
define void @drop_raw_buf_RawBuf(ptr %p) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define void @partial_drop_raw_buf_RawBuf(ptr %p, i64 %mask) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define i32 @main() {
call i8 @ail_list_main()
+22
View File
@@ -373,6 +373,28 @@ ret:
ret void
}
define void @drop_raw_buf_RawBuf(ptr %p) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define void @partial_drop_raw_buf_RawBuf(ptr %p, i64 %mask) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define i32 @main() {
call i8 @ail_max3_main()
+22
View File
@@ -277,6 +277,28 @@ ret:
ret void
}
define void @drop_raw_buf_RawBuf(ptr %p) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define void @partial_drop_raw_buf_RawBuf(ptr %p, i64 %mask) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define i64 @ail_sum_sum(i64 %arg_n) {
entry:
%v1 = call i1 @ail_prelude_eq__Int(i64 %arg_n, i64 0)
+22
View File
@@ -264,6 +264,28 @@ ret:
ret void
}
define void @drop_raw_buf_RawBuf(ptr %p) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define void @partial_drop_raw_buf_RawBuf(ptr %p, i64 %mask) {
entry:
%is_null = icmp eq ptr %p, null
br i1 %is_null, label %ret, label %live
live:
call void @ailang_rc_dec(ptr %p)
br label %ret
ret:
ret void
}
define i64 @ail_ws_lib_add(i64 %arg_a, i64 %arg_b) {
entry:
%v1 = add i64 %arg_a, %arg_b
+122 -43
View File
@@ -1081,6 +1081,29 @@ pub fn prepare_workspace_for_check(ws: &Workspace) -> Workspace {
(name.clone(), tys)
})
.collect();
// raw-buf.4: per-module set of *monomorphic* (non-`Forall`)
// top-level fn names. A type-scoped call `T.f` to a monomorphic
// op (e.g. the `kernel_stub.StubT.new` produced by the Term::New
// desugar) is normalised to the module-qualified `<home>.f` form
// by `qualify_workspace_term` — codegen recognises that spelling
// directly. Polymorphic type-scoped ops stay in the `T.f` form so
// monomorphisation can mint their scope-qualified `T_f__<elem>`
// symbols (the raw-buf.3 mechanism RawBuf's ops use).
let workspace_mono_fns: BTreeMap<String, BTreeSet<String>> = ws
.modules
.iter()
.map(|(name, m)| {
let fns: BTreeSet<String> = m
.defs
.iter()
.filter_map(|def| match def {
Def::Fn(f) if !matches!(f.ty, Type::Forall { .. }) => Some(f.name.clone()),
_ => None,
})
.collect();
(name.clone(), fns)
})
.collect();
Workspace {
entry: ws.entry.clone(),
modules: ws
@@ -1094,7 +1117,12 @@ pub fn prepare_workspace_for_check(ws: &Workspace) -> Workspace {
.unwrap_or_default();
(
k.clone(),
qualify_workspace_module(m, &own_local_types, &workspace_types_pre),
qualify_workspace_module(
m,
&own_local_types,
&workspace_types_pre,
&workspace_mono_fns,
),
)
})
.collect(),
@@ -4551,16 +4579,17 @@ pub fn qualify_workspace_module(
mut m: Module,
own_local_types: &IndexMap<String, TypeDef>,
module_types: &BTreeMap<String, IndexMap<String, TypeDef>>,
module_mono_fns: &BTreeMap<String, BTreeSet<String>>,
) -> Module {
for def in &mut m.defs {
match def {
Def::Fn(f) => {
f.ty = qualify_workspace_types(&f.ty, own_local_types, module_types);
qualify_workspace_term(&mut f.body, own_local_types, module_types);
qualify_workspace_term(&mut f.body, own_local_types, module_types, module_mono_fns);
}
Def::Const(c) => {
c.ty = qualify_workspace_types(&c.ty, own_local_types, module_types);
qualify_workspace_term(&mut c.value, own_local_types, module_types);
qualify_workspace_term(&mut c.value, own_local_types, module_types, module_mono_fns);
}
Def::Type(_) | Def::Class(_) | Def::Instance(_) => {
// TypeDef.ctors carry field types in the owner's local
@@ -4577,32 +4606,70 @@ pub(crate) fn qualify_workspace_term(
t: &mut Term,
own_local_types: &IndexMap<String, TypeDef>,
module_types: &BTreeMap<String, IndexMap<String, TypeDef>>,
module_mono_fns: &BTreeMap<String, BTreeSet<String>>,
) {
match t {
Term::Lit { .. } | Term::Var { .. } | Term::Recur { .. } => {}
Term::Lit { .. } | Term::Recur { .. } => {}
Term::Var { name } => {
// raw-buf.4: normalise a *cross-module* type-scoped call
// `T.f` to a *monomorphic* op into the module-qualified
// `<home>.f` form. The Term::New desugar emits
// `(app T.new …)` for every type; for a cross-module
// monomorphic `new` (e.g. kernel_stub's StubT.new) nothing
// downstream rewrites the `T.new` callee, so codegen would
// hit `unknown variable`. Three carve-outs:
// - a same-module type (`own_local_types`) keeps `T.f` —
// synth's TypeDef-first ladder resolves it directly and
// rewriting to the bare module self-name breaks the
// receiver resolution (`type-scoped-receiver-not-a-type`);
// - a *polymorphic* op keeps `T.f` so the raw-buf.3
// monomorphisation mechanism mints its scope-qualified
// `T_f__<elem>` symbol from that spelling (RawBuf's ops);
// - any non-TypeDef prefix is untouched (ordinary module
// or class-method dotted names).
if name.matches('.').count() == 1 {
let (prefix, suffix) = name.split_once('.').expect("checked");
if !own_local_types.contains_key(prefix) {
if let Some(home) = module_types.iter().find_map(|(m, types)| {
if types.contains_key(prefix) {
Some(m.clone())
} else {
None
}
}) {
let is_mono = module_mono_fns
.get(&home)
.is_some_and(|fns| fns.contains(suffix));
if is_mono {
*name = format!("{home}.{suffix}");
}
}
}
}
}
Term::App { callee, args, .. } => {
qualify_workspace_term(callee, own_local_types, module_types);
qualify_workspace_term(callee, own_local_types, module_types, module_mono_fns);
for a in args {
qualify_workspace_term(a, own_local_types, module_types);
qualify_workspace_term(a, own_local_types, module_types, module_mono_fns);
}
}
Term::Let { value, body, .. } => {
qualify_workspace_term(value, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types);
qualify_workspace_term(value, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(body, own_local_types, module_types, module_mono_fns);
}
Term::LetRec { ty, body, in_term, .. } => {
*ty = qualify_workspace_types(ty, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types);
qualify_workspace_term(in_term, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(in_term, own_local_types, module_types, module_mono_fns);
}
Term::If { cond, then, else_ } => {
qualify_workspace_term(cond, own_local_types, module_types);
qualify_workspace_term(then, own_local_types, module_types);
qualify_workspace_term(else_, own_local_types, module_types);
qualify_workspace_term(cond, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(then, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(else_, own_local_types, module_types, module_mono_fns);
}
Term::Do { args, .. } => {
for a in args {
qualify_workspace_term(a, own_local_types, module_types);
qualify_workspace_term(a, own_local_types, module_types, module_mono_fns);
}
}
Term::Ctor { type_name, args, .. } => {
@@ -4621,13 +4688,13 @@ pub(crate) fn qualify_workspace_term(
}
}
for a in args {
qualify_workspace_term(a, own_local_types, module_types);
qualify_workspace_term(a, own_local_types, module_types, module_mono_fns);
}
}
Term::Match { scrutinee, arms } => {
qualify_workspace_term(scrutinee, own_local_types, module_types);
qualify_workspace_term(scrutinee, own_local_types, module_types, module_mono_fns);
for arm in arms {
qualify_workspace_term(&mut arm.body, own_local_types, module_types);
qualify_workspace_term(&mut arm.body, own_local_types, module_types, module_mono_fns);
}
}
Term::Lam { param_tys, ret_ty, body, .. } => {
@@ -4635,23 +4702,25 @@ pub(crate) fn qualify_workspace_term(
*p = qualify_workspace_types(p, own_local_types, module_types);
}
**ret_ty = qualify_workspace_types(ret_ty, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types, module_mono_fns);
}
Term::Seq { lhs, rhs } => {
qualify_workspace_term(lhs, own_local_types, module_types);
qualify_workspace_term(rhs, own_local_types, module_types);
qualify_workspace_term(lhs, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(rhs, own_local_types, module_types, module_mono_fns);
}
Term::Clone { value } => {
qualify_workspace_term(value, own_local_types, module_types, module_mono_fns)
}
Term::Clone { value } => qualify_workspace_term(value, own_local_types, module_types),
Term::ReuseAs { source, body } => {
qualify_workspace_term(source, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types);
qualify_workspace_term(source, own_local_types, module_types, module_mono_fns);
qualify_workspace_term(body, own_local_types, module_types, module_mono_fns);
}
Term::Loop { binders, body } => {
for b in binders {
b.ty = qualify_workspace_types(&b.ty, own_local_types, module_types);
qualify_workspace_term(&mut b.init, own_local_types, module_types);
qualify_workspace_term(&mut b.init, own_local_types, module_types, module_mono_fns);
}
qualify_workspace_term(body, own_local_types, module_types);
qualify_workspace_term(body, own_local_types, module_types, module_mono_fns);
}
// prep.2 (kernel-extension-mechanics): prep.1 pre-pass partner.
// Mirror the Term::Ctor arm above — normalize a bare
@@ -4677,7 +4746,7 @@ pub(crate) fn qualify_workspace_term(
for arg in args.iter_mut() {
match arg {
NewArg::Value(v) => {
qualify_workspace_term(v, own_local_types, module_types)
qualify_workspace_term(v, own_local_types, module_types, module_mono_fns)
}
NewArg::Type(t) => {
*t = qualify_workspace_types(t, own_local_types, module_types)
@@ -7998,12 +8067,16 @@ mod tests {
);
}
/// prep.2 (kernel-extension-mechanics): a `(new T arg)` where T's
/// home module declares no `new` def fires
/// `NewTypeNotConstructible`. Property: synth's Term::New arm
/// resolves T's home module and then looks up `new` in its
/// globals; absence triggers the diagnostic with `type` and
/// `home_module` context.
/// raw-buf.4: a `(new T arg)` where T's home module declares no
/// `new` def is rejected. The Term::New desugar (raw-buf.4)
/// rewrites `(new T …)` to `(app T.new …)` *before* check, so the
/// rejection now arrives through the type-scoped resolution ladder
/// as `type-scoped-member-not-found` ("type `T` has no member
/// `new`") rather than the prep.2 `new-type-not-constructible`
/// code that synth's now-bypassed Term::New arm produced. The
/// rejection condition is identical (no constructor for T) and the
/// new diagnostic carries strictly more context (type + member +
/// home_module).
#[test]
fn new_type_not_constructible() {
let m: Module = serde_json::from_value(serde_json::json!({
@@ -8037,18 +8110,23 @@ mod tests {
};
let diags = check_workspace(&ws);
assert!(
diags.iter().any(|d| d.code == "new-type-not-constructible"),
"expected diagnostic code `new-type-not-constructible`, got {diags:?}"
diags.iter().any(|d| d.code == "type-scoped-member-not-found"),
"expected the desugared `(app T.new …)` to be rejected with \
`type-scoped-member-not-found` (no `new` member on T), got {diags:?}"
);
}
/// prep.2 (kernel-extension-mechanics): a `(new T ...)` whose
/// `new` def expects a Type-arg in position 0 (because its sig is
/// `forall a. (a) -> T a`) but the call site supplies a Value-arg
/// instead fires `NewArgKindMismatch`. Property: synth counts
/// NewArg::Type and NewArg::Value separately, compares Type count
/// to the Forall vars count, and emits the kind-mismatch on
/// inequality before any other check fires.
/// raw-buf.4: a `(new T value)` against a polymorphic
/// `new : forall a. (a) -> T a` checks cleanly — the element type
/// `a` is recovered by inference from use (here the `main` return
/// `(con T (con Int))` fixes `a = Int`). This pins the desugar's
/// type-arg-dropping design: `(new T …)` rewrites to
/// `(app T.new …)` carrying only the value args, with no NewArg
/// kind distinction surviving into check. The prep.2
/// `new-arg-kind-mismatch` diagnostic is obsoleted by this design
/// (there are no longer type-args at a `new` site to mismatch);
/// the call shape that used to trip it is now the canonical,
/// accepted form.
#[test]
fn new_arg_kind_mismatch_value_where_type() {
let m: Module = serde_json::from_value(serde_json::json!({
@@ -8096,8 +8174,9 @@ mod tests {
};
let diags = check_workspace(&ws);
assert!(
diags.iter().any(|d| d.code == "new-arg-kind-mismatch"),
"expected diagnostic code `new-arg-kind-mismatch`, got {diags:?}"
diags.is_empty(),
"(new T 42) against `forall a. (a) -> T a` must check cleanly — \
the desugar drops type-args and `a` is inferred from use; got {diags:?}"
);
}
+51
View File
@@ -699,6 +699,57 @@ impl<'a> Emitter<'a> {
self.body.push_str(&out);
}
/// raw-buf.4: emit the flat `drop_<m>_<T>` for an
/// intrinsic-storage TypeDef (e.g. RawBuf). The slab is
/// `[size:i64][primitive elements]` with no tag at offset 0, so
/// the generic tag-switch drop is wrong; the elements are
/// primitives carrying no recursive drops. The flat drop is just
/// the null-guarded outer rc-dec — same `entry/live/ret` shape
/// and `@ailang_rc_dec` call form as the generic drop's `join`
/// tail, minus the tag-switch.
pub(crate) fn emit_flat_intrinsic_drop_fn(&mut self, td: &TypeDef) {
let m = self.module_name;
let tname = &td.name;
let mut out = String::new();
out.push_str(&format!("define void @drop_{m}_{tname}(ptr %p) {{\n"));
out.push_str("entry:\n");
out.push_str(" %is_null = icmp eq ptr %p, null\n");
out.push_str(" br i1 %is_null, label %ret, label %live\n");
out.push_str("live:\n");
out.push_str(" call void @ailang_rc_dec(ptr %p)\n");
out.push_str(" br label %ret\n");
out.push_str("ret:\n");
out.push_str(" ret void\n");
out.push_str("}\n\n");
self.body.push_str(&out);
}
/// raw-buf.4: emit the flat `partial_drop_<m>_<T>` for an
/// intrinsic-storage TypeDef. Emitted for symbol-resolution
/// parity with the generic path (every TypeDef gets both a drop
/// and a partial-drop). No carve-out site can actually call it —
/// the sole ctor field is a primitive, never a moved-out ptr
/// field — so the `%mask` arg is ignored and the body is the same
/// flat outer rc-dec as the drop fn.
pub(crate) fn emit_flat_intrinsic_partial_drop_fn(&mut self, td: &TypeDef) {
let m = self.module_name;
let tname = &td.name;
let mut out = String::new();
out.push_str(&format!(
"define void @partial_drop_{m}_{tname}(ptr %p, i64 %mask) {{\n"
));
out.push_str("entry:\n");
out.push_str(" %is_null = icmp eq ptr %p, null\n");
out.push_str(" br i1 %is_null, label %ret, label %live\n");
out.push_str("live:\n");
out.push_str(" call void @ailang_rc_dec(ptr %p)\n");
out.push_str(" br label %ret\n");
out.push_str("ret:\n");
out.push_str(" ret void\n");
out.push_str("}\n\n");
self.body.push_str(&out);
}
/// resolve the `partial_drop_<owner>_<T>`
/// symbol for a type, parallel to the existing per-type drop
/// dispatch in `field_drop_call`. Returns `None` for non-ADT
+293
View File
@@ -185,6 +185,97 @@ pub(crate) static INTERCEPTS: &[Intercept] = &[
wants_alwaysinline: false,
emit: emit_stubt_peek_float,
},
// raw-buf.4: the 12 scope-qualified RawBuf ops. Symbols are
// `RawBuf_{new,get,set,size}__{Int,Float,Bool}` (raw-buf.3
// scope-qualified mono mangling). Slab layout:
// `[ size:i64 @0 ][ elem_0 @8 ][ elem_1 @8+w ]…`; element widths
// Int/Float = 8, Bool = 1. `own`/`borrow (con RawBuf T)` both
// lower to `ptr`. The header is always i64, so `new`/`size` are
// element-type-independent; `get`/`set` carry the element type.
Intercept {
name: "RawBuf_new__Int",
expected_params: &["i64"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_new_int,
},
Intercept {
name: "RawBuf_get__Int",
expected_params: &["ptr", "i64"],
expected_ret: "i64",
wants_alwaysinline: false,
emit: emit_rawbuf_get_int,
},
Intercept {
name: "RawBuf_set__Int",
expected_params: &["ptr", "i64", "i64"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_set_int,
},
Intercept {
name: "RawBuf_size__Int",
expected_params: &["ptr"],
expected_ret: "i64",
wants_alwaysinline: false,
emit: emit_rawbuf_size_int,
},
Intercept {
name: "RawBuf_new__Float",
expected_params: &["i64"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_new_float,
},
Intercept {
name: "RawBuf_get__Float",
expected_params: &["ptr", "i64"],
expected_ret: "double",
wants_alwaysinline: false,
emit: emit_rawbuf_get_float,
},
Intercept {
name: "RawBuf_set__Float",
expected_params: &["ptr", "i64", "double"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_set_float,
},
Intercept {
name: "RawBuf_size__Float",
expected_params: &["ptr"],
expected_ret: "i64",
wants_alwaysinline: false,
emit: emit_rawbuf_size_float,
},
Intercept {
name: "RawBuf_new__Bool",
expected_params: &["i64"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_new_bool,
},
Intercept {
name: "RawBuf_get__Bool",
expected_params: &["ptr", "i64"],
expected_ret: "i1",
wants_alwaysinline: false,
emit: emit_rawbuf_get_bool,
},
Intercept {
name: "RawBuf_set__Bool",
expected_params: &["ptr", "i64", "i1"],
expected_ret: "ptr",
wants_alwaysinline: false,
emit: emit_rawbuf_set_bool,
},
Intercept {
name: "RawBuf_size__Bool",
expected_params: &["ptr"],
expected_ret: "i64",
wants_alwaysinline: false,
emit: emit_rawbuf_size_bool,
},
];
pub(crate) fn lookup(name: &str) -> Option<&'static Intercept> {
@@ -499,6 +590,207 @@ pub(crate) fn emit_stubt_peek_float(emitter: &mut Emitter<'_>) -> Result<()> {
Ok(())
}
// ---------------------------------------------------------------
// raw-buf.4: RawBuf op emits over an `@ailang_rc_alloc` slab.
// Slab layout `[ size:i64 @0 ][ elem_0 @8 ][ elem_1 @8+w ]…`. The
// rc-header is auto-prepended by `@ailang_rc_alloc` (it returns the
// payload ptr); the i64 size header lives at payload offset 0, the
// elements follow at offset 8. Element widths: Int/Float = 8, Bool
// = 1. `new`/`size` are byte-identical across element types (the
// header is always i64); `get`/`set` carry the element load/store
// type and offset width. Intercepts run only under `--alloc=rc`, so
// `@ailang_rc_alloc` is hardcoded (not `alloc.fn_name()`).
// ---------------------------------------------------------------
// --- Int variants (element type i64, width 8) ---
pub(crate) fn emit_rawbuf_new_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let cap = emitter.locals[n - 1].1.clone(); // i64 capacity
let elems_bytes = emitter.fresh_ssa();
let total = emitter.fresh_ssa();
let slab = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {elems_bytes} = mul i64 {cap}, 8\n"));
emitter.body.push_str(&format!(" {total} = add i64 {elems_bytes}, 8\n"));
emitter
.body
.push_str(&format!(" {slab} = call ptr @ailang_rc_alloc(i64 {total})\n"));
emitter.body.push_str(&format!(" store i64 {cap}, ptr {slab}\n"));
emitter.body.push_str(&format!(" ret ptr {slab}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_get_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 2].1.clone();
let i = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
let v = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" {v} = load i64, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret i64 {v}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_set_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 3].1.clone();
let i = emitter.locals[n - 2].1.clone();
let v = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" store i64 {v}, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret ptr {b}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_size_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 1].1.clone();
let sz = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {sz} = load i64, ptr {b}\n"));
emitter.body.push_str(&format!(" ret i64 {sz}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
// --- Float variants (element type double, width 8) ---
pub(crate) fn emit_rawbuf_new_float(emitter: &mut Emitter<'_>) -> Result<()> {
// Header always i64; element width 8 — byte-identical to Int's new.
emit_rawbuf_new_int(emitter)
}
pub(crate) fn emit_rawbuf_get_float(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 2].1.clone();
let i = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
let v = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" {v} = load double, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret double {v}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_set_float(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 3].1.clone();
let i = emitter.locals[n - 2].1.clone();
let v = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 8\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" store double {v}, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret ptr {b}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_size_float(emitter: &mut Emitter<'_>) -> Result<()> {
// Header always i64 — byte-identical to Int's size.
emit_rawbuf_size_int(emitter)
}
// --- Bool variants (element type i1, width 1) ---
pub(crate) fn emit_rawbuf_new_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let cap = emitter.locals[n - 1].1.clone(); // i64 capacity
let elems_bytes = emitter.fresh_ssa();
let total = emitter.fresh_ssa();
let slab = emitter.fresh_ssa();
// element width 1 byte for Bool.
emitter.body.push_str(&format!(" {elems_bytes} = mul i64 {cap}, 1\n"));
emitter.body.push_str(&format!(" {total} = add i64 {elems_bytes}, 8\n"));
emitter
.body
.push_str(&format!(" {slab} = call ptr @ailang_rc_alloc(i64 {total})\n"));
emitter.body.push_str(&format!(" store i64 {cap}, ptr {slab}\n"));
emitter.body.push_str(&format!(" ret ptr {slab}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_get_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 2].1.clone();
let i = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
let v = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 1\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" {v} = load i1, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret i1 {v}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_set_bool(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let b = emitter.locals[n - 3].1.clone();
let i = emitter.locals[n - 2].1.clone();
let v = emitter.locals[n - 1].1.clone();
let off = emitter.fresh_ssa();
let byteoff = emitter.fresh_ssa();
let ptr = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {off} = mul i64 {i}, 1\n"));
emitter.body.push_str(&format!(" {byteoff} = add i64 {off}, 8\n"));
emitter.body.push_str(&format!(
" {ptr} = getelementptr inbounds i8, ptr {b}, i64 {byteoff}\n"
));
emitter.body.push_str(&format!(" store i1 {v}, ptr {ptr}\n"));
emitter.body.push_str(&format!(" ret ptr {b}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_rawbuf_size_bool(emitter: &mut Emitter<'_>) -> Result<()> {
// Header always i64 — byte-identical to Int's size.
emit_rawbuf_size_int(emitter)
}
#[cfg(test)]
mod tests {
use super::{lookup, INTERCEPTS};
@@ -523,6 +815,7 @@ mod tests {
for module in [
ailang_surface::parse_prelude(),
ailang_surface::parse_kernel_stub(),
ailang_surface::parse_raw_buf(),
] {
for def in &module.defs {
match def {
+62 -22
View File
@@ -694,6 +694,29 @@ fn llvm_scalar(t: &Type) -> &'static str {
}
}
/// raw-buf.4: does `f`'s declared return type construct the TypeDef
/// named `td_name`? Used by the drop-dispatch loop to recognise an
/// intrinsic-storage `new` op (its return is `(own (con T …))`).
/// Peels `Forall` → `Fn.ret`, then matches the result `Type::Con`
/// name against `td_name` (the `own`/`borrow` mode lives in
/// `ret_mode`, not in the type, so it needs no peeling). Accepts both
/// the bare same-module name and a `<module>.<T>` qualified spelling.
fn fn_returns_type(f: &FnDef, td_name: &str) -> bool {
let mut ty = &f.ty;
if let Type::Forall { body, .. } = ty {
ty = body;
}
let Type::Fn { ret, .. } = ty else {
return false;
};
match ret.as_ref() {
Type::Con { name, .. } => {
name == td_name || name.rsplit('.').next() == Some(td_name)
}
_ => false,
}
}
fn main_is_void(t: &Type) -> bool {
match t {
Type::Fn { params, ret, .. } => {
@@ -1070,6 +1093,32 @@ impl<'a> Emitter<'a> {
if matches!(self.alloc, AllocStrategy::Rc) {
for def in &self.module.defs {
if let Def::Type(td) = def {
// raw-buf.4: a TypeDef whose construction is
// compiler-supplied (its `new` op is an (intrinsic),
// not a real term-ctor body) has a flat slab layout
// [size:i64][elements], NOT a tagged-ADT layout. The
// generic drop loads a tag at offset 0 and switches
// on it — but offset 0 here is the size, so the
// generic tag-switch drop is wrong. Emit a flat
// drop instead: a single rc-dec on the slab pointer
// (primitive elements carry no recursive drops).
// This distinguishes RawBuf (intrinsic `new`) from
// StubT (real-body `new` → generic ADT drop), where
// a param_in heuristic would misclassify StubT
// (also param_in). A matching flat partial-drop is
// emitted for symbol-resolution parity with the
// generic path (no carve-out site can actually
// target it — the sole field is a primitive).
if self.module.defs.iter().any(|d| {
matches!(d, Def::Fn(f)
if f.name == "new"
&& matches!(f.body, Term::Intrinsic)
&& fn_returns_type(f, &td.name))
}) {
self.emit_flat_intrinsic_drop_fn(td);
self.emit_flat_intrinsic_partial_drop_fn(td);
continue;
}
if td.drop_iterative {
// opt-in iterative-drop body. The
// recursive cascade overflows the C stack on
@@ -2083,18 +2132,13 @@ impl<'a> Emitter<'a> {
self.block_terminated = true;
Ok(("0".into(), "i8".into()))
}
// prep.2 (kernel-extension-mechanics): Term::New has no
// direct codegen path in this milestone — typecheck
// accepts it via synth's elaboration, but lowering to LLVM
// IR requires a desugar pass that resolves the `new` def
// and rewrites the site as a Term::App. That desugar lands
// in the raw-buf milestone. Until then, codegen of
// Term::New is an internal error: a workspace that types-
// checks should not reach codegen with a surviving Term::New.
Term::New { .. } => Err(CodegenError::Internal(
"Term::New requires the type's `new` def to be desugared first; \
codegen does not yet support Term::New directly — milestone raw-buf".into(),
)),
// raw-buf.4: the Term::New desugar (desugar.rs) rewrites
// every `(new T …)` to `(app T.new …)` before check and
// codegen, so no Term::New survives to lowering. The arm is
// kept only for match exhaustiveness.
Term::New { .. } => {
unreachable!("Term::New is desugared to (app T.new …) before codegen — raw-buf.4")
}
Term::Intrinsic => Err(CodegenError::Internal(
"Term::Intrinsic must be consumed by the intercept route in fn-body \
emission, not lowered as an expression; reaching lower_term means an \
@@ -3287,16 +3331,12 @@ impl<'a> Emitter<'a> {
// value at its own position).
Term::Loop { body, .. } => self.synth_with_extras(body, extras),
Term::Recur { .. } => Ok(Type::unit()),
// prep.2 (kernel-extension-mechanics): Term::New has no
// codegen path in this milestone (see lower_term's arm).
// synth_with_extras would only fire if codegen reaches a
// surviving Term::New as a callee or sub-expression — same
// BUG class as a surviving LetRec — so an internal error
// here mirrors lower_term's stance.
Term::New { .. } => Err(CodegenError::Internal(
"Term::New cannot be synthed at codegen — must be desugared first; \
codegen support lands in the raw-buf milestone".into(),
)),
// raw-buf.4: Term::New is desugared to (app T.new …) before
// codegen (see lower_term's arm), so it never reaches the
// synth path. The arm is kept only for match exhaustiveness.
Term::New { .. } => {
unreachable!("Term::New is desugared to (app T.new …) before codegen — raw-buf.4")
}
Term::Intrinsic => Err(CodegenError::Internal(
"Term::Intrinsic cannot be synthed at codegen — an intrinsic body is \
signature-only and routed through the intercept registry, never an expression"
+24 -8
View File
@@ -923,16 +923,32 @@ impl Desugarer {
}));
in_full
}
Term::New { type_name, args } => Term::New {
type_name: type_name.clone(),
args: args
Term::New { type_name, args } => {
// raw-buf.4: (new T <types…> <values…>) desugars to
// (app T.new <values…>) — a type-scoped call so synth's
// dotted-name branch resolves it through the
// TypeDef-first ladder and the raw-buf.3 scope threading
// mints `T_new__<elem>`. The leading NewArg::Type is
// dropped: the AST carries no type-args on App, and the
// element type is recovered by ordinary inference from
// how the result is used (spec § Term::New desugar).
// Runs before check, so no Term::New reaches the
// type-checker or codegen.
let value_args: Vec<Term> = args
.iter()
.map(|arg| match arg {
NewArg::Value(v) => NewArg::Value(self.desugar_term(v, scope)),
NewArg::Type(t) => NewArg::Type(t.clone()),
.filter_map(|arg| match arg {
NewArg::Value(v) => Some(self.desugar_term(v, scope)),
NewArg::Type(_) => None,
})
.collect(),
},
.collect();
Term::App {
callee: Box::new(Term::Var {
name: format!("{type_name}.new"),
}),
args: value_args,
tail: false,
}
}
Term::Intrinsic => t.clone(),
}
}
@@ -772,3 +772,23 @@ fn kernel_stub_module_round_trips() {
}).expect("kernel_stub ships the peek ratifier op (raw-buf.3)");
assert!(matches!(peek.body, Term::Intrinsic), "peek is an (intrinsic) marker");
}
/// raw-buf.4: pin the JSON byte-shape of the raw_buf kernel-tier
/// base-extension module. Mirror of `kernel_stub_module_round_trips`.
#[test]
fn raw_buf_module_round_trips() {
let m = ailang_surface::parse_raw_buf();
assert!(m.kernel, "raw_buf is kernel-tier");
assert_eq!(m.name, "raw_buf");
let json = serde_json::to_value(&m).expect("serialise raw_buf");
let recovered: Module =
serde_json::from_value(json).expect("raw_buf round-trips through serde");
assert!(recovered.kernel);
assert_eq!(recovered.name, "raw_buf");
let td = recovered.defs.iter().find_map(|d| match d {
Def::Type(t) if t.name == "RawBuf" => Some(t),
_ => None,
}).expect("RawBuf TypeDef present");
let allowed = td.param_in.get("a").expect("RawBuf.a restricted");
assert!(allowed.contains("Int") && allowed.contains("Float") && allowed.contains("Bool"));
}
+7 -5
View File
@@ -49,13 +49,15 @@ fn loads_example_workspace_happy_path() {
assert_eq!(ws.entry, "ws_main");
assert!(ws.modules.contains_key("ws_main"));
assert!(ws.modules.contains_key("ws_lib"));
// the loader auto-injects two built-in kernel-tier modules
// (`prelude` and `kernel_stub` — see prep.3 of the
// kernel-extension-mechanics milestone), so the count is the
// user's two modules plus those two.
assert_eq!(ws.modules.len(), 4);
// the loader auto-injects three built-in kernel-tier modules
// (`prelude`, `kernel_stub` — see prep.3 of the
// kernel-extension-mechanics milestone — and `raw_buf`, the
// first real-payload kernel extension, raw-buf.4), so the count
// is the user's two modules plus those three.
assert_eq!(ws.modules.len(), 5);
assert!(ws.modules.contains_key("prelude"));
assert!(ws.modules.contains_key("kernel_stub"));
assert!(ws.modules.contains_key("raw_buf"));
}
#[test]
+2
View File
@@ -9,5 +9,7 @@
//! crate carries zero parser code.
mod kernel_stub;
mod raw_buf;
pub use kernel_stub::SOURCE as STUB_AIL;
pub use raw_buf::SOURCE as RAW_BUF_AIL;
+10
View File
@@ -0,0 +1,10 @@
//! Raw-buf submodule — the Form-A source of the `raw_buf` kernel-tier
//! base-extension module (RawBuf: a mutable indexed flat buffer of
//! primitive elements). Parsed by `ailang_surface::parse_raw_buf`,
//! round-trip-pinned by `raw_buf_module_round_trips`.
/// Source-of-truth Form-A text for the `raw_buf` kernel module.
/// Declares the `RawBuf` TypeDef (`param-in (a Int Float Bool)`) and
/// the four `(intrinsic)` ops `new`/`get`/`set`/`size`; codegen
/// supplies their bodies via the `RawBuf_op__<T>` intercept entries.
pub const SOURCE: &str = include_str!("source.ail");
@@ -0,0 +1,26 @@
(module raw_buf
(kernel)
(data RawBuf (vars a)
(doc "Mutable, indexed, fixed-size flat buffer of primitive elements. Opaque single-ctor handle; codegen intercepts emit raw alloc/load/store over an @ailang_rc_alloc slab. Element type restricted to {Int, Float, Bool} via param-in.")
(ctor B a)
(param-in (a Int Float Bool)))
(fn new
(doc "Allocate an uninitialised RawBuf of capacity n. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_new__<T> emits @ailang_rc_alloc plus the i64 size header. Element bytes are uninitialised; caller must set before get.")
(type (forall (vars a) (fn-type (params (con Int)) (ret (own (con RawBuf a))))))
(params n)
(intrinsic))
(fn get
(doc "Indexed read. UB if i >= (size b); caller checks bounds via size. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_get__<T> emits getelementptr plus load.")
(type (forall (vars a) (fn-type (params (borrow (con RawBuf a)) (con Int)) (ret a))))
(params b i)
(intrinsic))
(fn set
(doc "Indexed write. Linear: own in, own out. Under uniqueness in-place; under shared copy-on-write (Issue #22). Compiler-supplied (intrinsic) body; codegen intercept RawBuf_set__<T> emits getelementptr plus store.")
(type (forall (vars a) (fn-type (params (own (con RawBuf a)) (con Int) a) (ret (own (con RawBuf a))))))
(params b i v)
(intrinsic))
(fn size
(doc "Element count. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_size__<T> emits a single i64 load from the slab header.")
(type (forall (vars a) (fn-type (params (borrow (con RawBuf a))) (ret (con Int)))))
(params b)
(intrinsic)))
+3 -1
View File
@@ -36,6 +36,8 @@ pub mod loader;
pub mod parse;
pub mod print;
pub use loader::{load_module, load_workspace, parse_kernel_stub, parse_prelude, PRELUDE_AIL};
pub use loader::{
load_module, load_workspace, parse_kernel_stub, parse_prelude, parse_raw_buf, PRELUDE_AIL,
};
pub use parse::{parse, parse_term, ParseError};
pub use print::{print, term_to_form_a, type_to_form_a};
+25
View File
@@ -56,6 +56,20 @@ pub fn parse_kernel_stub() -> Module {
.expect("ailang_kernel::STUB_AIL must parse as a Module")
}
/// raw-buf.4: parse the embedded raw_buf kernel-tier base-extension
/// module bytes into a `Module`. Mirror of [`parse_kernel_stub`].
///
/// Source-of-truth: `ailang_kernel::RAW_BUF_AIL`. Declares the
/// `RawBuf` TypeDef with `param-in (a Int Float Bool)` and the four
/// `(intrinsic)` ops; codegen supplies their bodies via the
/// `RawBuf_op__<T>` intercept registry.
///
/// Panics on parse failure — build-time-validated by every drift run.
pub fn parse_raw_buf() -> Module {
crate::parse(ailang_kernel::RAW_BUF_AIL)
.expect("ailang_kernel::RAW_BUF_AIL must parse as a Module")
}
fn is_ail_source(path: &Path) -> bool {
path.extension().and_then(|s| s.to_str()) == Some("ail")
}
@@ -131,6 +145,17 @@ pub fn load_workspace(entry: &Path) -> Result<Workspace, WorkspaceLoadError> {
}
modules.insert("kernel_stub".to_string(), parse_kernel_stub());
// parse_raw_buf() injects the raw_buf kernel-tier base-extension
// module — RawBuf, the first real-payload kernel extension. Its
// source carries `(kernel)`, so the kernel-flag filter below
// auto-imports it.
if modules.contains_key("raw_buf") {
return Err(WorkspaceLoadError::ReservedModuleName {
name: "raw_buf".to_string(),
});
}
modules.insert("raw_buf".to_string(), parse_raw_buf());
// Derive the implicit-imports list from `kernel: true` modules.
// Replaces the previous hardcoded `&["prelude"]` literal: any
// workspace-loaded module that carries the kernel flag is now
+13
View File
@@ -0,0 +1,13 @@
(module new_stubt_smoke
(fn unwrap_int
(doc "Project the Int back out of a StubT<Int>.")
(type (fn-type (params (con StubT (con Int))) (ret (con Int))))
(params s)
(body (match s (case (pat-ctor Stub x) x))))
(fn main
(doc "raw-buf.4 desugar ratifier: (new StubT 42) desugars to (app StubT.new 42), builds, and prints 42.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(let s (new StubT 42)
(app print (app unwrap_int s))))))
+10
View File
@@ -0,0 +1,10 @@
(module raw_buf_bool
(fn main
(doc "raw-buf.4 Bool variant: store true in a 1-slot Bool RawBuf, read it back, discriminate to an Int and print (42). Exercises RawBuf.new/.set/.get @ Bool (i1 load/store, width-1 offsets).")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print
(let buf (new RawBuf (con Bool) 1)
(let buf (app RawBuf.set buf 0 true)
(if (app RawBuf.get buf 0) 42 0)))))))
+12
View File
@@ -0,0 +1,12 @@
(module raw_buf_float
(fn main
(doc "raw-buf.4 Float variant: fill a 2-slot Float RawBuf with 1.5/2.5 and print their sum (4.0). Exercises RawBuf.new/.set/.get @ Float (double load/store).")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print
(let buf (new RawBuf (con Float) 2)
(let buf (app RawBuf.set buf 0 1.5)
(let buf (app RawBuf.set buf 1 2.5)
(app + (app RawBuf.get buf 0)
(app RawBuf.get buf 1)))))))))
+14
View File
@@ -0,0 +1,14 @@
(module raw_buf_int
(fn main
(doc "raw-buf.4 worked consumer: fill a 3-slot Int RawBuf with 10/20/30 and print their sum (60). Exercises RawBuf.new/.set/.get @ Int end-to-end.")
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print
(let buf (new RawBuf (con Int) 3)
(let buf (app RawBuf.set buf 0 10)
(let buf (app RawBuf.set buf 1 20)
(let buf (app RawBuf.set buf 2 30)
(app + (app RawBuf.get buf 0)
(app + (app RawBuf.get buf 1)
(app RawBuf.get buf 2)))))))))))
+5
View File
@@ -0,0 +1,5 @@
(module raw_buf_reject_str
(fn cant_str
(type (fn-type (params (borrow (con RawBuf (con Str)))) (ret (con Str))))
(params buf)
(body (app RawBuf.get buf 0))))