refactor: List is slice-backed { items: []T; cap } — directly iterable
items is now a []T slice whose .len IS the live element count (cap = allocated
capacity), so a List iterates directly: `for xs.items (e) { ... }`. A
`len :: (self) -> i64 #get => items.len` accessor keeps `xs.len` reads working;
`.len` WRITES become `.items.len`. List stays 24 bytes (`[]T`=16 + cap=8).
- list.sx: append/ensure_capacity/deinit rewritten for the slice backing. deinit
guards the free on `cap > 0` (true ownership) and resets via explicit
ptr=null/len=0 (a `.{}` slice assignment yields a garbage len; `.[]` is the
empty-slice literal but can't be assigned to a generic []T — both worked around).
- Compiler coupling updated: comptime_vm makeStringList/readStringList write/read
items as a {ptr,len} fat pointer at field 0 + cap at field 1; control_flow
listView views an `items: []T` slice (keeps the legacy {[*]T,len} shape too).
- Migrated List `.len` writes to `.items.len` in sched.sx + ui/{render,pipeline,
glyph_cache} + platform/{sdl3,android,uikit}.
- Snapshots: List's type-table layout changed → ~40 .ir + memory/0800 (items now
prints as a slice) regenerated; diagnostics/1183 retargeted to a genuine
many-pointer (xs.items is a slice now). Example memory/0840 locks for-each.
This commit is contained in:
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
@@ -15,10 +15,9 @@ sum :: (s: []i64) -> i64 {
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
xs : List(i64) = .{};
|
||||
xs.append(10);
|
||||
xs.append(20);
|
||||
r := sum(xs.items); // [*]i64 → []i64 — needs xs.items[0..xs.len]
|
||||
a : [4]i64 = .[10, 20, 30, 40];
|
||||
mp : [*]i64 = xx @a[0]; // a genuine many-pointer (carries no length)
|
||||
r := sum(mp); // [*]i64 → []i64 — rejected; needs mp[0..len]
|
||||
print("{}\n", r);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
error: a many-pointer '[*]T' does not coerce to a slice '[]T' implicitly (it carries no length) — slice it with a length: ptr[0..len]
|
||||
--> examples/diagnostics/1183-diagnostics-many-pointer-to-slice-rejected.sx:21:10
|
||||
--> examples/diagnostics/1183-diagnostics-many-pointer-to-slice-rejected.sx:20:10
|
||||
|
|
||||
21 | r := sum(xs.items); // [*]i64 → []i64 — needs xs.items[0..xs.len]
|
||||
| ^^^^^^^^^^^^^
|
||||
20 | r := sum(mp); // [*]i64 → []i64 — rejected; needs mp[0..len]
|
||||
| ^^^^^^^
|
||||
|
||||
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
43
examples/memory/0840-memory-list-foreach.sx
Normal file
43
examples/memory/0840-memory-list-foreach.sx
Normal file
@@ -0,0 +1,43 @@
|
||||
// List is `{ items: []T; cap }` — `items` is a `[]T` slice whose `.len` IS the
|
||||
// live element count, so a List is directly iterable with a `for`-each, and
|
||||
// `xs.len` reads the live count via a `#get` accessor. Exercises append (incl.
|
||||
// a realloc past the initial cap of 4), for-each, parallel for-with-index,
|
||||
// empty iteration, direct `for xs` over the List, and truncation via items.len.
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () -> i64 {
|
||||
xs : List(i64) = .{};
|
||||
i := 0;
|
||||
while i < 6 { xs.append((i + 1) * 10); i = i + 1; } // 10..60; grows 4 -> 8
|
||||
|
||||
print("len={} cap>={}\n", xs.len, if xs.cap >= 6 then 1 else 0); // len=6 cap>=1
|
||||
|
||||
// for-each over the items slice
|
||||
sum := 0;
|
||||
for xs.items (e) { sum = sum + e; }
|
||||
print("foreach sum={}\n", sum); // 210
|
||||
|
||||
// parallel for with index
|
||||
print("indexed:");
|
||||
for xs.items, 0.. (e, ix) { if ix % 2 == 0 { print(" {}@{}", e, ix); } }
|
||||
print("\n"); // 10@0 30@2 50@4
|
||||
|
||||
// direct `for xs` over the List value (listView path)
|
||||
s2 := 0;
|
||||
for xs (e) { s2 = s2 + e; }
|
||||
print("for-list sum={}\n", s2); // 210
|
||||
|
||||
// len via #get used as a loop bound + indexing
|
||||
acc := 0;
|
||||
j := 0;
|
||||
while j < xs.len { acc = acc + xs.items[j]; j = j + 1; }
|
||||
print("indexed sum={}\n", acc); // 210
|
||||
|
||||
// truncate to empty via items.len, then iterate (zero iterations)
|
||||
xs.items.len = 0;
|
||||
cnt := 0;
|
||||
for xs.items (e) { cnt = cnt + 1; }
|
||||
print("after trunc: len={} iters={}\n", xs.len, cnt); // len=0 iters=0
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
List__i32{items: [*]i32@0xADDR, len: 5, cap: 8}
|
||||
List__i32{items: [1, 3, 4, 1, 5], cap: 8}
|
||||
|
||||
1
examples/memory/expected/0840-memory-list-foreach.exit
Normal file
1
examples/memory/expected/0840-memory-list-foreach.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/memory/expected/0840-memory-list-foreach.stderr
Normal file
1
examples/memory/expected/0840-memory-list-foreach.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
6
examples/memory/expected/0840-memory-list-foreach.stdout
Normal file
6
examples/memory/expected/0840-memory-list-foreach.stdout
Normal file
@@ -0,0 +1,6 @@
|
||||
len=6 cap>=1
|
||||
foreach sum=210
|
||||
indexed: 10@0 30@2 50@4
|
||||
for-list sum=210
|
||||
indexed sum=210
|
||||
after trunc: len=0 iters=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
@@ -400,7 +400,7 @@ impl Platform for AndroidPlatform {
|
||||
}
|
||||
|
||||
poll_events :: (self: *AndroidPlatform) -> []Event {
|
||||
self.events.len = 0;
|
||||
self.events.items.len = 0;
|
||||
sx_android_drain_touches(self, @self.events);
|
||||
result : []Event = ---;
|
||||
result.ptr = self.events.items;
|
||||
|
||||
@@ -144,7 +144,7 @@ impl Platform for SdlPlatform {
|
||||
}
|
||||
|
||||
poll_events :: (self: *SdlPlatform) -> []Event {
|
||||
self.events.len = 0;
|
||||
self.events.items.len = 0;
|
||||
sdl_event : SDL_Event = .none;
|
||||
while SDL_PollEvent(@sdl_event) {
|
||||
if sdl_event == {
|
||||
|
||||
@@ -379,7 +379,7 @@ impl Platform for UIKitPlatform {
|
||||
result : []Event = ---;
|
||||
result.ptr = self.events.items;
|
||||
result.len = self.events.len;
|
||||
self.events.len = 0;
|
||||
self.events.items.len = 0;
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -2,45 +2,58 @@
|
||||
// directly — std.sx re-exports `List`.
|
||||
#import "modules/std/core.sx";
|
||||
|
||||
// `items` is a `[]T` slice whose `.len` IS the live element count, so a `List`
|
||||
// is directly iterable: `for xs.items (e) { ... }`. `cap` is the allocated
|
||||
// capacity (`>= items.len`); the buffer spans `cap` elements, the slice spans
|
||||
// the live `items.len`. The `len` `#get` accessor exposes the live count as
|
||||
// `xs.len` (read), delegating to `items.len`; writes use `xs.items.len`.
|
||||
List :: struct ($T: Type) {
|
||||
items: [*]T = null;
|
||||
len: i64 = 0;
|
||||
items: []T = .[]; // empty slice ({ptr, len=0}); `.[]` is the empty-slice
|
||||
// literal — `.{}` would init the slice's underlying
|
||||
// {ptr,len} struct (and currently yields a garbage len).
|
||||
cap: i64 = 0;
|
||||
|
||||
// No-paren read accessor: `xs.len` → the live element count.
|
||||
len :: (self: *List(T)) -> i64 #get => self.items.len;
|
||||
|
||||
append :: (list: *List(T), item: T, alloc: Allocator = context.allocator) {
|
||||
if list.len >= list.cap {
|
||||
if list.items.len >= list.cap {
|
||||
new_cap := if list.cap == 0 then 4 else list.cap * 2;
|
||||
new_items : [*]T = xx alloc.alloc_bytes(new_cap * size_of(T));
|
||||
if list.len > 0 {
|
||||
memcpy(new_items, list.items, list.len * size_of(T));
|
||||
alloc.dealloc_bytes(list.items);
|
||||
new_ptr : [*]T = xx alloc.alloc_bytes(new_cap * size_of(T));
|
||||
if list.items.len > 0 {
|
||||
memcpy(new_ptr, list.items.ptr, list.items.len * size_of(T));
|
||||
alloc.dealloc_bytes(list.items.ptr);
|
||||
}
|
||||
list.items = new_items;
|
||||
list.items.ptr = new_ptr; // keep the live len; only the buffer moves
|
||||
list.cap = new_cap;
|
||||
}
|
||||
list.items[list.len] = item;
|
||||
list.len += 1;
|
||||
list.items.ptr[list.items.len] = item; // write at the live index (within cap)
|
||||
list.items.len += 1;
|
||||
}
|
||||
|
||||
ensure_capacity :: (list: *List(T), n: i64, alloc: Allocator = context.allocator) {
|
||||
if list.cap >= n { return; }
|
||||
new_cap := if list.cap == 0 then 4 else list.cap;
|
||||
while new_cap < n { new_cap = new_cap * 2; }
|
||||
new_items : [*]T = xx alloc.alloc_bytes(new_cap * size_of(T));
|
||||
if list.len > 0 {
|
||||
memcpy(new_items, list.items, list.len * size_of(T));
|
||||
alloc.dealloc_bytes(list.items);
|
||||
new_ptr : [*]T = xx alloc.alloc_bytes(new_cap * size_of(T));
|
||||
if list.items.len > 0 {
|
||||
memcpy(new_ptr, list.items.ptr, list.items.len * size_of(T));
|
||||
alloc.dealloc_bytes(list.items.ptr);
|
||||
}
|
||||
list.items = new_items;
|
||||
list.items.ptr = new_ptr;
|
||||
list.cap = new_cap;
|
||||
}
|
||||
|
||||
deinit :: (list: *List(T), alloc: Allocator = context.allocator) {
|
||||
if list.items != null {
|
||||
alloc.dealloc_bytes(list.items);
|
||||
// `cap > 0` is the ownership signal: a List holds an allocated buffer
|
||||
// ONLY after a growth set `cap`. Guarding on `cap` (not `items.ptr`)
|
||||
// makes deinit idempotent and safe on a never-grown / borrowed-items
|
||||
// list — and `.[]` leaves a len-0 slice whose ptr need not be null.
|
||||
if list.cap > 0 {
|
||||
alloc.dealloc_bytes(list.items.ptr);
|
||||
}
|
||||
list.items = null;
|
||||
list.len = 0;
|
||||
list.items.ptr = null;
|
||||
list.items.len = 0;
|
||||
list.cap = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +647,7 @@ remove_timer :: (self: *Scheduler, idx: i64) {
|
||||
self.timers.items[i] = self.timers.items[i + 1];
|
||||
i = i + 1;
|
||||
}
|
||||
self.timers.len = self.timers.len - 1;
|
||||
self.timers.items.len = self.timers.items.len - 1;
|
||||
}
|
||||
|
||||
// Remove a pending sleep timer referencing fiber `f`, if any. A fiber has at
|
||||
@@ -676,7 +676,7 @@ remove_io_waiter :: (self: *Scheduler, idx: i64) {
|
||||
self.io_waiters.items[i] = self.io_waiters.items[i + 1];
|
||||
i = i + 1;
|
||||
}
|
||||
self.io_waiters.len = self.io_waiters.len - 1;
|
||||
self.io_waiters.items.len = self.io_waiters.items.len - 1;
|
||||
}
|
||||
|
||||
// Remove a pending fd-waiter referencing fiber `f`, if any. A fiber has at most
|
||||
|
||||
@@ -575,7 +575,7 @@ GlyphCache :: struct {
|
||||
return; // shaped_buf already has the result
|
||||
}
|
||||
|
||||
self.shaped_buf.len = 0;
|
||||
self.shaped_buf.items.len = 0;
|
||||
if text.len == 0 { return; }
|
||||
|
||||
if is_ascii(text) {
|
||||
|
||||
@@ -141,7 +141,7 @@ UIPipeline :: struct {
|
||||
|
||||
// Reset render_tree nodes (backing is stale after arena reset)
|
||||
self.render_tree.nodes.items = null;
|
||||
self.render_tree.nodes.len = 0;
|
||||
self.render_tree.nodes.items.len = 0;
|
||||
self.render_tree.nodes.cap = 0;
|
||||
|
||||
push Context.{ allocator = xx build_arena, data = context.data } {
|
||||
|
||||
@@ -47,7 +47,7 @@ RenderTree :: struct {
|
||||
}
|
||||
|
||||
clear :: (self: *RenderTree) {
|
||||
self.nodes.len = 0;
|
||||
self.nodes.items.len = 0;
|
||||
self.generation += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -305,10 +305,14 @@ max :: (a: $T, b: T) -> T {
|
||||
}
|
||||
|
||||
List :: struct ($T: Type) {
|
||||
items: [*]T;
|
||||
len: i64;
|
||||
items: []T; // a slice; items.len is the live count, so a List is
|
||||
cap: i64; // directly iterable: `for xs.items (e) { ... }`
|
||||
|
||||
append :: (self: *List(T), item: T) { ... }
|
||||
|
||||
// `#get` property accessor: read via no-paren field syntax (`xs.len`),
|
||||
// not `xs.len()`. Takes only `self`; a real field of the same name wins.
|
||||
len :: (self: *List(T)) -> i64 #get => self.items.len;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -2516,18 +2516,22 @@ pub const Vm = struct {
|
||||
for (items, 0..) |s, i| {
|
||||
try self.writeField(table, backing + i * str_size, .string, try self.makeStringValue(table, s));
|
||||
}
|
||||
// The List struct: field 0 = items ([*]string), 1 = len (i64), 2 = cap (i64).
|
||||
// The List struct: field 0 = items (`[]string` fat pointer {ptr, len}),
|
||||
// field 1 = cap (i64). `items.ptr` = backing, `items.len` = cap = n.
|
||||
const addr = self.machine.allocBytes(table.typeSizeBytes(list_ty), 8);
|
||||
const items_fty = table.memberType(list_ty, 0) orelse
|
||||
return self.failMsg("comptime List builder: result type has no items field");
|
||||
const len_fty = table.memberType(list_ty, 1) orelse
|
||||
return self.failMsg("comptime List builder: result type has no len field");
|
||||
const cap_fty = table.memberType(list_ty, 2) orelse
|
||||
const cap_fty = table.memberType(list_ty, 1) orelse
|
||||
return self.failMsg("comptime List builder: result type has no cap field");
|
||||
const n: Reg = @bitCast(@as(i64, @intCast(items.len)));
|
||||
try self.writeField(table, addr + fieldOffset(table, list_ty, 0), items_fty, backing);
|
||||
try self.writeField(table, addr + fieldOffset(table, list_ty, 1), len_fty, n);
|
||||
try self.writeField(table, addr + fieldOffset(table, list_ty, 2), cap_fty, n);
|
||||
// Write the `items` slice as a {ptr, len} fat pointer at field 0.
|
||||
if (items_fty.isBuiltin() or table.get(items_fty) != .slice)
|
||||
return self.failMsg("comptime List builder: items field is not a slice");
|
||||
const items_off = addr + fieldOffset(table, list_ty, 0);
|
||||
try self.machine.writeWord(items_off, table.pointer_size, backing);
|
||||
try self.machine.writeWord(items_off + table.pointer_size, 8, n);
|
||||
// cap = live count (the backing is exactly `n` elements).
|
||||
try self.writeField(table, addr + fieldOffset(table, list_ty, 1), cap_fty, n);
|
||||
return addr;
|
||||
}
|
||||
|
||||
@@ -2547,8 +2551,11 @@ pub const Vm = struct {
|
||||
fn readStringList(self: *Vm, table: *const types.TypeTable, list_ty: TypeId, addr: Addr) Error![]const []const u8 {
|
||||
if (list_ty.isBuiltin() or table.get(list_ty) != .@"struct")
|
||||
return self.failMsg("comptime List reader: arg type is not a List struct");
|
||||
const items_ptr = try self.machine.readWord(addr + fieldOffset(table, list_ty, 0), table.pointer_size);
|
||||
const len: usize = @intCast(try self.machine.readWord(addr + fieldOffset(table, list_ty, 1), 8));
|
||||
// `items` is a `[]string` fat pointer at field 0: ptr at offset 0, len
|
||||
// at offset `pointer_size` (there is no separate `len` field now).
|
||||
const items_off = addr + fieldOffset(table, list_ty, 0);
|
||||
const items_ptr = try self.machine.readWord(items_off, table.pointer_size);
|
||||
const len: usize = @intCast(try self.machine.readWord(items_off + table.pointer_size, 8));
|
||||
const str_size = table.typeSizeBytes(.string);
|
||||
const out = self.gpa.alloc([]const u8, len) catch return self.failMsg("comptime List reader: out of memory");
|
||||
var i: usize = 0;
|
||||
|
||||
@@ -249,14 +249,34 @@ pub fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
||||
return self.builder.constInt(0, .void);
|
||||
}
|
||||
|
||||
/// View a `List(T)`-like struct (`{ items: [*]T, len, … }`) as its backing
|
||||
/// `items` pointer + element type + `len`, so `for list: (x)` iterates the
|
||||
/// elements. Null for anything that isn't such a struct.
|
||||
/// View a `List(T)`-like struct as its backing `items` pointer + element type
|
||||
/// + live length, so `for list (x)` iterates the elements. Two shapes:
|
||||
/// - CURRENT `List`: `{ items: []T, cap }` — `items` is a `[]T` slice whose
|
||||
/// own `.ptr`/`.len` ARE the backing pointer and live count.
|
||||
/// - LEGACY: `{ items: [*]T, len, … }` — a many-pointer `items` paired with a
|
||||
/// sibling `len` field (kept so a user struct of that shape still iterates).
|
||||
/// Null for anything that isn't such a struct.
|
||||
pub fn listView(self: *Lowering, value: Ref, ty: TypeId) ?struct { data: Ref, data_ty: TypeId, len: Ref } {
|
||||
if (ty.isBuiltin()) return null;
|
||||
const info = self.module.types.get(ty);
|
||||
if (info != .@"struct") return null;
|
||||
const items_id = self.module.types.internString("items");
|
||||
|
||||
// Current shape: an `items: []T` slice — view via its `.ptr`/`.len`.
|
||||
for (info.@"struct".fields, 0..) |f, i| {
|
||||
if (f.name == items_id and !f.ty.isBuiltin() and self.module.types.get(f.ty) == .slice) {
|
||||
const slice_val = self.builder.emit(.{ .struct_get = .{ .base = value, .field_index = @intCast(i) } }, f.ty);
|
||||
const elem = self.module.types.get(f.ty).slice.element;
|
||||
const mp_ty = self.module.types.manyPtrTo(elem);
|
||||
return .{
|
||||
.data = self.builder.emit(.{ .data_ptr = .{ .operand = slice_val } }, mp_ty),
|
||||
.data_ty = mp_ty,
|
||||
.len = self.builder.emit(.{ .length = .{ .operand = slice_val } }, .i64),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy shape: `items: [*]T` + a sibling `len` field.
|
||||
const len_id = self.module.types.internString("len");
|
||||
var items_idx: ?u32 = null;
|
||||
var items_ty: TypeId = .unresolved;
|
||||
|
||||
Reference in New Issue
Block a user