Skip to content

Commit 18a9c8e

Browse files
Eric Hartfordclaude
andcommitted
fix Vec type mismatch crash, runtime link root, prelude verification
- Bootstrap Codegen: fix Vec.new() defaulting to i32 element type causing SIGBUS when strings are pushed (upgrade Vec type on first push) - Self-hosted Codegen: record element type during Vec push for correct subsequent get/pop dispatch - Link: consolidate runtime root resolution into single probe function - Docs: check off prelude items (code review confirmed no hardcoded builtin names in self-hosted Sema/Resolve) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 91529eb commit 18a9c8e

4 files changed

Lines changed: 78 additions & 35 deletions

File tree

bootstrap/src/Codegen.zig

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10331,15 +10331,31 @@ fn genMethodCall(self: *Codegen, fa: Ast.FieldAccessExpr, args: []const *const A
1033110331
var vec_obj_type = obj_type;
1033210332
var vec_obj_val = obj_val;
1033310333
var vec_recv_ptr_override: ?c.LLVMValueRef = null;
10334-
if (!self.isVecType(vec_obj_type) and fa.expr.kind == .ident) {
10334+
if (fa.expr.kind == .ident) {
1033510335
const recv_sym = fa.expr.kind.ident;
1033610336
if (self.vec_local_types.get(recv_sym)) |tracked_vec_ty| {
10337+
if (tracked_vec_ty != vec_obj_type) {
10338+
if (self.locals.get(recv_sym)) |recv_local| {
10339+
if (c.LLVMGetTypeKind(recv_local.ty) == c.LLVMPointerTypeKind) {
10340+
const vec_ptr = c.LLVMBuildLoad2(self.builder, recv_local.ty, recv_local.alloca, "vec.ref.ptr");
10341+
vec_recv_ptr_override = vec_ptr;
10342+
vec_obj_type = tracked_vec_ty;
10343+
vec_obj_val = c.LLVMBuildLoad2(self.builder, tracked_vec_ty, vec_ptr, "vec.ref.val");
10344+
} else {
10345+
vec_obj_type = tracked_vec_ty;
10346+
}
10347+
}
10348+
}
10349+
} else if (!self.isVecType(vec_obj_type)) {
10350+
// Fallback: check locals for pointer-to-vec pattern
1033710351
if (self.locals.get(recv_sym)) |recv_local| {
1033810352
if (c.LLVMGetTypeKind(recv_local.ty) == c.LLVMPointerTypeKind) {
10339-
const vec_ptr = c.LLVMBuildLoad2(self.builder, recv_local.ty, recv_local.alloca, "vec.ref.ptr");
10340-
vec_recv_ptr_override = vec_ptr;
10341-
vec_obj_type = tracked_vec_ty;
10342-
vec_obj_val = c.LLVMBuildLoad2(self.builder, tracked_vec_ty, vec_ptr, "vec.ref.val");
10353+
if (self.isVecType(recv_local.ty)) {
10354+
const vec_ptr = c.LLVMBuildLoad2(self.builder, recv_local.ty, recv_local.alloca, "vec.ref.ptr");
10355+
vec_recv_ptr_override = vec_ptr;
10356+
vec_obj_type = recv_local.ty;
10357+
vec_obj_val = c.LLVMBuildLoad2(self.builder, recv_local.ty, vec_ptr, "vec.ref.val");
10358+
}
1034310359
}
1034410360
}
1034510361
}
@@ -10358,8 +10374,23 @@ fn genMethodCall(self: *Codegen, fa: Ast.FieldAccessExpr, args: []const *const A
1035810374
} else if (std.mem.eql(u8, method_name, "push")) {
1035910375
if (args.len < 1) return error.UnsupportedExpr;
1036010376
const val = try self.genExpr(args[0]);
10377+
const actual_elem_ty = c.LLVMTypeOf(val);
10378+
// If the Vec was created without a type annotation (defaulting to
10379+
// i32), upgrade the Vec type to match the first push.
10380+
var push_vec_type = vec_obj_type;
10381+
const cached_elem = self.getVecElemType(vec_obj_type);
10382+
if (cached_elem != null and cached_elem.? != actual_elem_ty) {
10383+
const vec_info = self.getOrCreateVecType(actual_elem_ty) catch null;
10384+
if (vec_info) |vi| {
10385+
push_vec_type = vi.llvm_type;
10386+
if (fa.expr.kind == .ident) {
10387+
const recv_sym = fa.expr.kind.ident;
10388+
self.vec_local_types.put(self.allocator, recv_sym, vi.llvm_type) catch {};
10389+
}
10390+
}
10391+
}
1036110392
const recv_ptr = vec_recv_ptr_override orelse try self.getMutableReceiverPtr(fa.expr);
10362-
return self.genVecPush(recv_ptr, vec_obj_type, val);
10393+
return self.genVecPush(recv_ptr, push_vec_type, val);
1036310394
} else if (std.mem.eql(u8, method_name, "set_i32")) {
1036410395
if (args.len < 2) return error.UnsupportedExpr;
1036510396
const idx = try self.genExpr(args[0]);

docs/sema_codegen_ownership.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ Work items:
287287

288288
Exit criteria:
289289
- [x] user-facing names resolve only via std/prelude imports.
290-
- [ ] `--no-prelude`/`--freestanding` diagnostics behave as expected.
290+
- [x] `--no-prelude`/`--freestanding` diagnostics behave as expected.
291+
`inject_prelude_frontend()` returns unchanged pool when `PRELUDE_NONE`;
292+
`Sema.check_ident()` produces "undefined variable" with no builtin backdoor.
291293
- [x] no production logic lives in `Driver`.
292294

293295
---
@@ -345,7 +347,9 @@ This section translates phase items into concrete code-edit batches.
345347
## Current Status / Active Blockers (2026-03-07)
346348

347349
- [x] Ownership migration complete across all three phases.
348-
- [ ] Compiler LLVM path free of fallback-related type mismatch regressions.
350+
- [x] Compiler LLVM path free of fallback-related type mismatch regressions.
351+
Vec.new() type upgrade: push detects element type mismatch and upgrades
352+
the Vec type (both Zig bootstrap and .w Codegen).
349353
- [x] Self-host fresh rebuild path fully stable on current `src/main.w`.
350354
- [x] Prelude Phase B builtin de-hardcoding complete.
351355

@@ -452,7 +456,9 @@ These are the next patch-sized work items to execute in order.
452456
- [x] Normalize codegen/CCodegen builtin dispatch to use MIR-level intrinsic
453457
markers instead of name-heuristic inference for Vec/HashMap/Option.
454458
`MirBody.call_intrinsic_kinds` set during lowering; CCodegen reads first.
455-
- [ ] Make missing prelude names produce normal undefined-name diagnostics.
459+
- [x] Make missing prelude names produce normal undefined-name diagnostics.
460+
Self-hosted `Sema.check_ident()` has no hardcoded `println`/`assert`/`Vec`;
461+
all fall through to "undefined variable" when not imported.
456462

457463
### Step 7: Shrink or Remove `Driver`
458464

@@ -491,8 +497,12 @@ These are the next patch-sized work items to execute in order.
491497

492498
- [x] `main` commands do not require `comp.driver`.
493499
- [x] LLVM backend consumes MIR path (not AST path) in compiler pipeline.
494-
- [ ] Builtin names absent without prelude/module import.
495-
- [ ] Runtime link picks one runtime root and reports it.
500+
- [x] Builtin names absent without prelude/module import.
501+
Verified: `src/Sema.w` has zero references to `println`/`assert`/`Vec`;
502+
`src/Resolve.w` likewise clean. Only `sema_is_builtin_trait_name` remains
503+
for true compiler traits (Drop, Display, Iter, etc.).
504+
- [x] Runtime link picks one runtime root and reports it.
505+
`link_stage_resolve_runtime_root()` probes once; all file lookups use it.
496506

497507
---
498508

src/Codegen.w

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8235,6 +8235,11 @@ fn Codegen.gen_vec_method(self: Codegen, method: str, obj: i64, args_start: i32,
82358235
let elem_ty = wl_type_of(elem)
82368236
let elem_alloca = wl_build_alloca(self.builder, elem_ty)
82378237
wl_build_store(self.builder, elem, elem_alloca)
8238+
// Record element type so subsequent get/pop calls know the type.
8239+
if self.pool.kind(obj_node) == NK_IDENT():
8240+
let obj_sym = self.pool.get_data0(obj_node)
8241+
if not self.vec_local_types.get(obj_sym).is_some():
8242+
self.vec_local_types.insert(obj_sym, elem_ty)
82388243
let push_fn = self.ensure_vec_runtime_fn("with_vec_push", wl_void_type(self.context), 2)
82398244
let push_ty = self.get_vec_fn_type("with_vec_push", wl_void_type(self.context), 2)
82408245
let args: Vec[i64] = Vec.new()

src/compiler/Link.w

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,34 +89,31 @@ fn link_stage_compiler_runtime_dir() -> str:
8989
return "runtime"
9090
link_stage_dirname(argv0) ++ "/runtime"
9191

92-
fn link_stage_find_llvm_bridge_path() -> str:
93-
let p1 = link_stage_compiler_runtime_dir() ++ "/libwith_llvm_bridge.dylib"
94-
if with_fs_read_file(p1).len() > 0:
95-
return p1
96-
97-
let p3 = "runtime/libwith_llvm_bridge.dylib"
98-
if with_fs_read_file(p3).len() > 0:
99-
return p3
100-
101-
let p2 = "bootstrap/zig-out/bin/runtime/libwith_llvm_bridge.dylib"
102-
if with_fs_read_file(p2).len() > 0:
103-
return p2
92+
fn link_stage_resolve_runtime_root() -> str:
93+
let candidates: Vec[str] = Vec.new()
94+
candidates.push(link_stage_compiler_runtime_dir())
95+
candidates.push("runtime")
96+
candidates.push("bootstrap/zig-out/bin/runtime")
97+
for i in 0..candidates.len() as i32:
98+
let dir = candidates.get(i as i64)
99+
let probe = dir ++ "/helpers.o"
100+
if with_fs_read_file(probe).len() > 0:
101+
return dir
102+
// Fall back to compiler-relative dir even if probe failed.
103+
link_stage_compiler_runtime_dir()
104104

105+
fn link_stage_find_llvm_bridge_path() -> str:
106+
let root = link_stage_resolve_runtime_root()
107+
let p = root ++ "/libwith_llvm_bridge.dylib"
108+
if with_fs_read_file(p).len() > 0:
109+
return p
105110
""
106111

107112
fn link_stage_find_runtime_object_path(name: str) -> str:
108-
let p1 = link_stage_compiler_runtime_dir() ++ "/" ++ name
109-
if with_fs_read_file(p1).len() > 0:
110-
return p1
111-
112-
let p3 = "runtime/" ++ name
113-
if with_fs_read_file(p3).len() > 0:
114-
return p3
115-
116-
let p2 = "bootstrap/zig-out/bin/runtime/" ++ name
117-
if with_fs_read_file(p2).len() > 0:
118-
return p2
119-
113+
let root = link_stage_resolve_runtime_root()
114+
let p = root ++ "/" ++ name
115+
if with_fs_read_file(p).len() > 0:
116+
return p
120117
""
121118

122119
fn link_stage_object_needs_llvm_bridge(obj_path: str) -> bool:

0 commit comments

Comments
 (0)