iter hs.2: static-Str layout retrofit — drop sentinel rc-header slot
Per the amended heap-Str ABI spec (2a72a4a), static-Str globals lose
the leading UINT64_MAX sentinel rc-header field that hs.1 introduced.
Layout flips from <{ i64, i64, [N x i8] }> <{ i64 -1, i64 N, ... }>
to <{ i64, [N x i8] }> <{ i64 N, ... }>; the IR-Str pointer now lands
on the len-field at struct-index 0 (was 1) via constexpr-GEP. The four
+8 consumer-side GEPs (@puts / @ail_str_eq / @ail_str_compare / @strcmp)
do not change — the bytes still sit 8 bytes past the len-field. Static-
Str pointers are kept out of ailang_rc_dec at the codegen level via
move-tracking (iter 18d.3) and non-escape lowering (iter 18b), so no
header slot is needed at the global; this is a codegen-level invariant,
not a runtime guard.
Change surface: three format strings + two doc-comments in lib.rs,
rename + assertion update of two IR-shape tests, snapshot regen of
hello.ll. No runtime changes, no checker changes, no new fixtures.
Static-Str globals are now 8 bytes smaller per literal.
cargo test --workspace green; cross_lang.py / compile_check.py /
check.py all green. 3 tasks, 0 re-loops.
This commit is contained in:
@@ -459,17 +459,22 @@ fn lower_workspace_inner(ws: &Workspace, alloc: AllocStrategy) -> Result<String>
|
||||
emitted_global = true;
|
||||
}
|
||||
}
|
||||
// Iter hs.1: packed-struct globals for language `Str` literals.
|
||||
// The `i64 -1` slot is the `UINT64_MAX` sentinel rc-header; the
|
||||
// next `i64` is the byte length (excluding the trailing NUL); the
|
||||
// Iter hs.2: packed-struct globals for language `Str` literals.
|
||||
// First `i64` is the byte length (excluding the trailing NUL); the
|
||||
// `[N+1 x i8]` carries the bytes followed by the terminating NUL.
|
||||
// The IR-`Str` pointer that flows through the rest of codegen lands
|
||||
// on the `len`-field via constexpr-GEP at the `Literal::Str` arms
|
||||
// (see emit_const_def / lower_term). Static-Str pointers are kept
|
||||
// out of `ailang_rc_dec` by codegen-level elision (move-tracking
|
||||
// from iter 18d.3 + non-escape lowering from iter 18b), so no
|
||||
// sentinel rc-header slot is needed at the global.
|
||||
for entries in all_str_literals.values() {
|
||||
for (name, content) in entries {
|
||||
let escaped = ll_string_literal(content);
|
||||
let total = c_byte_len(content); // bytes + NUL
|
||||
let bytes_len = total - 1; // bytes only
|
||||
out.push_str(&format!(
|
||||
"@{name} = private unnamed_addr constant <{{ i64, i64, [{total} x i8] }}> <{{ i64 -1, i64 {bytes_len}, [{total} x i8] c\"{escaped}\" }}>, align 8\n",
|
||||
"@{name} = private unnamed_addr constant <{{ i64, [{total} x i8] }}> <{{ i64 {bytes_len}, [{total} x i8] c\"{escaped}\" }}>, align 8\n",
|
||||
));
|
||||
emitted_global = true;
|
||||
}
|
||||
@@ -558,9 +563,9 @@ struct Emitter<'a> {
|
||||
body: String,
|
||||
/// String constants: content -> (global name (without `@`), llvm type length incl. \0)
|
||||
strings: BTreeMap<String, (String, usize)>,
|
||||
/// Iter hs.1: language `Str` literals interned as packed-struct
|
||||
/// globals (`<{ i64, i64, [N+1 x i8] }>`) carrying a `UINT64_MAX`
|
||||
/// sentinel rc-header and an explicit `len` field. Parallel to
|
||||
/// Iter hs.1 (amended hs.2): language `Str` literals interned as
|
||||
/// packed-struct globals (`<{ i64, [N+1 x i8] }>`) carrying an
|
||||
/// explicit `len` field and the bytes + trailing NUL. Parallel to
|
||||
/// `strings` (which still serves runtime-internal format strings
|
||||
/// like `%lld\n` / `true\n` in the raw `[N x i8]` shape). Same
|
||||
/// key shape; the two tables coexist.
|
||||
@@ -922,16 +927,18 @@ impl<'a> Emitter<'a> {
|
||||
),
|
||||
Literal::Unit => ("i8".to_string(), "0".to_string()),
|
||||
Literal::Str { value } => {
|
||||
// Iter hs.1: emit a packed-struct global and return a
|
||||
// constexpr-GEP pointer landing on the `len`-field, so
|
||||
// every IR-Str pointer in this codegen pipeline has
|
||||
// the same shape (header at -8, len at 0, bytes at +8).
|
||||
// Iter hs.2: emit a packed-struct global and return a
|
||||
// constexpr-GEP pointer landing on the `len`-field (now
|
||||
// the first field of the packed struct, since the
|
||||
// hs.1-era sentinel rc-header slot was removed). Every
|
||||
// IR-Str pointer in this codegen pipeline has the
|
||||
// shape len at offset 0, bytes at offset 8.
|
||||
let g = self.intern_str_literal("str", value);
|
||||
let total = c_byte_len(value); // bytes + NUL
|
||||
(
|
||||
"ptr".to_string(),
|
||||
format!(
|
||||
"getelementptr inbounds (<{{ i64, i64, [{total} x i8] }}>, ptr @{g}, i32 0, i32 1)",
|
||||
"getelementptr inbounds (<{{ i64, [{total} x i8] }}>, ptr @{g}, i32 0, i32 0)",
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1244,15 +1251,17 @@ impl<'a> Emitter<'a> {
|
||||
"i1".into(),
|
||||
),
|
||||
Literal::Str { value } => {
|
||||
// Iter hs.1: language `Str` literals materialise as
|
||||
// Iter hs.2: language `Str` literals materialise as
|
||||
// a constexpr-GEP into the packed-struct global,
|
||||
// landing on the `len`-field so the IR-Str
|
||||
// pointer carries header at -8 and bytes at +8.
|
||||
// landing on the `len`-field (now the first field,
|
||||
// since the hs.1-era sentinel rc-header slot was
|
||||
// removed). IR-Str pointer carries len at 0, bytes
|
||||
// at +8.
|
||||
let g = self.intern_str_literal("str", value);
|
||||
let total = c_byte_len(value); // bytes + NUL
|
||||
(
|
||||
format!(
|
||||
"getelementptr inbounds (<{{ i64, i64, [{total} x i8] }}>, ptr @{g}, i32 0, i32 1)",
|
||||
"getelementptr inbounds (<{{ i64, [{total} x i8] }}>, ptr @{g}, i32 0, i32 0)",
|
||||
),
|
||||
"ptr".into(),
|
||||
)
|
||||
@@ -2593,10 +2602,10 @@ impl<'a> Emitter<'a> {
|
||||
name
|
||||
}
|
||||
|
||||
/// Iter hs.1: parallel to `intern_string`, but for language `Str`
|
||||
/// literals emitted as packed-struct globals (sentinel + len +
|
||||
/// bytes + NUL). Shares the same monotonic `str_counter` so the
|
||||
/// produced global names remain alphabetically orderable
|
||||
/// Iter hs.1 (amended hs.2): parallel to `intern_string`, but for
|
||||
/// language `Str` literals emitted as packed-struct globals
|
||||
/// (len + bytes + NUL). Shares the same monotonic `str_counter`
|
||||
/// so the produced global names remain alphabetically orderable
|
||||
/// alongside format-string globals.
|
||||
fn intern_str_literal(&mut self, hint: &str, content: &str) -> String {
|
||||
if let Some((name, _)) = self.str_literals.get(content) {
|
||||
@@ -3807,13 +3816,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter hs.1: language `Str` literals emit as packed-struct
|
||||
/// globals carrying a `UINT64_MAX` sentinel rc-header, an
|
||||
/// explicit `len` field, and the bytes + trailing NUL. This
|
||||
/// test pins the layout shape against a tiny single-Literal::Str
|
||||
/// fixture.
|
||||
/// Iter hs.2: language `Str` literals emit as packed-struct globals
|
||||
/// carrying an explicit `len` field followed by the bytes + trailing
|
||||
/// NUL. (The hs.1-era sentinel rc-header slot was removed per the
|
||||
/// amended spec; static-Str pointers are kept out of `ailang_rc_dec`
|
||||
/// at the codegen level via move-tracking and non-escape lowering,
|
||||
/// so no header slot is needed.) This test pins the layout shape
|
||||
/// against a tiny single-`Literal::Str` fixture.
|
||||
#[test]
|
||||
fn static_str_global_uses_packed_struct_with_sentinel_and_len() {
|
||||
fn static_str_global_uses_packed_struct_with_len() {
|
||||
let m = Module {
|
||||
schema: SCHEMA.into(),
|
||||
name: "t".into(),
|
||||
@@ -3843,14 +3854,15 @@ mod tests {
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains(r#"<{ i64, i64, [6 x i8] }> <{ i64 -1, i64 5, [6 x i8] c"hello\00" }>"#),
|
||||
"expected packed-struct global with sentinel + len + bytes + NUL; ir was:\n{ir}"
|
||||
ir.contains(r#"<{ i64, [6 x i8] }> <{ i64 5, [6 x i8] c"hello\00" }>"#),
|
||||
"expected packed-struct global with len + bytes + NUL; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Iter hs.1: a `Literal::Str` at a callsite materialises a
|
||||
/// constexpr `getelementptr` landing on the `len`-field of the
|
||||
/// packed-struct global, not the bare global name. This pins the
|
||||
/// Iter hs.2: a `Literal::Str` at a callsite materialises a constexpr
|
||||
/// `getelementptr` landing on the `len`-field of the packed-struct
|
||||
/// global (now the *first* field, since the hs.1-era sentinel
|
||||
/// rc-header slot was removed per the amended spec). This pins the
|
||||
/// IR-Str-pointer convention used uniformly by all consumers.
|
||||
#[test]
|
||||
fn static_str_callsite_pointer_is_payload_via_constexpr_gep() {
|
||||
@@ -3879,7 +3891,7 @@ mod tests {
|
||||
};
|
||||
let ir = emit_ir(&m).unwrap();
|
||||
assert!(
|
||||
ir.contains(r#"getelementptr inbounds (<{ i64, i64, [3 x i8] }>, ptr @.str_t_str_0, i32 0, i32 1)"#),
|
||||
ir.contains(r#"getelementptr inbounds (<{ i64, [3 x i8] }>, ptr @.str_t_str_0, i32 0, i32 0)"#),
|
||||
"expected constexpr-GEP-to-len-field at callsite; ir was:\n{ir}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user