iter 23.3.1: runtime ail_str_compare + IR header declaration

This commit is contained in:
2026-05-10 23:00:22 +02:00
parent 2c974e3c7e
commit 94893bfb9d
7 changed files with 62 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@ declare i32 @puts(ptr)
declare ptr @GC_malloc(i64) declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr) declare i32 @strcmp(ptr, ptr)
declare zeroext i1 @ail_str_eq(ptr, ptr) declare zeroext i1 @ail_str_eq(ptr, ptr)
declare i32 @ail_str_compare(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double) declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_hello_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_hello_main_adapter, ptr null } @ail_hello_main_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_hello_main_adapter, ptr null }
+1
View File
@@ -9,6 +9,7 @@ declare i32 @puts(ptr)
declare ptr @GC_malloc(i64) declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr) declare i32 @strcmp(ptr, ptr)
declare zeroext i1 @ail_str_eq(ptr, ptr) declare zeroext i1 @ail_str_eq(ptr, ptr)
declare i32 @ail_str_compare(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double) declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_list_sum_list_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_sum_list_adapter, ptr null } @ail_list_sum_list_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_list_sum_list_adapter, ptr null }
+1
View File
@@ -9,6 +9,7 @@ declare i32 @puts(ptr)
declare ptr @GC_malloc(i64) declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr) declare i32 @strcmp(ptr, ptr)
declare zeroext i1 @ail_str_eq(ptr, ptr) declare zeroext i1 @ail_str_eq(ptr, ptr)
declare i32 @ail_str_compare(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double) declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_max3_max_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max_adapter, ptr null } @ail_max3_max_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_max3_max_adapter, ptr null }
+1
View File
@@ -9,6 +9,7 @@ declare i32 @puts(ptr)
declare ptr @GC_malloc(i64) declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr) declare i32 @strcmp(ptr, ptr)
declare zeroext i1 @ail_str_eq(ptr, ptr) declare zeroext i1 @ail_str_eq(ptr, ptr)
declare i32 @ail_str_compare(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double) declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_sum_sum_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_sum_adapter, ptr null } @ail_sum_sum_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_sum_sum_adapter, ptr null }
+1
View File
@@ -9,6 +9,7 @@ declare i32 @puts(ptr)
declare ptr @GC_malloc(i64) declare ptr @GC_malloc(i64)
declare i32 @strcmp(ptr, ptr) declare i32 @strcmp(ptr, ptr)
declare zeroext i1 @ail_str_eq(ptr, ptr) declare zeroext i1 @ail_str_eq(ptr, ptr)
declare i32 @ail_str_compare(ptr, ptr)
declare i64 @llvm.fptosi.sat.i64.f64(double) declare i64 @llvm.fptosi.sat.i64.f64(double)
@ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null } @ail_ws_lib_add_clos = private unnamed_addr constant { ptr, ptr } { ptr @ail_ws_lib_add_adapter, ptr null }
+43
View File
@@ -495,6 +495,15 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
// by `ail build` (see `crates/ail/src/main.rs::build_to`). The // by `ail build` (see `crates/ail/src/main.rs::build_to`). The
// `zeroext i1` return matches clang's lowering of C `_Bool`. // `zeroext i1` return matches clang's lowering of C `_Bool`.
out.push_str("declare zeroext i1 @ail_str_eq(ptr, ptr)\n"); out.push_str("declare zeroext i1 @ail_str_eq(ptr, ptr)\n");
// Iter 23.3: `runtime/str.c::ail_str_compare` backs the prelude's
// `compare__Str` mono symbol (see `try_emit_primitive_instance_body`
// `compare__Str` arm below). Declared unconditionally on the same
// rationale as `@ail_str_eq` — the .o is supplied by the
// unconditionally-linked `runtime/str.c`, and clang -O2 dead-
// strips the symbol when no caller exists. Returns i32 normalised
// to {-1, 0, +1} so the branch ladder in the intercept can compare
// against constant 0 directly.
out.push_str("declare i32 @ail_str_compare(ptr, ptr)\n");
// Floats iter 4.4: saturating fp-to-int intrinsic for // Floats iter 4.4: saturating fp-to-int intrinsic for
// float_to_int_truncate. NaN → 0, +Inf → i64::MAX, -Inf → // float_to_int_truncate. NaN → 0, +Inf → i64::MAX, -Inf →
// i64::MIN, finite-out-of-range saturates, finite-in-range // i64::MIN, finite-out-of-range saturates, finite-in-range
@@ -3653,4 +3662,38 @@ mod tests {
"eq__Str body must call @ail_str_eq; ir was:\n{ir}" "eq__Str body must call @ail_str_eq; ir was:\n{ir}"
); );
} }
/// Iter 23.3: `runtime/str.c::ail_str_compare` backs the prelude's
/// `compare__Str` mono symbol (see `try_emit_primitive_instance_body`).
/// The declaration is unconditional in the IR header alongside
/// `@ail_str_eq`; this test pins the header line so a regression
/// that drops it would fail at link time only on programs that
/// actually call `compare` on a Str.
#[test]
fn ir_header_declares_ail_str_compare() {
let m = Module {
schema: SCHEMA.into(),
name: "t".into(),
imports: vec![],
defs: vec![Def::Fn(FnDef {
name: "main".into(),
ty: Type::Fn {
params: vec![],
ret: Box::new(Type::unit()),
effects: vec![],
param_modes: vec![],
ret_mode: ParamMode::Implicit,
},
params: vec![],
body: Term::Lit { lit: Literal::Unit },
suppress: vec![],
doc: None,
})],
};
let ir = emit_ir(&m).unwrap();
assert!(
ir.contains("declare i32 @ail_str_compare(ptr, ptr)"),
"header missing @ail_str_compare declaration; ir was:\n{ir}"
);
}
} }
+14
View File
@@ -24,3 +24,17 @@
bool ail_str_eq(const char *a, const char *b) { bool ail_str_eq(const char *a, const char *b) {
return strcmp(a, b) == 0; return strcmp(a, b) == 0;
} }
/* Three-way comparison of NUL-terminated strings. Normalises libc
* strcmp's signed-int output to exactly -1 / 0 / +1 so the codegen
* caller can branch on `icmp slt i32 result, 0` and
* `icmp eq i32 result, 0` against constants directly without
* worrying about strcmp implementations that return values outside
* {-1, 0, +1}. Heap-Str ABI milestone will replace the body with
* length-prefixed comparison without changing the signature. */
int ail_str_compare(const char *a, const char *b) {
int r = strcmp(a, b);
if (r < 0) return -1;
if (r > 0) return 1;
return 0;
}