iOS lock step keyboard + metal
This commit is contained in:
@@ -267,6 +267,15 @@ pub const LLVMEmitter = struct {
|
||||
defer self.alloc.free(name_z);
|
||||
|
||||
const llvm_global = c.LLVMAddGlobal(self.llvm_module, llvm_ty, name_z.ptr);
|
||||
|
||||
// Extern globals (`<name> : <type> #foreign;`) resolve at link time
|
||||
// to a libSystem / framework symbol — no initializer, default linkage.
|
||||
if (global.is_extern) {
|
||||
c.LLVMSetLinkage(llvm_global, c.LLVMExternalLinkage);
|
||||
self.global_map.put(@intCast(i), llvm_global) catch {};
|
||||
continue;
|
||||
}
|
||||
|
||||
c.LLVMSetLinkage(llvm_global, c.LLVMInternalLinkage);
|
||||
|
||||
// Evaluate comptime initializer if present
|
||||
|
||||
387
src/ir/lower.zig
387
src/ir/lower.zig
@@ -83,6 +83,7 @@ pub const Lowering = struct {
|
||||
local_fn_counter: u32 = 0, // unique counter for mangling local function names
|
||||
import_flags: std.StringHashMap(bool), // tracks whether each function is imported
|
||||
module_scopes: ?*std.StringHashMap(std.StringHashMap(void)) = null, // per-module visible names (from import resolution)
|
||||
import_graph: ?*std.StringHashMap(std.StringHashMap(void)) = null, // module path → set of directly imported paths (used by param_impl_map visibility filter)
|
||||
current_source_file: ?[]const u8 = null, // source file of function currently being lowered
|
||||
type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId)
|
||||
current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch)
|
||||
@@ -103,6 +104,7 @@ pub const Lowering = struct {
|
||||
protocol_thunk_map: std.StringHashMap([]const FuncId) = std.StringHashMap([]const FuncId).init(std.heap.page_allocator), // "Proto\x00Type" → thunk FuncIds
|
||||
protocol_vtable_type_map: std.StringHashMap(TypeId) = std.StringHashMap(TypeId).init(std.heap.page_allocator), // protocol name → vtable struct TypeId
|
||||
protocol_vtable_global_map: std.StringHashMap(inst_mod.GlobalId) = std.StringHashMap(inst_mod.GlobalId).init(std.heap.page_allocator), // "Proto\x00Type" → vtable GlobalId
|
||||
param_impl_map: std.StringHashMap(std.ArrayList(ParamImplEntry)) = std.StringHashMap(std.ArrayList(ParamImplEntry)).init(std.heap.page_allocator), // "Proto\x00<arg_mangled>\x00<src_mangled>" → impl entries (parameterised protocols only; list lets Phase 4/5 detect cross-module overlap)
|
||||
struct_const_map: std.StringHashMap(StructConstInfo) = std.StringHashMap(StructConstInfo).init(std.heap.page_allocator), // "Struct.CONST" → value info
|
||||
module_const_map: std.StringHashMap(ModuleConstInfo) = std.StringHashMap(ModuleConstInfo).init(std.heap.page_allocator), // module-level value constants (e.g. AF_INET :s32: 2)
|
||||
foreign_name_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // sx name → C name for #foreign renames
|
||||
@@ -111,6 +113,7 @@ pub const Lowering = struct {
|
||||
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
|
||||
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
|
||||
diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations
|
||||
xx_reentrancy: std.AutoHashMap(u64, void) = std.AutoHashMap(u64, void).init(std.heap.page_allocator), // (src_ty, dst_ty) pairs currently being resolved through user-space Into; prevents infinite monomorphisation when a convert body re-enters the same xx
|
||||
|
||||
pub const ComptimeValue = union(enum) {
|
||||
int_val: i64,
|
||||
@@ -139,6 +142,17 @@ pub const Lowering = struct {
|
||||
ret_type: TypeId,
|
||||
};
|
||||
|
||||
/// One impl block for a parameterised protocol (e.g. `impl Into(Block) for Closure() -> void`).
|
||||
/// Stored in `param_impl_map` keyed by (protocol_name, target_args_mangled, source_mangled).
|
||||
/// `defining_module` enables import-scoped visibility + cross-module duplicate diagnostics.
|
||||
const ParamImplEntry = struct {
|
||||
methods: []const *const ast.FnDecl,
|
||||
source_ty: TypeId,
|
||||
target_args: []const TypeId,
|
||||
defining_module: []const u8,
|
||||
span: ast.Span,
|
||||
};
|
||||
|
||||
/// Owned copy of a generic struct template (AST pointers are copied/interned to survive imports)
|
||||
const StructTemplate = struct {
|
||||
name: []const u8,
|
||||
@@ -294,11 +308,11 @@ pub const Lowering = struct {
|
||||
.union_decl => {
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
},
|
||||
.protocol_decl => |pd| {
|
||||
self.registerProtocolDecl(&pd);
|
||||
.protocol_decl => {
|
||||
self.registerProtocolDecl(&decl.data.protocol_decl);
|
||||
},
|
||||
.impl_block => |ib| {
|
||||
self.registerImplBlock(&ib, is_imported);
|
||||
.impl_block => {
|
||||
self.registerImplBlock(&decl.data.impl_block, is_imported, decl);
|
||||
},
|
||||
.namespace_decl => |ns| {
|
||||
if (self.main_file != null) {
|
||||
@@ -431,11 +445,11 @@ pub const Lowering = struct {
|
||||
// Register plain union types in the type table
|
||||
_ = type_bridge.resolveAstType(decl, &self.module.types);
|
||||
},
|
||||
.protocol_decl => |pd| {
|
||||
self.registerProtocolDecl(&pd);
|
||||
.protocol_decl => {
|
||||
self.registerProtocolDecl(&decl.data.protocol_decl);
|
||||
},
|
||||
.impl_block => |ib| {
|
||||
self.registerImplBlock(&ib, is_imported);
|
||||
.impl_block => {
|
||||
self.registerImplBlock(&decl.data.impl_block, is_imported, decl);
|
||||
},
|
||||
.namespace_decl => |ns| {
|
||||
if (self.main_file != null) {
|
||||
@@ -450,8 +464,12 @@ pub const Lowering = struct {
|
||||
// Use self.resolveType so type aliases like `Handle :: u32;` resolve
|
||||
// to their target type (not a synthetic empty struct).
|
||||
const var_ty = self.resolveType(vd.type_annotation);
|
||||
const name_id = self.module.types.internString(vd.name);
|
||||
const init_val: ?inst_mod.ConstantValue = if (vd.value) |v| switch (v.data) {
|
||||
// Foreign globals reference a symbol defined in libSystem etc.
|
||||
// (`_NSConcreteStackBlock : *void #foreign;`). The C symbol
|
||||
// name is the optional override or the sx name itself.
|
||||
const sym_name = vd.foreign_name orelse vd.name;
|
||||
const name_id = self.module.types.internString(sym_name);
|
||||
const init_val: ?inst_mod.ConstantValue = if (vd.is_foreign) null else if (vd.value) |v| switch (v.data) {
|
||||
.undef_literal => .zeroinit,
|
||||
.int_literal => |il| .{ .int = il.value },
|
||||
.bool_literal => |bl| .{ .boolean = bl.value },
|
||||
@@ -466,6 +484,7 @@ pub const Lowering = struct {
|
||||
.ty = var_ty,
|
||||
.init_val = init_val,
|
||||
.is_const = false,
|
||||
.is_extern = vd.is_foreign,
|
||||
});
|
||||
self.global_names.put(vd.name, .{ .id = gid, .ty = var_ty }) catch {};
|
||||
},
|
||||
@@ -5976,14 +5995,19 @@ pub const Lowering = struct {
|
||||
const saved_bindings = self.type_bindings;
|
||||
const saved_defer_base = self.func_defer_base;
|
||||
const saved_block_terminated = self.block_terminated;
|
||||
const saved_target = self.target_type;
|
||||
self.func_defer_base = self.defer_stack.items.len;
|
||||
self.block_terminated = false;
|
||||
|
||||
// Install type bindings
|
||||
self.type_bindings = bindings.*;
|
||||
|
||||
// Resolve return type with type bindings active
|
||||
// Resolve return type with type bindings active. The body's tail
|
||||
// expression inherits this as its target_type so bare `.{...}`
|
||||
// literals resolve to the monomorphised return type instead of
|
||||
// whatever leaked in from the caller (e.g. caller's xx target).
|
||||
const ret_ty = self.resolveReturnType(fd);
|
||||
self.target_type = ret_ty;
|
||||
|
||||
// Build param list (substituting type params, skipping type param declarations)
|
||||
var params = std.ArrayList(Function.Param).empty;
|
||||
@@ -6060,6 +6084,7 @@ pub const Lowering = struct {
|
||||
self.scope = saved_scope;
|
||||
self.func_defer_base = saved_defer_base;
|
||||
self.block_terminated = saved_block_terminated;
|
||||
self.target_type = saved_target;
|
||||
self.builder.func = saved_func;
|
||||
self.builder.current_block = saved_block;
|
||||
self.builder.inst_counter = saved_counter;
|
||||
@@ -6435,10 +6460,74 @@ pub const Lowering = struct {
|
||||
const inner = self.mangleTypeName(v.element);
|
||||
break :blk std.fmt.allocPrint(self.alloc, "vec_{d}_{s}", .{ v.length, inner }) catch "vector";
|
||||
},
|
||||
.closure => |c| self.mangleParamList("cl", c.params, c.ret),
|
||||
.function => |f| self.mangleParamList("fn", f.params, f.ret),
|
||||
.tuple => |t| blk: {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
buf.appendSlice(self.alloc, "tu") catch break :blk "tuple";
|
||||
for (t.fields) |fid| {
|
||||
buf.append(self.alloc, '_') catch break :blk "tuple";
|
||||
buf.appendSlice(self.alloc, self.mangleTypeName(fid)) catch break :blk "tuple";
|
||||
}
|
||||
break :blk buf.items;
|
||||
},
|
||||
else => @tagName(info),
|
||||
};
|
||||
}
|
||||
|
||||
/// Collect impl entries visible from `current_source_file` — defined in
|
||||
/// the current file or in any module the current file transitively
|
||||
/// imports. Falls open (returns all entries) when the source-file
|
||||
/// context or import graph isn't wired (e.g. comptime callers).
|
||||
fn findVisibleImpls(self: *Lowering, entries: []const ParamImplEntry, out: *std.ArrayList(ParamImplEntry)) void {
|
||||
const here = self.current_source_file orelse {
|
||||
out.appendSlice(self.alloc, entries) catch {};
|
||||
return;
|
||||
};
|
||||
const graph = self.import_graph orelse {
|
||||
out.appendSlice(self.alloc, entries) catch {};
|
||||
return;
|
||||
};
|
||||
|
||||
// BFS over the import graph to compute the visible set.
|
||||
var visible = std.StringHashMap(void).init(self.alloc);
|
||||
defer visible.deinit();
|
||||
visible.put(here, {}) catch {};
|
||||
var queue = std.ArrayList([]const u8).empty;
|
||||
defer queue.deinit(self.alloc);
|
||||
queue.append(self.alloc, here) catch {};
|
||||
var head: usize = 0;
|
||||
while (head < queue.items.len) : (head += 1) {
|
||||
const node = queue.items[head];
|
||||
const direct = graph.get(node) orelse continue;
|
||||
var it = direct.iterator();
|
||||
while (it.next()) |kv| {
|
||||
const next = kv.key_ptr.*;
|
||||
if (visible.contains(next)) continue;
|
||||
visible.put(next, {}) catch {};
|
||||
queue.append(self.alloc, next) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
for (entries) |e| {
|
||||
if (visible.contains(e.defining_module)) {
|
||||
out.append(self.alloc, e) catch {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mangleParamList(self: *Lowering, prefix: []const u8, params: []const TypeId, ret: TypeId) []const u8 {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
buf.appendSlice(self.alloc, prefix) catch return prefix;
|
||||
for (params) |p| {
|
||||
buf.append(self.alloc, '_') catch return prefix;
|
||||
buf.appendSlice(self.alloc, self.mangleTypeName(p)) catch return prefix;
|
||||
}
|
||||
buf.appendSlice(self.alloc, "__") catch return prefix;
|
||||
buf.appendSlice(self.alloc, self.mangleTypeName(ret)) catch return prefix;
|
||||
return buf.items;
|
||||
}
|
||||
|
||||
/// Resolve type category names (like "int", "struct", "float") to matching TypeId tag values.
|
||||
/// Returns a list of TypeId index values that match the category.
|
||||
fn resolveTypeCategoryTags(self: *Lowering, name: []const u8) []const u64 {
|
||||
@@ -6584,6 +6673,27 @@ pub const Lowering = struct {
|
||||
if (c.callee.data == .field_access) {
|
||||
const fa = c.callee.data.field_access;
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
// Protocol-typed receiver: look up the method on the protocol decl. The
|
||||
// protocol's ProtocolMethodInfo.param_types already excludes self.
|
||||
if (self.getProtocolInfo(obj_ty)) |proto_info| {
|
||||
for (proto_info.methods) |m| {
|
||||
if (std.mem.eql(u8, m.name, fa.field)) return m.param_types;
|
||||
}
|
||||
}
|
||||
// Closure-typed struct field: `c.on(args)` lowers to call_closure on
|
||||
// the field value. Pick up the callee's param types from the closure
|
||||
// type so each arg gets the right target_type during lowering.
|
||||
if (!obj_ty.isBuiltin()) {
|
||||
const field_name_id = self.module.types.internString(fa.field);
|
||||
const struct_fields = self.getStructFields(obj_ty);
|
||||
for (struct_fields) |f| {
|
||||
if (f.name == field_name_id and !f.ty.isBuiltin()) {
|
||||
const fti = self.module.types.get(f.ty);
|
||||
if (fti == .closure) return fti.closure.params;
|
||||
if (fti == .function) return fti.function.params;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.getStructTypeName(obj_ty)) |sname| {
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch return &.{};
|
||||
// Try already-lowered functions first
|
||||
@@ -7498,6 +7608,16 @@ pub const Lowering = struct {
|
||||
/// Non-inline protocols: { ctx: *void, __vtable: *void }
|
||||
/// Also stores protocol info for dispatch and vtable struct type for vtable protocols.
|
||||
fn registerProtocolDecl(self: *Lowering, pd: *const ast.ProtocolDecl) void {
|
||||
// Parameterised protocols are compile-time-only — no vtable, no boxed
|
||||
// instance struct. Methods reference unbound type params (e.g.
|
||||
// `convert :: () -> Target`) that only get a concrete TypeId per
|
||||
// (Source, Target) pair at xx resolution time. Stash the AST so
|
||||
// `param_impl_map` lookup can resolve method signatures lazily.
|
||||
if (pd.type_params.len > 0) {
|
||||
self.protocol_ast_map.put(pd.name, pd) catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
const table = &self.module.types;
|
||||
const name_id = table.internString(pd.name);
|
||||
|
||||
@@ -7596,7 +7716,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Register an impl block: register its methods as TypeName.method in fn_ast_map.
|
||||
fn registerImplBlock(self: *Lowering, ib: *const ast.ImplBlock, is_imported: bool) void {
|
||||
fn registerImplBlock(self: *Lowering, ib: *const ast.ImplBlock, is_imported: bool, decl: *const Node) void {
|
||||
// Parameterised-protocol impl (e.g. `impl Into(Block) for Closure() -> void`):
|
||||
// record into `param_impl_map` for compile-time resolution by `lowerXX`.
|
||||
// Methods are NOT registered in fn_ast_map — they're monomorphised lazily
|
||||
// per (Source, Target) pair at the xx call site.
|
||||
if (ib.protocol_type_args.len > 0) {
|
||||
self.registerParamImpl(ib, decl);
|
||||
return;
|
||||
}
|
||||
// Collect explicitly implemented method names
|
||||
var impl_methods = std.StringHashMap(void).init(self.alloc);
|
||||
defer impl_methods.deinit();
|
||||
@@ -7625,6 +7753,80 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a parameterised-protocol impl into `param_impl_map`.
|
||||
/// Resolves the protocol's type args + the source type, mangles them, and
|
||||
/// stashes the impl's method fn_decls for later monomorphisation by
|
||||
/// `lowerXX`. Same-module duplicate impls produce a diagnostic here;
|
||||
/// cross-module duplicates are detected at the xx resolution site.
|
||||
fn registerParamImpl(self: *Lowering, ib: *const ast.ImplBlock, decl: *const Node) void {
|
||||
const table = &self.module.types;
|
||||
|
||||
// Resolve the protocol's type-arg list to concrete TypeIds.
|
||||
var arg_tys = std.ArrayList(TypeId).empty;
|
||||
for (ib.protocol_type_args) |arg_node| {
|
||||
const t = type_bridge.resolveAstType(arg_node, table);
|
||||
arg_tys.append(self.alloc, t) catch return;
|
||||
}
|
||||
|
||||
// Resolve the source type. Parser stores it on `target_type_expr` for
|
||||
// parameterised impls (back-compat `target_type` string is kept for
|
||||
// simple cases but the canonical form is the TypeExpr).
|
||||
const src_ty: TypeId = if (ib.target_type_expr) |te|
|
||||
type_bridge.resolveAstType(te, table)
|
||||
else if (ib.target_type.len > 0)
|
||||
type_bridge.resolveAstType(&.{ .span = decl.span, .data = .{ .type_expr = .{ .name = ib.target_type } } }, table)
|
||||
else
|
||||
return;
|
||||
|
||||
// Mangle into the lookup key.
|
||||
var key_buf = std.ArrayList(u8).empty;
|
||||
key_buf.appendSlice(self.alloc, ib.protocol_name) catch return;
|
||||
for (arg_tys.items) |t| {
|
||||
key_buf.append(self.alloc, 0) catch return;
|
||||
key_buf.appendSlice(self.alloc, self.mangleTypeName(t)) catch return;
|
||||
}
|
||||
key_buf.append(self.alloc, 0) catch return;
|
||||
key_buf.appendSlice(self.alloc, self.mangleTypeName(src_ty)) catch return;
|
||||
const key = key_buf.items;
|
||||
|
||||
// Collect method fn_decl pointers.
|
||||
var methods = std.ArrayList(*const ast.FnDecl).empty;
|
||||
for (ib.methods) |method_node| {
|
||||
if (method_node.data == .fn_decl) {
|
||||
methods.append(self.alloc, &method_node.data.fn_decl) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
const defining_module: []const u8 = self.current_source_file orelse "";
|
||||
const entry: ParamImplEntry = .{
|
||||
.methods = self.alloc.dupe(*const ast.FnDecl, methods.items) catch return,
|
||||
.source_ty = src_ty,
|
||||
.target_args = self.alloc.dupe(TypeId, arg_tys.items) catch return,
|
||||
.defining_module = defining_module,
|
||||
.span = decl.span,
|
||||
};
|
||||
|
||||
const gop = self.param_impl_map.getOrPut(key) catch return;
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = std.ArrayList(ParamImplEntry).empty;
|
||||
} else {
|
||||
// Same-file duplicate is an immediate error. Cross-file overlaps
|
||||
// are deferred to the xx resolution site (Phase 5) so the impl
|
||||
// surface can be richer than any one file's view.
|
||||
for (gop.value_ptr.items) |existing| {
|
||||
if (std.mem.eql(u8, existing.defining_module, defining_module)) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, decl.span, "duplicate impl '{s}' for source '{s}' in {s}", .{
|
||||
ib.protocol_name, self.mangleTypeName(src_ty), defining_module,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
gop.value_ptr.append(self.alloc, entry) catch return;
|
||||
}
|
||||
|
||||
/// Synthesize a fn_decl from a protocol default method for a concrete type.
|
||||
fn synthesizeDefaultMethod(self: *Lowering, method: ast.ProtocolMethodDecl, target_type: []const u8) *const ast.FnDecl {
|
||||
// Build parameter list: self: *TargetType, then the protocol method params
|
||||
@@ -7957,15 +8159,15 @@ pub const Lowering = struct {
|
||||
const raw_result = self.builder.emit(.{ .call_indirect = .{ .callee = fn_ptr, .args = owned } }, mi.ret_type);
|
||||
|
||||
// If protocol method returns *void (Self) and the caller expects a value type,
|
||||
// unbox: load the concrete value from the returned pointer
|
||||
if (mi.ret_type != .void) {
|
||||
const ret_info = self.module.types.get(mi.ret_type);
|
||||
if (ret_info == .pointer) {
|
||||
if (self.target_type) |target| {
|
||||
const target_info = self.module.types.get(target);
|
||||
if (target_info != .pointer) {
|
||||
return self.builder.load(raw_result, target);
|
||||
}
|
||||
// unbox: load the concrete value from the returned pointer. Real pointer
|
||||
// returns (declared `-> *T` for non-Self T) are NOT auto-loaded — the
|
||||
// pointee may be a single byte and reading `sizeof(target)` past it
|
||||
// segfaults. Self is encoded as `*void`, so test against that exact type.
|
||||
if (mi.ret_type == void_ptr) {
|
||||
if (self.target_type) |target| {
|
||||
const target_info = self.module.types.get(target);
|
||||
if (target_info != .pointer) {
|
||||
return self.builder.load(raw_result, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8334,7 +8536,13 @@ pub const Lowering = struct {
|
||||
/// - int → int: widen/narrow
|
||||
/// - int ↔ float: int_to_float/float_to_int
|
||||
fn lowerXX(self: *Lowering, operand: Ref, operand_node: *const Node) Ref {
|
||||
const src_ty = self.inferExprType(operand_node);
|
||||
// Use the operand's *actual* lowered Ref type rather than reaching
|
||||
// back through inferExprType — the latter doesn't cover every
|
||||
// expression shape (notably lambdas), and a wrong src_ty here can
|
||||
// route the cast through coerceToType (e.g. a bogus s64→ptr bitcast)
|
||||
// and silently skip the user-space Into fallback.
|
||||
const src_ty = self.builder.getRefType(operand);
|
||||
const target_explicit = self.target_type != null;
|
||||
const dst_ty = self.target_type orelse .s64;
|
||||
|
||||
// Any → concrete type: unbox
|
||||
@@ -8377,7 +8585,140 @@ pub const Lowering = struct {
|
||||
return self.buildProtocolErasure(operand, operand_node, src_ty, dst_ty);
|
||||
}
|
||||
|
||||
return self.coerceToType(operand, src_ty, dst_ty);
|
||||
const result = self.coerceToType(operand, src_ty, dst_ty);
|
||||
|
||||
// User-space fallback via `impl Into(Target) for Source`. Only fires
|
||||
// when the target was explicitly named (not the .s64 default), src and
|
||||
// dst differ, and the built-in ladder made no progress. Built-ins
|
||||
// always win.
|
||||
if (target_explicit and src_ty != dst_ty and result == operand) {
|
||||
if (self.tryUserConversion(operand, operand_node, src_ty, dst_ty)) |converted| {
|
||||
return converted;
|
||||
}
|
||||
// Pointer-target fallback: `xx <expr>` whose surrounding context
|
||||
// expects `*T` (a fn arg slot, a var typed as a pointer-to-aggregate)
|
||||
// can be satisfied by `impl Into(T) for src` plus an implicit
|
||||
// alloca+store on the result. Lets users write
|
||||
// `fn(xx () => { ... })` instead of materialising a named Block local
|
||||
// just to take its address.
|
||||
if (!dst_ty.isBuiltin()) {
|
||||
const dst_info = self.module.types.get(dst_ty);
|
||||
if (dst_info == .pointer) {
|
||||
const pointee = dst_info.pointer.pointee;
|
||||
if (pointee != src_ty) {
|
||||
if (self.tryUserConversion(operand, operand_node, src_ty, pointee)) |converted| {
|
||||
const slot = self.builder.alloca(pointee);
|
||||
self.builder.store(slot, converted);
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Look up `Into(dst_ty)` impl for `src_ty` and, if found, monomorphise
|
||||
/// the impl's `convert` method and emit a direct call. Returns null when
|
||||
/// no impl matches (caller falls back to the built-in result, which is
|
||||
/// the unchanged operand — Phase 3 emits no diagnostic for v0).
|
||||
fn tryUserConversion(self: *Lowering, operand: Ref, operand_node: *const Node, src_ty: TypeId, dst_ty: TypeId) ?Ref {
|
||||
// Reentrancy guard — pack (src, dst) into a u64.
|
||||
const guard_key: u64 = (@as(u64, src_ty.index()) << 32) | @as(u64, dst_ty.index());
|
||||
if (self.xx_reentrancy.contains(guard_key)) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, operand_node.span, "recursive xx conversion from '{s}' to '{s}'", .{
|
||||
self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty),
|
||||
});
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
|
||||
// Build lookup key: "Into\x00<dst_mangled>\x00<src_mangled>".
|
||||
// Hardcoded to the "Into" protocol for v1. Generalising to other
|
||||
// parameterised protocols would walk protocol_decl_map looking for
|
||||
// protocols that take a single type-param and have a `convert` method.
|
||||
const proto_name = "Into";
|
||||
const pd = self.protocol_ast_map.get(proto_name) orelse return null;
|
||||
if (pd.type_params.len != 1) return null;
|
||||
|
||||
var key_buf = std.ArrayList(u8).empty;
|
||||
key_buf.appendSlice(self.alloc, proto_name) catch return null;
|
||||
key_buf.append(self.alloc, 0) catch return null;
|
||||
key_buf.appendSlice(self.alloc, self.mangleTypeName(dst_ty)) catch return null;
|
||||
key_buf.append(self.alloc, 0) catch return null;
|
||||
key_buf.appendSlice(self.alloc, self.mangleTypeName(src_ty)) catch return null;
|
||||
const key = key_buf.items;
|
||||
|
||||
const entries = self.param_impl_map.get(key) orelse return null;
|
||||
if (entries.items.len == 0) return null;
|
||||
|
||||
// Filter by import visibility: only impls in modules that the current
|
||||
// file transitively imports (or the current file itself) are reachable.
|
||||
// Falls open when import_graph isn't wired (e.g. comptime callers).
|
||||
var visible_impls = std.ArrayList(ParamImplEntry).empty;
|
||||
defer visible_impls.deinit(self.alloc);
|
||||
self.findVisibleImpls(entries.items, &visible_impls);
|
||||
|
||||
if (visible_impls.items.len == 0) {
|
||||
if (self.diagnostics) |diags| {
|
||||
const saved = diags.current_source_file;
|
||||
diags.current_source_file = operand_node.source_file orelse self.current_source_file;
|
||||
defer diags.current_source_file = saved;
|
||||
diags.addFmt(.err, operand_node.span, "no visible xx conversion from '{s}' to '{s}' — impl exists in another module but is not imported", .{
|
||||
self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty),
|
||||
});
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
if (visible_impls.items.len > 1) {
|
||||
if (self.diagnostics) |diags| {
|
||||
const saved = diags.current_source_file;
|
||||
diags.current_source_file = operand_node.source_file orelse self.current_source_file;
|
||||
defer diags.current_source_file = saved;
|
||||
diags.addFmt(.err, operand_node.span, "duplicate xx conversion from '{s}' to '{s}': impls in {s} and {s}", .{
|
||||
self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty),
|
||||
visible_impls.items[0].defining_module, visible_impls.items[1].defining_module,
|
||||
});
|
||||
}
|
||||
return operand;
|
||||
}
|
||||
const entry = visible_impls.items[0];
|
||||
|
||||
// Find the `convert` method on this impl.
|
||||
var convert_fd: ?*const ast.FnDecl = null;
|
||||
for (entry.methods) |m| {
|
||||
if (std.mem.eql(u8, m.name, "convert")) {
|
||||
convert_fd = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const fd = convert_fd orelse return null;
|
||||
|
||||
// Bind Target → dst_ty.
|
||||
var bindings = std.StringHashMap(TypeId).init(self.alloc);
|
||||
defer bindings.deinit();
|
||||
bindings.put(pd.type_params[0].name, dst_ty) catch return null;
|
||||
|
||||
// Mangled name: "<src>.convert__<dst>".
|
||||
const mangled = std.fmt.allocPrint(self.alloc, "{s}.convert__{s}", .{
|
||||
self.mangleTypeName(src_ty), self.mangleTypeName(dst_ty),
|
||||
}) catch return null;
|
||||
|
||||
self.xx_reentrancy.put(guard_key, {}) catch {};
|
||||
defer _ = self.xx_reentrancy.remove(guard_key);
|
||||
|
||||
if (!self.lowered_functions.contains(mangled)) {
|
||||
self.monomorphizeFunction(fd, mangled, &bindings);
|
||||
}
|
||||
|
||||
const fid = self.resolveFuncByName(mangled) orelse return null;
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
var args = [_]Ref{operand};
|
||||
self.coerceCallArgs(args[0..], params);
|
||||
return self.builder.call(fid, args[0..], ret_ty);
|
||||
}
|
||||
|
||||
/// Build a protocol value from a concrete value via xx conversion.
|
||||
|
||||
Reference in New Issue
Block a user