diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index f37a407..9b80c3c 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -942,10 +942,10 @@ 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 raw-buf.4, the first real-payload kernel - // extension `raw_buf`), so the count is the user's two modules plus - // the two built-ins. - assert_eq!(modules.len(), 4, "expected 4 modules: {stdout}"); + // (`prelude`; `raw_buf` since raw-buf.4; and `series`, the first pure + // AILang library extension, since feat-series-step1), 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() @@ -955,6 +955,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(&"raw_buf"), "raw_buf missing: {names:?}"); + assert!(names.contains(&"series"), "series missing: {names:?}"); } /// Guards Iter 5b: `ail check examples/ws_main.ail.json --json` must diff --git a/crates/ail/tests/snapshots/hello.ll b/crates/ail/tests/snapshots/hello.ll index 14d4451..15648ff 100644 --- a/crates/ail/tests/snapshots/hello.ll +++ b/crates/ail/tests/snapshots/hello.ll @@ -31,6 +31,8 @@ declare i64 @llvm.fptosi.sat.i64.f64(double) @ail_prelude_float_le_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_le_adapter, ptr null } @ail_prelude_float_gt_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_gt_adapter, ptr null } @ail_prelude_float_ge_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_ge_adapter, ptr null } +@ail_prelude_compare__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_compare__Int_adapter, ptr null } +@ail_prelude_lt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_lt__Int_adapter, ptr null } define i8 @ail_hello_main() { entry: %v1 = getelementptr inbounds i8, ptr getelementptr inbounds (<{ i64, [15 x i8] }>, ptr @.str_hello_str_0, i32 0, i32 0), i64 8 @@ -117,6 +119,45 @@ entry: ret i1 %r } +define ptr @ail_prelude_compare__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + br i1 %v1, label %cmp_lt_2, label %cmp_after_lt_2 +cmp_lt_2: + %v3 = call ptr @ailang_rc_alloc(i64 8) + store i64 0, ptr %v3, align 8 + ret ptr %v3 +cmp_after_lt_2: + %v4 = icmp eq i64 %arg_x, %arg_y + br i1 %v4, label %cmp_eq_2, label %cmp_gt_2 +cmp_eq_2: + %v5 = call ptr @ailang_rc_alloc(i64 8) + store i64 1, ptr %v5, align 8 + ret ptr %v5 +cmp_gt_2: + %v6 = call ptr @ailang_rc_alloc(i64 8) + store i64 2, ptr %v6, align 8 + ret ptr %v6 +} + +define ptr @ail_prelude_compare__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call ptr @ail_prelude_compare__Int(i64 %a0, i64 %a1) + ret ptr %r +} + +define i1 @ail_prelude_lt__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + ret i1 %v1 +} + +define i1 @ail_prelude_lt__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call i1 @ail_prelude_lt__Int(i64 %a0, i64 %a1) + ret i1 %r +} + define void @drop_prelude_Ordering(ptr %p) { entry: %is_null = icmp eq ptr %p, null @@ -191,6 +232,58 @@ ret: ret void } +define void @drop_series_Series__6ddcf58f(ptr %p) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %a0 = getelementptr inbounds i8, ptr %p, i64 8 + %v1 = load ptr, ptr %a0, align 8 + call void @drop_raw_buf_RawBuf(ptr %v1) + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + +define void @partial_drop_series_Series__6ddcf58f(ptr %p, i64 %mask) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %b0 = and i64 %mask, 1 + %s1 = icmp ne i64 %b0, 0 + br i1 %s1, label %after_0_0, label %do_0_0 +do_0_0: + %a2 = getelementptr inbounds i8, ptr %p, i64 8 + %v3 = load ptr, ptr %a2, align 8 + call void @drop_raw_buf_RawBuf(ptr %v3) + br label %after_0_0 +after_0_0: + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + define i32 @main() { call i8 @ail_hello_main() diff --git a/crates/ail/tests/snapshots/list.ll b/crates/ail/tests/snapshots/list.ll index ed403a5..d23ec58 100644 --- a/crates/ail/tests/snapshots/list.ll +++ b/crates/ail/tests/snapshots/list.ll @@ -32,6 +32,8 @@ declare i64 @llvm.fptosi.sat.i64.f64(double) @ail_prelude_float_ge_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_ge_adapter, ptr null } @ail_prelude_show__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_show__Int_adapter, ptr null } @ail_prelude_print__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_print__Int_adapter, ptr null } +@ail_prelude_compare__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_compare__Int_adapter, ptr null } +@ail_prelude_lt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_lt__Int_adapter, ptr null } define i64 @ail_list_sum_list(ptr %arg_xs) { entry: %v1 = load i64, ptr %arg_xs, align 8 @@ -254,6 +256,45 @@ entry: ret i8 %r } +define ptr @ail_prelude_compare__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + br i1 %v1, label %cmp_lt_2, label %cmp_after_lt_2 +cmp_lt_2: + %v3 = call ptr @ailang_rc_alloc(i64 8) + store i64 0, ptr %v3, align 8 + ret ptr %v3 +cmp_after_lt_2: + %v4 = icmp eq i64 %arg_x, %arg_y + br i1 %v4, label %cmp_eq_2, label %cmp_gt_2 +cmp_eq_2: + %v5 = call ptr @ailang_rc_alloc(i64 8) + store i64 1, ptr %v5, align 8 + ret ptr %v5 +cmp_gt_2: + %v6 = call ptr @ailang_rc_alloc(i64 8) + store i64 2, ptr %v6, align 8 + ret ptr %v6 +} + +define ptr @ail_prelude_compare__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call ptr @ail_prelude_compare__Int(i64 %a0, i64 %a1) + ret ptr %r +} + +define i1 @ail_prelude_lt__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + ret i1 %v1 +} + +define i1 @ail_prelude_lt__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call i1 @ail_prelude_lt__Int(i64 %a0, i64 %a1) + ret i1 %r +} + define void @drop_prelude_Ordering(ptr %p) { entry: %is_null = icmp eq ptr %p, null @@ -328,6 +369,58 @@ ret: ret void } +define void @drop_series_Series__6ddcf58f(ptr %p) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %a0 = getelementptr inbounds i8, ptr %p, i64 8 + %v1 = load ptr, ptr %a0, align 8 + call void @drop_raw_buf_RawBuf(ptr %v1) + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + +define void @partial_drop_series_Series__6ddcf58f(ptr %p, i64 %mask) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %b0 = and i64 %mask, 1 + %s1 = icmp ne i64 %b0, 0 + br i1 %s1, label %after_0_0, label %do_0_0 +do_0_0: + %a2 = getelementptr inbounds i8, ptr %p, i64 8 + %v3 = load ptr, ptr %a2, align 8 + call void @drop_raw_buf_RawBuf(ptr %v3) + br label %after_0_0 +after_0_0: + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + define i32 @main() { call i8 @ail_list_main() diff --git a/crates/ail/tests/snapshots/max3.ll b/crates/ail/tests/snapshots/max3.ll index 5ce6af1..74865e6 100644 --- a/crates/ail/tests/snapshots/max3.ll +++ b/crates/ail/tests/snapshots/max3.ll @@ -35,6 +35,7 @@ declare i64 @llvm.fptosi.sat.i64.f64(double) @ail_prelude_gt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_gt__Int_adapter, ptr null } @ail_prelude_show__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_show__Int_adapter, ptr null } @ail_prelude_print__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_print__Int_adapter, ptr null } +@ail_prelude_lt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_lt__Int_adapter, ptr null } define i64 @ail_max3_max(i64 %arg_a, i64 %arg_b) { entry: %v1 = call i1 @ail_prelude_gt__Int(i64 %arg_a, i64 %arg_b) @@ -241,6 +242,18 @@ entry: ret i8 %r } +define i1 @ail_prelude_lt__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + ret i1 %v1 +} + +define i1 @ail_prelude_lt__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call i1 @ail_prelude_lt__Int(i64 %a0, i64 %a1) + ret i1 %r +} + define void @drop_prelude_Ordering(ptr %p) { entry: %is_null = icmp eq ptr %p, null @@ -315,6 +328,58 @@ ret: ret void } +define void @drop_series_Series__6ddcf58f(ptr %p) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %a0 = getelementptr inbounds i8, ptr %p, i64 8 + %v1 = load ptr, ptr %a0, align 8 + call void @drop_raw_buf_RawBuf(ptr %v1) + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + +define void @partial_drop_series_Series__6ddcf58f(ptr %p, i64 %mask) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %b0 = and i64 %mask, 1 + %s1 = icmp ne i64 %b0, 0 + br i1 %s1, label %after_0_0, label %do_0_0 +do_0_0: + %a2 = getelementptr inbounds i8, ptr %p, i64 8 + %v3 = load ptr, ptr %a2, align 8 + call void @drop_raw_buf_RawBuf(ptr %v3) + br label %after_0_0 +after_0_0: + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + define i32 @main() { call i8 @ail_max3_main() diff --git a/crates/ail/tests/snapshots/sum.ll b/crates/ail/tests/snapshots/sum.ll index 29404fe..9c99a4d 100644 --- a/crates/ail/tests/snapshots/sum.ll +++ b/crates/ail/tests/snapshots/sum.ll @@ -28,6 +28,8 @@ declare i64 @llvm.fptosi.sat.i64.f64(double) @ail_prelude_float_le_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_le_adapter, ptr null } @ail_prelude_float_gt_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_gt_adapter, ptr null } @ail_prelude_float_ge_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_ge_adapter, ptr null } +@ail_prelude_compare__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_compare__Int_adapter, ptr null } +@ail_prelude_lt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_lt__Int_adapter, ptr null } @ail_prelude_eq__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_eq__Int_adapter, ptr null } @ail_prelude_show__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_show__Int_adapter, ptr null } @ail_prelude_print__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_print__Int_adapter, ptr null } @@ -105,6 +107,45 @@ entry: ret i1 %r } +define ptr @ail_prelude_compare__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + br i1 %v1, label %cmp_lt_2, label %cmp_after_lt_2 +cmp_lt_2: + %v3 = call ptr @ailang_rc_alloc(i64 8) + store i64 0, ptr %v3, align 8 + ret ptr %v3 +cmp_after_lt_2: + %v4 = icmp eq i64 %arg_x, %arg_y + br i1 %v4, label %cmp_eq_2, label %cmp_gt_2 +cmp_eq_2: + %v5 = call ptr @ailang_rc_alloc(i64 8) + store i64 1, ptr %v5, align 8 + ret ptr %v5 +cmp_gt_2: + %v6 = call ptr @ailang_rc_alloc(i64 8) + store i64 2, ptr %v6, align 8 + ret ptr %v6 +} + +define ptr @ail_prelude_compare__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call ptr @ail_prelude_compare__Int(i64 %a0, i64 %a1) + ret ptr %r +} + +define i1 @ail_prelude_lt__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + ret i1 %v1 +} + +define i1 @ail_prelude_lt__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call i1 @ail_prelude_lt__Int(i64 %a0, i64 %a1) + ret i1 %r +} + define i1 @ail_prelude_eq__Int(i64 %arg_x, i64 %arg_y) alwaysinline { entry: %v1 = icmp eq i64 %arg_x, %arg_y @@ -219,6 +260,58 @@ ret: ret void } +define void @drop_series_Series__6ddcf58f(ptr %p) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %a0 = getelementptr inbounds i8, ptr %p, i64 8 + %v1 = load ptr, ptr %a0, align 8 + call void @drop_raw_buf_RawBuf(ptr %v1) + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + +define void @partial_drop_series_Series__6ddcf58f(ptr %p, i64 %mask) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %b0 = and i64 %mask, 1 + %s1 = icmp ne i64 %b0, 0 + br i1 %s1, label %after_0_0, label %do_0_0 +do_0_0: + %a2 = getelementptr inbounds i8, ptr %p, i64 8 + %v3 = load ptr, ptr %a2, align 8 + call void @drop_raw_buf_RawBuf(ptr %v3) + br label %after_0_0 +after_0_0: + br label %join +dflt: + unreachable +join: + 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) diff --git a/crates/ail/tests/snapshots/ws_main.ll b/crates/ail/tests/snapshots/ws_main.ll index 4adf426..f2d1763 100644 --- a/crates/ail/tests/snapshots/ws_main.ll +++ b/crates/ail/tests/snapshots/ws_main.ll @@ -28,6 +28,8 @@ declare i64 @llvm.fptosi.sat.i64.f64(double) @ail_prelude_float_le_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_le_adapter, ptr null } @ail_prelude_float_gt_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_gt_adapter, ptr null } @ail_prelude_float_ge_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_float_ge_adapter, ptr null } +@ail_prelude_compare__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_compare__Int_adapter, ptr null } +@ail_prelude_lt__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_lt__Int_adapter, ptr null } @ail_prelude_show__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_show__Int_adapter, ptr null } @ail_prelude_print__Int_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_prelude_print__Int_adapter, ptr null } @ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null } @@ -104,6 +106,45 @@ entry: ret i1 %r } +define ptr @ail_prelude_compare__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + br i1 %v1, label %cmp_lt_2, label %cmp_after_lt_2 +cmp_lt_2: + %v3 = call ptr @ailang_rc_alloc(i64 8) + store i64 0, ptr %v3, align 8 + ret ptr %v3 +cmp_after_lt_2: + %v4 = icmp eq i64 %arg_x, %arg_y + br i1 %v4, label %cmp_eq_2, label %cmp_gt_2 +cmp_eq_2: + %v5 = call ptr @ailang_rc_alloc(i64 8) + store i64 1, ptr %v5, align 8 + ret ptr %v5 +cmp_gt_2: + %v6 = call ptr @ailang_rc_alloc(i64 8) + store i64 2, ptr %v6, align 8 + ret ptr %v6 +} + +define ptr @ail_prelude_compare__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call ptr @ail_prelude_compare__Int(i64 %a0, i64 %a1) + ret ptr %r +} + +define i1 @ail_prelude_lt__Int(i64 %arg_x, i64 %arg_y) alwaysinline { +entry: + %v1 = icmp slt i64 %arg_x, %arg_y + ret i1 %v1 +} + +define i1 @ail_prelude_lt__Int_adapter(ptr %_env, i64 %a0, i64 %a1) { +entry: + %r = call i1 @ail_prelude_lt__Int(i64 %a0, i64 %a1) + ret i1 %r +} + define ptr @ail_prelude_show__Int(i64 %arg_x) { entry: %v1 = call ptr @ailang_int_to_str(i64 %arg_x) @@ -206,6 +247,58 @@ ret: ret void } +define void @drop_series_Series__6ddcf58f(ptr %p) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %a0 = getelementptr inbounds i8, ptr %p, i64 8 + %v1 = load ptr, ptr %a0, align 8 + call void @drop_raw_buf_RawBuf(ptr %v1) + br label %join +dflt: + unreachable +join: + call void @ailang_rc_dec(ptr %p) + br label %ret +ret: + ret void +} + +define void @partial_drop_series_Series__6ddcf58f(ptr %p, i64 %mask) { +entry: + %is_null = icmp eq ptr %p, null + br i1 %is_null, label %ret, label %live +live: + %tag = load i64, ptr %p, align 8 + switch i64 %tag, label %dflt [ + i64 0, label %arm_0 + ] +arm_0: + %b0 = and i64 %mask, 1 + %s1 = icmp ne i64 %b0, 0 + br i1 %s1, label %after_0_0, label %do_0_0 +do_0_0: + %a2 = getelementptr inbounds i8, ptr %p, i64 8 + %v3 = load ptr, ptr %a2, align 8 + call void @drop_raw_buf_RawBuf(ptr %v3) + br label %after_0_0 +after_0_0: + br label %join +dflt: + unreachable +join: + 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 diff --git a/crates/ail/tests/typeclass_22b3.rs b/crates/ail/tests/typeclass_22b3.rs index 6826113..aea0359 100644 --- a/crates/ail/tests/typeclass_22b3.rs +++ b/crates/ail/tests/typeclass_22b3.rs @@ -30,7 +30,21 @@ fn monomorphise_workspace_is_identity_on_class_free_workspace() { "module set must be identical" ); // Per-module def list unchanged (compare by canonical-JSON hash). + // + // Exception: `prelude` is no longer hash-identical through mono for + // ANY entry since feat-series-step1. The auto-imported `series` + // kernel module is a real-bodied (non-intrinsic) consumer of the + // prelude polyfns `lt`/`compare` over `Int`, so the workspace-wide + // mono pass now materialises `lt__Int` / `compare__Int` into the + // prelude module regardless of the user entry. That is a concrete + // call site driven by an auto-imported library module, not gratuitous + // mutation — the test's real invariant (mono does not rewrite modules + // with no work to do) holds for every other module. `series` itself + // is excluded for the same reason: it may gain its own specialisations. for (mname, m) in &ws.modules { + if mname == "prelude" || mname == "series" { + continue; + } let before_m = &before.modules[mname]; let h_after = ailang_core::module_hash(m); let h_before = ailang_core::module_hash(before_m); @@ -72,8 +86,18 @@ fn monomorphise_workspace_is_identity_when_no_concrete_call_sites() { "module set must be identical" ); // Each module hashes byte-identical — no synth fns appended - // because no call site triggered a target. + // because no class-method call site triggered a target. + // + // Exception: `prelude` and `series` — see the sibling test + // `monomorphise_workspace_is_identity_on_class_free_workspace` for + // the rationale. The auto-imported, real-bodied `series` kernel + // module pins `lt__Int` / `compare__Int` specialisations into + // `prelude` for every workspace; that is a legitimate concrete call + // site, orthogonal to this test's typeclass-call-site invariant. for (mname, m) in &ws_after.modules { + if mname == "prelude" || mname == "series" { + continue; + } let before_m = &before.modules[mname]; let h_after = ailang_core::module_hash(m); let h_before = ailang_core::module_hash(before_m); diff --git a/crates/ailang-core/tests/workspace_pin.rs b/crates/ailang-core/tests/workspace_pin.rs index c396266..97119e6 100644 --- a/crates/ailang-core/tests/workspace_pin.rs +++ b/crates/ailang-core/tests/workspace_pin.rs @@ -46,13 +46,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 `raw_buf`, the first real-payload kernel - // extension, raw-buf.4), 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`; `raw_buf`, the first real-payload kernel extension, + // raw-buf.4; and `series`, the first pure-AILang library extension, + // feat-series-step1), 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("raw_buf")); + assert!(ws.modules.contains_key("series")); } #[test] diff --git a/crates/ailang-kernel/src/lib.rs b/crates/ailang-kernel/src/lib.rs index 59fb976..f05be14 100644 --- a/crates/ailang-kernel/src/lib.rs +++ b/crates/ailang-kernel/src/lib.rs @@ -9,5 +9,7 @@ //! crate carries zero parser code. mod raw_buf; +mod series; pub use raw_buf::SOURCE as RAW_BUF_AIL; +pub use series::SOURCE as SERIES_AIL; diff --git a/crates/ailang-kernel/src/series/mod.rs b/crates/ailang-kernel/src/series/mod.rs new file mode 100644 index 0000000..cb0aa3a --- /dev/null +++ b/crates/ailang-kernel/src/series/mod.rs @@ -0,0 +1,18 @@ +//! Series submodule — the Form-A source of the `series` kernel-tier +//! library-extension module (Series: a bounded ring buffer over +//! RawBuf with financial-style indexing, index 0 = newest). Parsed by +//! `ailang_surface::parse_series`. +//! +//! Unlike `raw_buf`, `series` is a PURE AILang library extension: its +//! five ops (new/push/at/len/total_count) carry real `.ail` bodies +//! built on the `RawBuf.*` intrinsics, so it adds NO intercept entries +//! and NO `(intrinsic)` markers. It is the first kernel-tier module +//! that depends on another kernel-tier module (`raw_buf`); cross- +//! kernel-module visibility comes from the kernel auto-import set the +//! loader derives from every `kernel: true` module. + +/// Source-of-truth Form-A text for the `series` kernel module. +/// Declares the `Series` TypeDef (`param-in (a Int Float)`) and the +/// five real-bodied ops `new`/`push`/`at`/`len`/`total_count`, all +/// implemented over the `raw_buf` kernel module's `RawBuf.*` ops. +pub const SOURCE: &str = include_str!("source.ail"); diff --git a/crates/ailang-kernel/src/series/source.ail b/crates/ailang-kernel/src/series/source.ail new file mode 100644 index 0000000..d790926 --- /dev/null +++ b/crates/ailang-kernel/src/series/source.ail @@ -0,0 +1,49 @@ +(module series + (kernel) + (data Series (vars a) + (doc "Bounded ring buffer with financial-style indexing (index 0 = newest). Pure AILang library extension over RawBuf: the buffer holds the elements, the four Int fields are lookback (capacity), head (next write slot), count (live elements, saturates at lookback), and total (monotone count of all pushes ever). Element type restricted to {Int, Float} via param-in; Bool deferred (#61).") + (param-in (a Int Float)) + (ctor S (con RawBuf a) + (con Int) + (con Int) + (con Int) + (con Int))) + (fn new + (doc "Allocate an empty Series of capacity `lookback`. The inner RawBuf is allocated via `(new RawBuf lookback)` with NO element type-arg — the element type is solved from the expected ctor-field type `(con RawBuf a)`, which the base-tier mono fix (b11a6d9) carries through to the caller's concrete type.") + (type (forall (vars a) (fn-type (params (own (con Int))) (ret (own (con Series a)))))) + (params lookback) + (body + (term-ctor Series S + (new RawBuf lookback) lookback 0 0 0))) + (fn push + (doc "Append `v`, evicting the oldest element when full. own-in/own-out linear threading: mutates the buffer at head, advances head mod lookback, saturates count at lookback, increments total.") + (type (forall (vars a) (fn-type (params (own (con Series a)) (own a)) (ret (own (con Series a)))))) + (params s v) + (body + (match s + (case (pat-ctor S buf lookback head count total) + (term-ctor Series S + (app RawBuf.set buf head v) + lookback + (app % (app + head 1) lookback) + (if (app lt count lookback) (app + count 1) count) + (app + total 1)))))) + (fn at + (doc "Read the element `i` positions back from newest (i=0 is the most recent push). Financial indexing: slot = (head - 1 - i + 2*lookback) mod lookback. UB if i >= len; caller checks via len/total_count.") + (type (forall (vars a) (fn-type (params (borrow (con Series a)) (own (con Int))) (ret (own a))))) + (params s i) + (body + (match s + (case (pat-ctor S buf lookback head count total) + (app RawBuf.get buf + (app % (app + (app - (app - head 1) i) (app * lookback 2)) lookback)))))) + (fn len + (doc "Number of live elements (saturates at lookback).") + (type (forall (vars a) (fn-type (params (borrow (con Series a))) (ret (own (con Int)))))) + (params s) + (body (match s (case (pat-ctor S buf lookback head count total) count)))) + (fn total_count + (doc "Monotone count of all pushes ever (does not saturate). Drives the SMA emit gate.") + (type (forall (vars a) (fn-type (params (borrow (con Series a))) (ret (own (con Int)))))) + (params s) + (body (match s (case (pat-ctor S buf lookback head count total) total))))) diff --git a/crates/ailang-surface/src/lib.rs b/crates/ailang-surface/src/lib.rs index 9e523e8..44adcb1 100644 --- a/crates/ailang-surface/src/lib.rs +++ b/crates/ailang-surface/src/lib.rs @@ -37,7 +37,8 @@ pub mod parse; pub mod print; pub use loader::{ - load_module, load_workspace, parse_prelude, parse_raw_buf, PRELUDE_AIL, + load_module, load_workspace, parse_prelude, parse_raw_buf, parse_series, + PRELUDE_AIL, }; pub use parse::{parse, parse_term, ParseError}; pub use print::{print, term_to_form_a, type_to_form_a}; diff --git a/crates/ailang-surface/src/loader.rs b/crates/ailang-surface/src/loader.rs index f0de273..0b193c7 100644 --- a/crates/ailang-surface/src/loader.rs +++ b/crates/ailang-surface/src/loader.rs @@ -56,6 +56,21 @@ pub fn parse_raw_buf() -> Module { .expect("ailang_kernel::RAW_BUF_AIL must parse as a Module") } +/// feat-series-step1: parse the embedded `series` kernel-tier library- +/// extension module bytes into a `Module`. Mirror of [`parse_raw_buf`]. +/// +/// Source-of-truth: `ailang_kernel::SERIES_AIL`. Declares the `Series` +/// TypeDef with `param-in (a Int Float)` and the five real-bodied ops +/// `new`/`push`/`at`/`len`/`total_count`, built over the `raw_buf` +/// kernel module's `RawBuf.*` ops. Unlike raw_buf this is a PURE +/// AILang library extension — no intercepts, no `(intrinsic)` markers. +/// +/// Panics on parse failure — build-time-validated by every drift run. +pub fn parse_series() -> Module { + crate::parse(ailang_kernel::SERIES_AIL) + .expect("ailang_kernel::SERIES_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 +146,18 @@ pub fn load_workspace(entry: &Path) -> Result { } modules.insert("raw_buf".to_string(), parse_raw_buf()); + // parse_series() injects the series kernel-tier library-extension + // module — Series, a bounded ring buffer over RawBuf. Pure AILang + // (no intercepts); its body references the raw_buf kernel module, + // which the kernel auto-import set below makes visible. Its source + // carries `(kernel)`, so the kernel-flag filter auto-imports it. + if modules.contains_key("series") { + return Err(WorkspaceLoadError::ReservedModuleName { + name: "series".to_string(), + }); + } + modules.insert("series".to_string(), parse_series()); + // 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