lang: introduce cstring — the C-boundary string (Odin model)

cstring is ONE pointer to a null-terminated u8 buffer, C's char*: thin
(8 bytes, no length; cstring_len walks to the terminator), crossing
#foreign boundaries verbatim in both directions, with ?cstring as the
nullable case lowering to the same bare pointer (null = absent).

Conversion discipline mirrors Odin: a string LITERAL coerces implicitly
(its bytes are terminated constants); any other string is rejected with
a diagnostic naming to_cstring (it may be an unterminated view); and
cstring never coerces to string implicitly — from_cstring(c) is the
explicit zero-copy view, pricing the strlen.

Plumbing: TypeId/TypeInfo builtin slot 18 (first_user 19), name
classifiers, size/align/name tables, LLVM ptr lowering, the ?T pointer
niche, the xx pointer ladder, the literal-gated coercion plan
(isConstString + data_ptr), and the reserved-spelling set. std gains
cstring_len/from_cstring/to_cstring (fmt.sx, re-exported); the old
cstring(size) allocator helper is renamed alloc_string everywhere;
getenv migrates to (name: cstring) -> ?cstring as the canonical user
and env() drops its manual strlen/memcpy.

Pinned: examples/1222 (FFI both directions, literal coercion,
?cstring null paths, round trip) and examples/1173 (both coercion
diagnostics); FAIL pre-feature. The alloc_string rename + getenv
signature shift the .ir snapshots — regenerated. zig build test
426/426; run_examples 604/604.

Spec: reserved spelling + cstring section + C-interop rows.
This commit is contained in:
agra
2026-06-12 14:50:53 +03:00
parent d88bdd7242
commit 1d17b0abcf
58 changed files with 26437 additions and 25257 deletions

View File

@@ -0,0 +1,13 @@
// cstring's coercion discipline (Odin-style): only a string LITERAL
// converts implicitly — an arbitrary string may be an unterminated view
// (use to_cstring); and cstring never converts to string implicitly —
// the length is an O(n) strlen the code must ask for (from_cstring).
#import "modules/std.sx";
main :: () -> i32 {
s := concat("a", "b");
c : cstring = s; // error: non-literal string -> cstring
t : string = c; // error: cstring -> string
print("{}{}\n", t, cstring_len(c));
return 0;
}

View File

@@ -0,0 +1,38 @@
// The `cstring` type: ONE pointer to a null-terminated u8 buffer — C's
// `char *`. Crosses #foreign boundaries verbatim in both directions;
// `?cstring` is the nullable case (null pointer = absent); string
// LITERALS coerce implicitly (terminated constants); arbitrary strings
// materialize via to_cstring; from_cstring is the zero-copy view back.
#import "modules/std.sx";
libc :: #library "c";
strerror_c :: (code: i32) -> cstring #foreign libc "strerror";
getenv_c :: (name: cstring) -> ?cstring #foreign libc "getenv";
dlerror_c :: () -> ?cstring #foreign libc "dlerror";
main :: () -> i32 {
// literal -> cstring param; cstring return -> view
e := strerror_c(2);
v := from_cstring(e);
if v.len < 5 { print("BUG: strerror short\n"); return 1; }
print("strerror ok\n");
// ?cstring: present, absent, and null-returning C call
p := getenv_c("PATH");
if p == null { print("BUG: PATH null\n"); return 2; }
if cstring_len(p!) < 1 { print("BUG: PATH empty\n"); return 3; }
q := getenv_c("NO_SUCH_VAR_ZZZ");
if q != null { print("BUG: missing not null\n"); return 4; }
d := dlerror_c();
if d != null { print("BUG: dlerror non-null\n"); return 5; }
print("?cstring ok\n");
// round trip: built string -> owned cstring -> view -> equality
s := concat("hi-", "there");
c := to_cstring(s);
r := from_cstring(c);
if r != "hi-there" { print("BUG: round trip\n"); return 6; }
if cstring_len(c) != 8 { print("BUG: len\n"); return 7; }
print("round trip ok\n");
return 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,11 @@
error: only a string LITERAL coerces to 'cstring' implicitly; an arbitrary string may be an unterminated view — materialize it with to_cstring(s)
--> examples/1173-diagnostics-cstring-coercions.sx:9:5
|
9 | c : cstring = s; // error: non-literal string -> cstring
| ^^^^^^^^^^^^^^^^
error: 'cstring' does not coerce to 'string' implicitly (the length is implicit); convert with from_cstring(c)
--> examples/1173-diagnostics-cstring-coercions.sx:10:5
|
10 | t : string = c; // error: cstring -> string
| ^^^^^^^^^^^^^^^

View File

@@ -0,0 +1 @@

View File

@@ -16,7 +16,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
declare ptr @cstring(ptr, i64) #0
declare ptr @alloc_string(ptr, i64) #0
; Function Attrs: nounwind
declare ptr @int_to_string(ptr, i64) #0
@@ -45,6 +45,15 @@ declare ptr @concat(ptr, ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @substr(ptr, ptr, i64, i64) #0
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -266,9 +275,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
strerror ok
?cstring ok
round trip ok

View File

@@ -48,7 +48,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -99,7 +99,7 @@ if.merge.15: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -203,7 +203,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -234,7 +234,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -249,6 +249,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -758,9 +767,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -64,7 +64,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -115,7 +115,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -219,7 +219,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -250,7 +250,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -265,6 +265,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -774,9 +783,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

File diff suppressed because one or more lines are too long

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.15: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

File diff suppressed because one or more lines are too long

View File

@@ -66,7 +66,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -117,7 +117,7 @@ if.merge.15: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -221,7 +221,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -252,7 +252,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -267,6 +267,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -776,9 +785,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -42,7 +42,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -93,7 +93,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -197,7 +197,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -228,7 +228,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -243,6 +243,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -752,9 +761,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -40,7 +40,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
define internal { ptr, i64 } @cstring(ptr %0, i64 %1) #0 {
define internal { ptr, i64 } @alloc_string(ptr %0, i64 %1) #0 {
entry:
%alloca = alloca i64, align 8
store i64 %1, ptr %alloca, align 8
@@ -91,7 +91,7 @@ if.merge.17: ; preds = %entry
%loadN = load i64, ptr %alloca, align 8
%icmpN = icmp slt i64 %loadN, 0
store i1 %icmpN, ptr %allocaN, align 1
%call = call { ptr, i64 } @cstring(ptr %0, i64 20)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 20)
store { ptr, i64 } %call, ptr %allocaN, align 8
store i64 19, ptr %allocaN, align 8
%loadN = load i64, ptr %alloca, align 8
@@ -195,7 +195,7 @@ entry:
%loadN = load i64, ptr %allocaN, align 8
%loadN = load i64, ptr %allocaN, align 8
%add = add i64 %loadN, %loadN
%call = call { ptr, i64 } @cstring(ptr %0, i64 %add)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %add)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -226,7 +226,7 @@ entry:
%allocaN = alloca i64, align 8
store i64 %3, ptr %allocaN, align 8
%load = load i64, ptr %allocaN, align 8
%call = call { ptr, i64 } @cstring(ptr %0, i64 %load)
%call = call { ptr, i64 } @alloc_string(ptr %0, i64 %load)
%allocaN = alloca { ptr, i64 }, align 8
store { ptr, i64 } %call, ptr %allocaN, align 8
%loadN = load { ptr, i64 }, ptr %allocaN, align 8
@@ -241,6 +241,15 @@ entry:
ret { ptr, i64 } %loadN
}
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -750,9 +759,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -23,7 +23,7 @@ declare ptr @memset(ptr, i32, i64)
declare void @out.1(ptr) #0
; Function Attrs: nounwind
declare ptr @cstring(ptr, i64) #0
declare ptr @alloc_string(ptr, i64) #0
; Function Attrs: nounwind
declare ptr @int_to_string(ptr, i64) #0
@@ -52,6 +52,15 @@ declare ptr @concat(ptr, ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @substr(ptr, ptr, i64, i64) #0
; Function Attrs: nounwind
declare i64 @cstring_len(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @from_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @to_cstring(ptr, ptr) #0
; Function Attrs: nounwind
declare ptr @path_join(ptr, ptr) #0
@@ -273,9 +282,6 @@ declare i32 @feof(ptr) #0
; Function Attrs: nounwind
declare ptr @getenv(ptr) #0
; Function Attrs: nounwind
declare i64 @strlen(ptr) #0
; Function Attrs: nounwind
declare i32 @system(ptr) #0

View File

@@ -204,7 +204,7 @@ bundle_main :: () -> bool {
// buffer for libc / `[:0]u8` callees. Allocated from
// `context.allocator` like the rest of the bundling stage.
str_to_cstr :: (s: string) -> [:0]u8 {
buf := cstring(s.len);
buf := alloc_string(s.len);
memcpy(buf.ptr, s.ptr, s.len);
buf
}
@@ -927,7 +927,7 @@ MANIFEST, pkg_esc, lib_esc, lib_esc, lib_esc)
// `co/swipelab/sxchess/SxApp` → `co.swipelab.sxchess.SxApp`.
slash_to_dot :: (path: string) -> string {
buf := cstring(path.len);
buf := alloc_string(path.len);
i := 0;
while i < path.len {
c := path[i];

View File

@@ -69,7 +69,10 @@ optional_to_string :: fmt.optional_to_string;
concat :: fmt.concat;
substr :: fmt.substr;
path_join :: fmt.path_join;
cstring :: fmt.cstring;
alloc_string :: fmt.alloc_string;
cstring_len :: fmt.cstring_len;
from_cstring :: fmt.from_cstring;
to_cstring :: fmt.to_cstring;
alloc_slice :: fmt.alloc_slice;
// fmt internals, re-exported only because they were always part of the

View File

@@ -6,7 +6,7 @@
// --- Slice & string allocation ---
cstring :: (size: i64) -> string {
alloc_string :: (size: i64) -> string {
raw := context.allocator.alloc_bytes(size + 1);
memset(raw, 0, size + 1);
s : string = ---;
@@ -31,7 +31,7 @@ int_to_string :: (n: i64) -> string {
// overflows for i64::MIN (its magnitude is unrepresentable as a
// positive i64). sx `%` truncates toward zero, so `n % 10` keeps n's
// sign; take each remainder's absolute value for the digit.
tmp := cstring(20);
tmp := alloc_string(20);
i := 19;
v := n;
while v != 0 {
@@ -56,7 +56,7 @@ uint_to_string :: (n: i64) -> string {
// next limb; the per-step accumulator stays well within i64
// (max 9*65536 + 65535), so signed `/` and `%` are exact.
g := decompose_u16x4(n);
tmp := cstring(20);
tmp := alloc_string(20);
i := 19;
while g[0] != 0 or g[1] != 0 or g[2] != 0 or g[3] != 0 {
rem := 0;
@@ -89,7 +89,7 @@ float_to_string :: (f: f64) -> string {
fl := fstr.len;
prefix := if neg then 1 else 0;
total := prefix + il + 1 + 6;
buf := cstring(total);
buf := alloc_string(total);
pos := 0;
if neg { buf[0] = 45; pos = 1; }
memcpy(@buf[pos], istr.ptr, il);
@@ -144,7 +144,7 @@ int_to_hex_string :: (n: i64) -> string {
if n == 0 { return "0"; }
g := decompose_u16x4(n);
buf := cstring(16);
buf := alloc_string(16);
hex_group(buf, 0, g[0]);
hex_group(buf, 4, g[1]);
hex_group(buf, 8, g[2]);
@@ -162,18 +162,48 @@ int_to_hex_string :: (n: i64) -> string {
concat :: (a: string, b: string) -> string {
al := a.len;
bl := b.len;
buf := cstring(al + bl);
buf := alloc_string(al + bl);
memcpy(buf.ptr, a.ptr, al);
memcpy(@buf[al], b.ptr, bl);
buf
}
substr :: (s: string, start: i64, len: i64) -> string {
buf := cstring(len);
buf := alloc_string(len);
memcpy(buf.ptr, @s[start], len);
buf
}
// ── cstring: the C-boundary string ────────────────────────────────────
// `cstring` is ONE pointer to a null-terminated u8 buffer — C's `char *`.
// It carries no length (`cstring_len` walks to the terminator) and
// crosses `#foreign` boundaries verbatim in both directions; `?cstring`
// is the nullable case (null pointer = absent). String LITERALS coerce
// to `cstring` implicitly — their bytes are terminated constants; every
// other `string` must materialize through `to_cstring`.
// Byte length of `c` (strlen — O(n), walks to the terminator).
cstring_len :: (c: cstring) -> i64 {
p : [*]u8 = xx c;
n := 0;
while p[n] != 0 { n += 1; }
n
}
// A zero-copy string VIEW over `c`'s bytes ({ptr, strlen}). The view
// shares C's buffer — `substr` it if it must outlive the source.
from_cstring :: (c: cstring) -> string {
p : [*]u8 = xx c;
string.{ ptr = p, len = cstring_len(c) }
}
// An owned, terminated copy of `s` as a `cstring`.
to_cstring :: (s: string) -> cstring {
z := alloc_string(s.len);
memcpy(z.ptr, s.ptr, s.len);
xx z.ptr
}
// Join path components with the POSIX separator ('/'). Skips empty
// components and collapses duplicate separators at component
// boundaries. Used for bundle paths where Apple .app and Android APK

View File

@@ -130,7 +130,7 @@ read_file :: (path: [:0]u8) -> ?string {
size := lseek(fd, 0, SEEK_END);
if size < 0 { close(fd); return null; }
lseek(fd, 0, SEEK_SET);
buf := cstring(size);
buf := alloc_string(size);
n := read(fd, buf.ptr, xx size);
close(fd);
if cast(i64) n != size { return null; }
@@ -191,7 +191,7 @@ create_dir_all :: (path: [:0]u8) -> bool {
last -= 1;
}
if last > 0 {
parent := cstring(last);
parent := alloc_string(last);
memcpy(parent.ptr, path.ptr, last);
if !create_dir_all(parent) { return false; }
}
@@ -211,7 +211,7 @@ copy_file :: (src: [:0]u8, dst: [:0]u8) -> bool {
return false;
}
ok := true;
buf := cstring(65536);
buf := alloc_string(65536);
loop := true;
while loop {
n := read(src_fd, buf.ptr, 65536);

View File

@@ -24,8 +24,7 @@ popen :: (cmd: [:0]u8, mode: [:0]u8) -> *void #foreign libc;
pclose :: (stream: *void) -> i32 #foreign libc;
fread :: (ptr: [*]u8, size: usize, nmemb: usize, stream: *void) -> usize #foreign libc;
feof :: (stream: *void) -> i32 #foreign libc;
getenv :: (name: [:0]u8) -> *u8 #foreign libc;
strlen :: (s: *u8) -> usize #foreign libc;
getenv :: (name: cstring) -> ?cstring #foreign libc;
system :: (cmd: [:0]u8) -> i32 #foreign libc;
// ── Public types ─────────────────────────────────────────────────────
@@ -53,7 +52,7 @@ run :: (cmd: [:0]u8) -> ?ProcessResult {
if cast(i64) f == 0 { return null; }
out := "";
buf := cstring(4096);
buf := alloc_string(4096);
loop := true;
while loop {
n := fread(buf.ptr, 1, 4096, f);
@@ -84,14 +83,11 @@ run :: (cmd: [:0]u8) -> ?ProcessResult {
// Read an environment variable. Returns null if unset; an empty
// string if set to "".
env :: (name: [:0]u8) -> ?string {
p := getenv(name);
addr : i64 = xx p;
if addr == 0 { return null; }
n := strlen(p);
if n == 0 { return ""; }
buf := cstring(cast(i64) n);
memcpy(buf.ptr, xx p, cast(i64) n);
buf
p := getenv(to_cstring(name));
if p == null { return null; }
v := from_cstring(p!);
if v.len == 0 { return ""; }
substr(v, 0, v.len)
}
// Locate an executable by walking `$PATH`. Returns the absolute path
@@ -103,7 +99,7 @@ find_executable :: (name: [:0]u8) -> ?string {
// (executable names like `codesign`, `javac`, `aapt2`).
cmd := concat("command -v ", name);
// Need null-terminated for popen.
cmd_z := cstring(cmd.len);
cmd_z := alloc_string(cmd.len);
memcpy(cmd_z.ptr, cmd.ptr, cmd.len);
if r := run(cmd_z) {
if r.exit_code != 0 { return null; }

View File

@@ -16,7 +16,7 @@ Line comments start with `//` and extend to end of line.
#### Reserved type names
A spelling that names a builtin type — the arbitrary-width integers `i1`..`i64` /
`u1`..`u64`, plus `bool`, `string`, `void`, `f32`, `f64`, `usize`, `isize`, `Any`
`u1`..`u64`, plus `bool`, `string`, `cstring`, `void`, `f32`, `f64`, `usize`, `isize`, `Any`
is reserved. A bare reserved spelling is rejected at **value-binding and
declaration-name sites**: a value binding (`:=` / typed local / parameter), a
`::` **constant** or **function** declaration, an `impl` method **definition**,
@@ -39,8 +39,8 @@ slot), so a reserved-spelled impl method still needs the backtick
(`` `i2 :: (self) ``), exactly like a free function. See `examples/0158`.
The bare member-name exemption applies only to the **identifier-classified**
reserved spellings — `i1`..`i64`, `u1`..`u64`, `bool`, `string`, `void`, `usize`,
`isize`, `Any` — which all lex as ordinary identifiers. The two
reserved spellings — `i1`..`i64`, `u1`..`u64`, `bool`, `string`, `cstring`, `void`,
`usize`, `isize`, `Any` — which all lex as ordinary identifiers. The two
**keyword-classified** reserved spellings, `f32` and `f64`, are lexer keywords, and
member-name slots require an identifier token; a bare `f32` / `f64` is therefore
rejected at parse (`expected field name in struct`) even in a member position. Use
@@ -1043,6 +1043,29 @@ the chain leaves the checked zone.
**Fat pointer layout**: `[:0]u8`, `string`, and `[]T` are `{ptr, i64}` structs. The raw pointer is always the first field at offset 0. This means `*[:0]u8` works as C's `char**` — a C function dereferences through the outer pointer and reads the raw `char*` from offset 0.
### cstring
`cstring` is the C-boundary string: ONE pointer to a null-terminated u8
buffer — exactly C's `char *`. It is thin (8 bytes, no length field;
`cstring_len` walks to the terminator, O(n)) and crosses `#foreign`
boundaries verbatim in BOTH directions. `?cstring` is the nullable case
and lowers to the same bare pointer (null = absent) — the natural type
for `getenv`-style returns and optional `char *` parameters.
Conversion discipline (Odin's model):
- A string **literal** coerces to `cstring` implicitly — literal bytes
are terminated constants in the binary, so the conversion is free.
- Any **other** `string` does NOT coerce: it may be an unterminated view
(`string.{ptr, len}` windows, writer output). Materialize an owned,
terminated copy with `to_cstring(s)`.
- `cstring` does not coerce to `string` implicitly — the length is an
O(n) strlen the code must ask for. `from_cstring(c)` is the zero-copy
view (shares C's buffer); `substr(from_cstring(c), 0, n)` the owned
copy.
- `xx` bit-casts `cstring` ↔ `*u8` / `[*]u8` / integer-pointer values
for low-level interop.
### Optional Types
Optional types represent values that may or may not be present.
@@ -1182,7 +1205,10 @@ write_fd :: (fd: i32, buf: [*]u8, count: u64) -> i64 #foreign libc "write";
| C type | sx type | Notes |
|--------|---------|-------|
| `const char*` (input) | `[:0]u8` | compiler extracts `.ptr` at call site |
| `const char*` (input) | `cstring` | the pointer, verbatim; literals coerce |
| `const char*` (input, legacy) | `[:0]u8` | compiler extracts `.ptr` at call site |
| `const char*` (return) | `cstring` | the pointer, verbatim; `from_cstring` to view |
| nullable `const char*` (both directions) | `?cstring` | null pointer = `null` |
| `char*` (output buffer) | `[*]u8` | raw buffer, no length |
| `const char**` | `*[:0]u8` | address of `[:0]u8` — `.ptr` at offset 0 |
| `int*` (single out) | `*i32` | |

View File

@@ -64,13 +64,14 @@ pub const TypeLowering = struct {
.bool => self.e.cached_i1,
.error_set => self.e.cached_i32, // u32 tag id on the error channel
.string => self.e.getStringStructType(),
.cstring => self.e.cached_ptr,
.pointer, .many_pointer, .function => self.e.cached_ptr,
.closure => self.e.getClosureStructType(),
.slice => self.e.getStringStructType(), // same {ptr, i64} layout
.optional => |opt| {
// ?*T / ?fn → bare pointer (null = none)
const child_info = self.e.ir_mod.types.get(opt.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function) {
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .cstring) {
return self.e.cached_ptr;
}
if (child_info == .closure) {

View File

@@ -43,11 +43,15 @@ pub const CoercionResolver = struct {
widen, // same kind, dst wider
narrow, // same kind, dst narrower
array_to_slice, // [N]T → []T (materialize backing storage + header)
string_to_cstring, // literal-only implicit; other strings need to_cstring
cstring_to_string_reject, // explicit from_cstring required (diagnostic)
none, // nothing applies — pass the value through
};
pub fn classify(self: CoercionResolver, src_ty: TypeId, dst_ty: TypeId) CoercionPlan {
if (src_ty == dst_ty) return .no_op;
if (src_ty == .string and dst_ty == .cstring) return .string_to_cstring;
if (src_ty == .cstring and dst_ty == .string) return .cstring_to_string_reject;
if (src_ty == .any and dst_ty != .any) return .unbox_any;
if (dst_ty == .any and src_ty != .any) return .box_any;
@@ -113,8 +117,8 @@ pub const CoercionResolver = struct {
const dst_float = Lowering.isFloat(dst_ty);
const src_int = self.l.isIntEx(src_ty);
const dst_int = self.l.isIntEx(dst_ty);
const src_ptr = !src_ty.isBuiltin() and self.l.module.types.get(src_ty) == .pointer;
const dst_ptr = !dst_ty.isBuiltin() and self.l.module.types.get(dst_ty) == .pointer;
const src_ptr = (!src_ty.isBuiltin() and self.l.module.types.get(src_ty) == .pointer) or src_ty == .cstring;
const dst_ptr = (!dst_ty.isBuiltin() and self.l.module.types.get(dst_ty) == .pointer) or dst_ty == .cstring;
if (src_int and dst_float) return .int_to_float;
if (src_float and dst_int) return .float_to_int;

View File

@@ -630,6 +630,30 @@ pub fn coerceMode(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId, mod
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = val } }, child_ty);
return self.coerceMode(unwrapped, child_ty, dst_ty, mode);
},
// string → cstring: ONLY a string LITERAL coerces implicitly — its
// bytes are a terminated constant (Odin's literal blessing). Any
// other string may be an unterminated view, so it must materialize
// through `to_cstring`.
.string_to_cstring => {
if (self.builder.isConstString(val)) {
return self.builder.emit(.{ .data_ptr = .{ .operand = val } }, .cstring);
}
if (self.diagnostics) |d| {
const cs = self.builder.current_span;
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "only a string LITERAL coerces to 'cstring' implicitly; an arbitrary string may be an unterminated view — materialize it with to_cstring(s)", .{});
}
return val;
},
// cstring → string: the length is implicit (strlen), so the
// conversion is never silent — `from_cstring(c)` is the zero-copy
// view, `substr(from_cstring(c), 0, ...)` the owned copy.
.cstring_to_string_reject => {
if (self.diagnostics) |d| {
const cs = self.builder.current_span;
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "'cstring' does not coerce to 'string' implicitly (the length is implicit); convert with from_cstring(c)", .{});
}
return val;
},
// void → Optional: produce null (void is the type of null_literal)
.void_to_optional => return self.builder.constNull(dst_ty),
// Concrete → Optional wrapping (coerce to the inner type first)

View File

@@ -358,6 +358,24 @@ pub const Builder = struct {
/// value and the span it was emitted with; else null. The implicit
/// float→int coercion rule reads this to fold an integral literal to its
/// int (and to locate a non-integral one for its diagnostic).
/// True iff `ref` is a `const_string` instruction — a string LITERAL
/// value (terminated constant data), the only string shape that may
/// implicitly coerce to `cstring`.
pub fn isConstString(self: *Builder, ref: Ref) bool {
if (self.func == null) return false;
const func = self.currentFunc();
const ref_idx = @intFromEnum(ref);
if (ref_idx < func.params.len) return false;
for (func.blocks.items) |*block| {
const first = block.first_ref;
if (ref_idx >= first and ref_idx < first + @as(u32, @intCast(block.insts.items.len))) {
const i = block.insts.items[ref_idx - first];
return i.op == .const_string;
}
}
return false;
}
pub fn constFloatInfo(self: *Builder, ref: Ref) ?ConstFloatInfo {
if (self.func == null) return null;
const func = self.currentFunc();

View File

@@ -55,6 +55,7 @@ pub const TypeResolver = struct {
if (std.mem.eql(u8, name, "f64")) return .f64;
if (std.mem.eql(u8, name, "bool")) return .bool;
if (std.mem.eql(u8, name, "string")) return .string;
if (std.mem.eql(u8, name, "cstring")) return .cstring;
if (std.mem.eql(u8, name, "void")) return .void;
if (std.mem.eql(u8, name, "Any")) return .any;
// `Type` values are runtime-representable as Any-shaped pairs

View File

@@ -33,9 +33,10 @@ pub const TypeId = enum(u32) {
isize = 15,
usize = 16,
void = 17,
_, // user-defined types start at 18
cstring = 18, // thin null-terminated char* (see TypeInfo.cstring)
_, // user-defined types start at 19
pub const first_user: u32 = 18;
pub const first_user: u32 = 19;
pub fn index(self: TypeId) u32 {
return @intFromEnum(self);
@@ -63,6 +64,7 @@ pub const TypeInfo = union(enum) {
void,
bool,
string, // [:0]u8 — fat pointer {ptr, len}
cstring, // thin null-terminated char* — ONE pointer, length implicit (strlen)
@"struct": StructInfo,
@"enum": EnumInfo,
@@ -382,6 +384,7 @@ pub const TypeTable = struct {
.isize, // 15: isize (pointer-sized signed)
.usize, // 16: usize (pointer-sized unsigned)
.void, // 17
.cstring, // 18: thin null-terminated char*
};
for (&builtins) |info| {
table.infos.append(alloc, info) catch unreachable;
@@ -587,13 +590,14 @@ pub const TypeTable = struct {
.f32 => 4,
.f64 => 8,
.string => 16, // {ptr, len}
.cstring => 8, // one pointer
.pointer, .many_pointer, .function => 8,
.closure => 16, // {fn_ptr, env}
.optional => |opt| blk: {
// Sentinel-shaped optionals (pointer/closure/protocol) cost
// no extra storage — null reuses the payload's null state.
const child_info = self.get(opt.child);
if (child_info == .pointer or child_info == .many_pointer or child_info == .function) break :blk 8;
if (child_info == .pointer or child_info == .many_pointer or child_info == .function or child_info == .cstring) break :blk 8;
if (child_info == .closure) break :blk 16;
if (child_info == .@"struct" and child_info.@"struct".is_protocol) break :blk self.sizeOf(opt.child);
// Discriminated form: payload + has_value flag (8-aligned).
@@ -682,6 +686,7 @@ pub const TypeTable = struct {
if (ty == .i32 or ty == .u32 or ty == .f32) return 4;
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
if (ty == .usize or ty == .isize) return ptr_size;
if (ty == .cstring) return ptr_size;
if (ty == .string) return 16; // {ptr, i64} — always 16 (i64 alignment pads on wasm32)
if (ty == .any) return 16; // {i64 tag, i64 value} — Any boxed layout
if (ty.isBuiltin()) return ptr_size; // default for unknown builtins
@@ -783,6 +788,7 @@ pub const TypeTable = struct {
if (ty == .i64 or ty == .u64 or ty == .f64) return 8;
if (ty == .usize or ty == .isize) return ptr_align;
if (ty == .string) return 8; // i64 drives alignment
if (ty == .cstring) return ptr_align;
if (ty == .any) return 8; // {i64, i64} aligns to 8
if (ty.isBuiltin()) return ptr_align;
const info = self.get(ty);
@@ -853,6 +859,7 @@ pub const TypeTable = struct {
.f32 => "f32",
.f64 => "f64",
.string => "string",
.cstring => "cstring",
.any => "Any",
.noreturn => "noreturn",
.isize => "isize",
@@ -999,7 +1006,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
switch (info) {
.signed => |w| h.update(&.{w}),
.unsigned => |w| h.update(&.{w}),
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => {},
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => {},
.pointer => |p| h.update(std.mem.asBytes(&p.pointee)),
.many_pointer => |p| h.update(std.mem.asBytes(&p.element)),
.slice => |s| h.update(std.mem.asBytes(&s.element)),
@@ -1070,7 +1077,7 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
return switch (a) {
.signed => |w| w == b.signed,
.unsigned => |w| w == b.unsigned,
.f32, .f64, .void, .bool, .string, .any, .noreturn, .usize, .isize, .unresolved => true,
.f32, .f64, .void, .bool, .string, .cstring, .any, .noreturn, .usize, .isize, .unresolved => true,
.pointer => |p| p.pointee == b.pointer.pointee,
.many_pointer => |p| p.element == b.many_pointer.element,
.slice => |s| s.element == b.slice.element,

View File

@@ -20,6 +20,7 @@ pub const Type = union(enum) {
void_type,
boolean,
string_type,
cstring_type,
enum_type: []const u8,
struct_type: []const u8,
union_type: []const u8,
@@ -116,6 +117,7 @@ pub const Type = union(enum) {
if (name.len == 0) return null;
return switch (name[0]) {
's' => if (std.mem.eql(u8, name, "string")) .string_type else null,
'c' => if (std.mem.eql(u8, name, "cstring")) .cstring_type else null,
'u' => {
if (std.mem.eql(u8, name, "usize")) return .usize_type;
if (name.len >= 2) {
@@ -199,6 +201,7 @@ pub const Type = union(enum) {
.f64 => "f64",
.boolean => "bool",
.string_type => "string",
.cstring_type => "cstring",
.void_type => "void",
.usize_type => "usize",
.isize_type => "isize",
@@ -286,6 +289,7 @@ pub const Type = union(enum) {
.f64 => "f64",
.boolean => "bool",
.string_type => "string",
.cstring_type => "cstring",
.void_type => "void",
.any_type => "Any",
.usize_type => "usize",