ios + ir cleanup
- ios: --target ios/ios-sim shorthands, iOS SDK auto-discovery,
#framework directive + BuildOptions.add_framework hook,
.app bundle + Info.plist + codesign (ad-hoc and real),
--codesign-identity/--provisioning-profile/--entitlements flags,
modules/std/{objc,uikit}.sx, dynamic class registration,
typed objc_msgSend cast pattern, UIApplicationMain handoff,
UIWindow scene attach. Runs on iPhone hardware.
- ir: silent .s64 defaults → loud diagnostics,
resolveReturnType infers from body, sub-byte int sizes match LLVM,
tuple type interning includes names, compile errors exit 1
- issue-NNNN convention: resolved bugs rename to focused features
- 50 regression tests passing
This commit is contained in:
@@ -9,11 +9,13 @@ const Interpreter = interp_mod.Interpreter;
|
||||
|
||||
pub const BuildConfig = struct {
|
||||
link_flags: std.ArrayList([]const u8) = .empty,
|
||||
frameworks: std.ArrayList([]const u8) = .empty,
|
||||
output_path: ?[]const u8 = null,
|
||||
wasm_shell_path: ?[]const u8 = null,
|
||||
|
||||
pub fn deinit(self: *BuildConfig, alloc: Allocator) void {
|
||||
self.link_flags.deinit(alloc);
|
||||
self.frameworks.deinit(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,6 +54,7 @@ pub const Registry = struct {
|
||||
pub fn registerDefaults(self: *Registry) void {
|
||||
self.hooks.put("build_options", &hookBuildOptions) catch {};
|
||||
self.hooks.put("BuildOptions.add_link_flag", &hookAddLinkFlag) catch {};
|
||||
self.hooks.put("BuildOptions.add_framework", &hookAddFramework) catch {};
|
||||
self.hooks.put("BuildOptions.set_output_path", &hookSetOutputPath) catch {};
|
||||
self.hooks.put("BuildOptions.set_wasm_shell", &hookSetWasmShell) catch {};
|
||||
}
|
||||
@@ -87,6 +90,21 @@ fn hookAddLinkFlag(
|
||||
return .void_val;
|
||||
}
|
||||
|
||||
fn hookAddFramework(
|
||||
interp: *const Interpreter,
|
||||
args: []const Value,
|
||||
bc: *BuildConfig,
|
||||
alloc: Allocator,
|
||||
) HookError!Value {
|
||||
// args: [self (BuildOptions value), framework_name]
|
||||
if (args.len < 2) return .void_val;
|
||||
const str_val = args[1];
|
||||
if (str_val.asString(interp)) |s| {
|
||||
bc.frameworks.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
|
||||
}
|
||||
return .void_val;
|
||||
}
|
||||
|
||||
fn hookSetOutputPath(
|
||||
interp: *const Interpreter,
|
||||
args: []const Value,
|
||||
|
||||
134
src/ir/lower.zig
134
src/ir/lower.zig
@@ -201,6 +201,8 @@ pub const Lowering = struct {
|
||||
self.findVariantIndex(os_info.@"enum".variants, "windows")
|
||||
else if (tc.isLinux())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "linux")
|
||||
else if (tc.isIOS())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "ios")
|
||||
else if (tc.isMacOS())
|
||||
self.findVariantIndex(os_info.@"enum".variants, "macos")
|
||||
else
|
||||
@@ -1631,7 +1633,7 @@ pub const Lowering = struct {
|
||||
.break_expr => self.lowerBreak(),
|
||||
.continue_expr => self.lowerContinue(),
|
||||
.call => |c| self.lowerCall(&c),
|
||||
.field_access => |fa| self.lowerFieldAccess(&fa),
|
||||
.field_access => |fa| self.lowerFieldAccess(&fa, node.span),
|
||||
.struct_literal => |sl| self.lowerStructLiteral(&sl),
|
||||
.array_literal => |al| self.lowerArrayLiteral(&al),
|
||||
.index_expr => |ie| self.lowerIndexExpr(&ie),
|
||||
@@ -2486,6 +2488,10 @@ pub const Lowering = struct {
|
||||
break :blk @intCast(vi);
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
const ty_name = self.formatTypeName(subject_ty);
|
||||
diags.addFmt(.err, pat.span, "no variant '{s}' on type '{s}'", .{ pat_name, ty_name });
|
||||
}
|
||||
} else if (ty_info == .@"enum") {
|
||||
for (ty_info.@"enum".variants, 0..) |v, vi| {
|
||||
const vname = self.module.types.strings.get(v);
|
||||
@@ -2496,6 +2502,10 @@ pub const Lowering = struct {
|
||||
break :blk @intCast(vi);
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
const ty_name = self.formatTypeName(subject_ty);
|
||||
diags.addFmt(.err, pat.span, "no variant '{s}' on type '{s}'", .{ pat_name, ty_name });
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk @intCast(i);
|
||||
@@ -3017,7 +3027,7 @@ pub const Lowering = struct {
|
||||
return .s64;
|
||||
}
|
||||
|
||||
fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess) Ref {
|
||||
fn lowerFieldAccess(self: *Lowering, fa: *const ast.FieldAccess, span: ast.Span) Ref {
|
||||
// Check for struct constant access: Struct.CONST
|
||||
if (fa.object.data == .identifier) {
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch fa.field;
|
||||
@@ -3061,10 +3071,10 @@ pub const Lowering = struct {
|
||||
|
||||
// Optional chaining: p?.field
|
||||
if (fa.is_optional) {
|
||||
return self.lowerOptionalChain(obj, fa);
|
||||
return self.lowerOptionalChain(obj, fa, span);
|
||||
}
|
||||
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field);
|
||||
return self.lowerFieldAccessOnType(obj, obj_ty, fa.field, span);
|
||||
}
|
||||
|
||||
/// Lower a struct-level constant value (e.g., Phys.GRAVITY).
|
||||
@@ -3082,7 +3092,7 @@ pub const Lowering = struct {
|
||||
/// Lower optional chaining: `p?.field` where p is ?T
|
||||
/// Produces ?FieldType: some(unwrap(p).field) if p has value, else null
|
||||
/// If FieldType is already optional (?U), flattens to ?U (no double wrapping)
|
||||
fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess) Ref {
|
||||
fn lowerOptionalChain(self: *Lowering, obj: Ref, fa: *const ast.FieldAccess, span: ast.Span) Ref {
|
||||
const obj_ty = self.inferExprType(fa.object);
|
||||
// Get the inner (non-optional) type
|
||||
const inner_ty = if (!obj_ty.isBuiltin()) blk: {
|
||||
@@ -3109,7 +3119,7 @@ pub const Lowering = struct {
|
||||
// Some: unwrap, access field (already ?FieldType if flattened, else wrap)
|
||||
self.builder.switchToBlock(some_bb);
|
||||
const unwrapped = self.builder.emit(.{ .optional_unwrap = .{ .operand = obj } }, inner_ty);
|
||||
const field_val = self.lowerFieldAccessOnType(unwrapped, inner_ty, fa.field);
|
||||
const field_val = self.lowerFieldAccessOnType(unwrapped, inner_ty, fa.field, span);
|
||||
const some_result = if (field_already_optional) field_val else self.builder.emit(.{ .optional_wrap = .{ .operand = field_val } }, result_ty);
|
||||
self.builder.br(merge_bb, &.{some_result});
|
||||
|
||||
@@ -3124,7 +3134,7 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
/// Field access on a known type (shared by regular field access and optional chaining)
|
||||
fn lowerFieldAccessOnType(self: *Lowering, obj: Ref, obj_ty: TypeId, field: []const u8) Ref {
|
||||
fn lowerFieldAccessOnType(self: *Lowering, obj: Ref, obj_ty: TypeId, field: []const u8, span: ast.Span) Ref {
|
||||
const field_name_id = self.module.types.internString(field);
|
||||
|
||||
// Check if it's a union type
|
||||
@@ -3221,28 +3231,24 @@ pub const Lowering = struct {
|
||||
}
|
||||
// Try numeric index (e.g., "0", "1")
|
||||
const idx = std.fmt.parseInt(u32, field, 10) catch {
|
||||
return self.builder.structGet(obj, 0, if (tuple.fields.len > 0) tuple.fields[0] else .s64);
|
||||
return self.emitFieldError(obj_ty, field, span);
|
||||
};
|
||||
if (idx < tuple.fields.len) {
|
||||
return self.builder.structGet(obj, idx, tuple.fields[idx]);
|
||||
}
|
||||
return self.builder.structGet(obj, 0, if (tuple.fields.len > 0) tuple.fields[0] else .s64);
|
||||
return self.emitFieldError(obj_ty, field, span);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve struct field index and type
|
||||
const struct_fields = self.getStructFields(obj_ty);
|
||||
var field_idx: u32 = 0;
|
||||
var field_ty: TypeId = .s64;
|
||||
for (struct_fields, 0..) |f, i| {
|
||||
if (f.name == field_name_id) {
|
||||
field_idx = @intCast(i);
|
||||
field_ty = f.ty;
|
||||
break;
|
||||
return self.builder.structGet(obj, @intCast(i), f.ty);
|
||||
}
|
||||
}
|
||||
|
||||
return self.builder.structGet(obj, field_idx, field_ty);
|
||||
return self.emitFieldError(obj_ty, field, span);
|
||||
}
|
||||
|
||||
fn lowerEnumLiteral(self: *Lowering, el: *const ast.EnumLiteral) Ref {
|
||||
@@ -4210,34 +4216,44 @@ pub const Lowering = struct {
|
||||
return self.emitError(fa.field, c.callee.span);
|
||||
},
|
||||
.enum_literal => |el| {
|
||||
const target = self.target_type orelse .s64;
|
||||
const target_opt: ?TypeId = self.target_type;
|
||||
|
||||
// Check if target type is a struct — dispatch as static method call
|
||||
if (!target.isBuiltin()) {
|
||||
const target_info = self.module.types.get(target);
|
||||
if (target_info == .@"struct") {
|
||||
// Try to resolve StructName.method
|
||||
const struct_name = self.module.types.typeName(target);
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, el.name }) catch el.name;
|
||||
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||
if (fd.type_params.len > 0) {
|
||||
return self.lowerGenericCall(fd, qualified, c, args.items);
|
||||
// Try struct-method dispatch first: .{...}.method() where target is a struct
|
||||
if (target_opt) |tgt| {
|
||||
if (!tgt.isBuiltin()) {
|
||||
const target_info = self.module.types.get(tgt);
|
||||
if (target_info == .@"struct") {
|
||||
const struct_name = self.module.types.typeName(tgt);
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, el.name }) catch el.name;
|
||||
if (self.fn_ast_map.get(qualified)) |fd| {
|
||||
if (fd.type_params.len > 0) {
|
||||
return self.lowerGenericCall(fd, qualified, c, args.items);
|
||||
}
|
||||
if (!self.lowered_functions.contains(qualified)) {
|
||||
self.lazyLowerFunction(qualified);
|
||||
}
|
||||
}
|
||||
if (!self.lowered_functions.contains(qualified)) {
|
||||
self.lazyLowerFunction(qualified);
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
self.coerceCallArgs(args.items, params);
|
||||
return self.builder.call(fid, args.items, ret_ty);
|
||||
}
|
||||
}
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
const func = &self.module.functions.items[@intFromEnum(fid)];
|
||||
const ret_ty = func.ret;
|
||||
const params = func.params;
|
||||
self.coerceCallArgs(args.items, params);
|
||||
return self.builder.call(fid, args.items, ret_ty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .Variant(payload) — tagged enum construction
|
||||
// .Variant(payload) — tagged enum construction. Requires target to be a tagged union.
|
||||
const target = blk: {
|
||||
if (target_opt) |tgt| {
|
||||
if (!tgt.isBuiltin() and self.module.types.get(tgt) == .tagged_union) break :blk tgt;
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, c.callee.span, "cannot infer enum type for '.{s}' \u{2014} use an explicit type or assign to a typed variable", .{el.name});
|
||||
}
|
||||
return self.emitPlaceholder(el.name);
|
||||
};
|
||||
const tag = self.resolveVariantIndex(target, el.name);
|
||||
var payload = if (args.items.len > 0) args.items[0] else Ref.none;
|
||||
// Coerce payload to match the field type
|
||||
@@ -6723,9 +6739,44 @@ pub const Lowering = struct {
|
||||
if (fd.is_arrow) {
|
||||
return self.inferExprType(fd.body);
|
||||
}
|
||||
// No annotation, not arrow: an explicit `return <value>` statement
|
||||
// wins. Otherwise default to void — the body's tail expression is
|
||||
// a side-effect statement, not an implicit return.
|
||||
if (self.findReturnValueType(fd.body)) |ty| return ty;
|
||||
return .void;
|
||||
}
|
||||
|
||||
/// Walk a function body and return the type of the first `return <value>;`
|
||||
/// statement encountered. Does not descend into nested function or lambda
|
||||
/// declarations (those have their own return types).
|
||||
fn findReturnValueType(self: *Lowering, node: *const Node) ?TypeId {
|
||||
return switch (node.data) {
|
||||
.return_stmt => |rs| if (rs.value) |v| self.inferExprType(v) else null,
|
||||
.block => |blk| blk: {
|
||||
for (blk.stmts) |s| {
|
||||
if (self.findReturnValueType(s)) |t| break :blk t;
|
||||
}
|
||||
break :blk null;
|
||||
},
|
||||
.if_expr => |ie| blk: {
|
||||
if (self.findReturnValueType(ie.then_branch)) |t| break :blk t;
|
||||
if (ie.else_branch) |eb| {
|
||||
if (self.findReturnValueType(eb)) |t| break :blk t;
|
||||
}
|
||||
break :blk null;
|
||||
},
|
||||
.while_expr => |we| self.findReturnValueType(we.body),
|
||||
.for_expr => |fe| self.findReturnValueType(fe.body),
|
||||
.match_expr => |me| blk: {
|
||||
for (me.arms) |arm| {
|
||||
if (self.findReturnValueType(arm.body)) |t| break :blk t;
|
||||
}
|
||||
break :blk null;
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
fn resolveParamType(self: *Lowering, p: *const ast.Param) TypeId {
|
||||
const elem_ty = self.resolveTypeWithBindings(p.type_expr);
|
||||
if (p.is_variadic) {
|
||||
@@ -8420,6 +8471,9 @@ pub const Lowering = struct {
|
||||
if (ty.isBuiltin()) return self.builder.constInt(0, ty);
|
||||
const info = self.module.types.get(ty);
|
||||
return switch (info) {
|
||||
// Arbitrary-width integer types (u1, u2, s4, ...) interned as
|
||||
// `.signed`/`.unsigned` variants — fall through `isBuiltin()`.
|
||||
.signed, .unsigned => self.builder.constInt(0, ty),
|
||||
.pointer, .tuple, .optional => self.builder.constNull(ty),
|
||||
.@"struct", .array, .slice, .many_pointer => self.builder.constNull(ty),
|
||||
else => self.builder.constUndef(ty),
|
||||
@@ -8523,6 +8577,14 @@ pub const Lowering = struct {
|
||||
return self.emitPlaceholder(name);
|
||||
}
|
||||
|
||||
fn emitFieldError(self: *Lowering, obj_ty: TypeId, field: []const u8, span: ast.Span) Ref {
|
||||
if (self.diagnostics) |diags| {
|
||||
const ty_name = self.formatTypeName(obj_ty);
|
||||
diags.addFmt(.err, span, "field '{s}' not found on type '{s}'", .{ field, ty_name });
|
||||
}
|
||||
return self.emitPlaceholder(field);
|
||||
}
|
||||
|
||||
/// Insert a conversion if src_ty and dst_ty differ.
|
||||
/// Handles int widening/narrowing, float widening/narrowing, and int↔float.
|
||||
fn coerceToType(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId) Ref {
|
||||
|
||||
@@ -407,6 +407,15 @@ pub const TypeTable = struct {
|
||||
/// Compute the ABI size in bytes for a type, matching LLVM's struct layout rules.
|
||||
/// This is the authoritative size computation used for closure env sizing and
|
||||
/// verified against LLVMABISizeOfType.
|
||||
fn intAbiBytes(w: u16) usize {
|
||||
// LLVM ABI size for iN: round w up to the next power of 2, then /8.
|
||||
// Sub-byte widths (i1, i2, ..., i7) are 1 byte.
|
||||
if (w <= 8) return 1;
|
||||
if (w <= 16) return 2;
|
||||
if (w <= 32) return 4;
|
||||
return 8;
|
||||
}
|
||||
|
||||
pub fn typeSizeBytes(self: *const TypeTable, ty: TypeId) usize {
|
||||
const ptr_size: usize = self.pointer_size;
|
||||
if (ty == .void) return 0;
|
||||
@@ -494,6 +503,10 @@ pub const TypeTable = struct {
|
||||
if (e.backing_type) |bt| return self.typeSizeBytes(bt);
|
||||
return 8;
|
||||
},
|
||||
// LLVM rounds arbitrary-width integers up to the next power-of-2
|
||||
// width before computing ABI size (i12 → 2 bytes, i24 → 4 bytes).
|
||||
.signed => |w| intAbiBytes(w),
|
||||
.unsigned => |w| intAbiBytes(w),
|
||||
else => 8,
|
||||
};
|
||||
}
|
||||
@@ -544,6 +557,8 @@ pub const TypeTable = struct {
|
||||
}
|
||||
break :blk max_a;
|
||||
},
|
||||
.signed => |w| intAbiBytes(w),
|
||||
.unsigned => |w| intAbiBytes(w),
|
||||
else => 8,
|
||||
};
|
||||
}
|
||||
@@ -650,6 +665,7 @@ fn hashTypeInfo(h: *std.hash.Wyhash, info: TypeInfo) void {
|
||||
.protocol => |p| h.update(std.mem.asBytes(&p.name)),
|
||||
.tuple => |t| {
|
||||
for (t.fields) |f| h.update(std.mem.asBytes(&f));
|
||||
if (t.names) |ns| for (ns) |n| h.update(std.mem.asBytes(&n));
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -697,6 +713,12 @@ fn typeInfoEql(a: TypeInfo, b: TypeInfo) bool {
|
||||
for (t.fields, u.fields) |tf, uf| {
|
||||
if (tf != uf) return false;
|
||||
}
|
||||
if ((t.names == null) != (u.names == null)) return false;
|
||||
if (t.names) |tn| {
|
||||
const un = u.names.?;
|
||||
if (tn.len != un.len) return false;
|
||||
for (tn, un) |tna, una| if (tna != una) return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user