From ca20d0ea26bba603229377f4843eb8772aeb749d Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 28 Jan 2021 19:40:53 +0200 Subject: [PATCH 1/8] stage2 cbe: pointer like optionals --- src/codegen/c.zig | 81 ++++++++++++++++++++++++++++++++++++++++++++- test/stage2/cbe.zig | 15 +++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1a323441d9..ac3ddca990 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -140,7 +140,7 @@ pub const DeclGen = struct { return writer.print("{d}", .{val.toUnsignedInt()}); }, .Pointer => switch (val.tag()) { - .undef, .zero => try writer.writeAll("0"), + .null_value, .zero => try writer.writeAll("NULL"), .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; @@ -201,6 +201,20 @@ pub const DeclGen = struct { } }, .Bool => return writer.print("{}", .{val.toBool()}), + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&opt_buf); + if (t.isPtrLikeOptional()) { + return dg.renderValue(writer, child_type, val); + } + if (val.tag() == .null_value) { + try writer.writeAll("{ .is_null = true }"); + } else { + try writer.writeAll("{ .is_null = false, .payload = "); + try dg.renderValue(writer, child_type, val); + try writer.writeAll(" }"); + } + }, else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -299,6 +313,14 @@ pub const DeclGen = struct { try dg.renderType(w, t.elemType()); try w.writeAll(" *"); }, + .Optional => { + var opt_buf: Type.Payload.ElemType = undefined; + const child_type = t.optionalChild(&opt_buf); + if (t.isPtrLikeOptional()) { + return dg.renderType(w, child_type); + } + return dg.fail(dg.decl.src(), "TODO: C backend: more optional types", .{}); + }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ @tagName(e), @@ -429,6 +451,13 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "), .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), .not => try genUnOp(o, inst.castTag(.not).?, "!"), + .is_null => try genIsNull(o, inst.castTag(.is_null).?), + .is_non_null => try genIsNull(o, inst.castTag(.is_non_null).?), + .is_null_ptr => try genIsNull(o, inst.castTag(.is_null_ptr).?), + .is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?), + .wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?), + .optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?), + .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -802,6 +831,56 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); } +fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr; + const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else ""; + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(Type.initTag(.bool), .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + + if (inst.operand.ty.isPtrLikeOptional()) { + // operand is a regular pointer, test `operand !=/== NULL` + const operator = if (invert_logic) "!=" else "=="; + try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator }); + } else { + const operator = if (invert_logic) "!=" else "=="; + try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator }); + } + return local; +} + +fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + if (opt_ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + return operand; + } + + const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer) + inst.operand.ty.elemType() + else + inst.operand.ty; + + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: genOptionalPayload non ptr-like optionals", .{}); +} + +fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + if (inst.base.ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + return operand; + } + + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: genWrapOptional non ptr-like optionals", .{}); +} + fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 873f0e1198..b22ef44148 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -244,6 +244,21 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + { + var case = ctx.exeFromCompiledC("optionals", .{}); + + // Simple while loop + case.addCompareOutput( + \\export fn main() c_int { + \\ var count: c_int = 0; + \\ var opt_ptr: ?*c_int = &count; + \\ while (opt_ptr) |_| : (count += 1) { + \\ if (count == 4) opt_ptr = null; + \\ } + \\ return count - 5; + \\} + , ""); + } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { \\ unreachable; From c22f010fdd2c5ead80cbdc902738a9d023322436 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 28 Jan 2021 20:46:15 +0200 Subject: [PATCH 2/8] stage2 cbe: regular optional types --- src/codegen/c.zig | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ac3ddca990..642166092e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -207,10 +207,12 @@ pub const DeclGen = struct { if (t.isPtrLikeOptional()) { return dg.renderValue(writer, child_type, val); } + try writer.writeByte('('); + try dg.renderType(writer, t); if (val.tag() == .null_value) { - try writer.writeAll("{ .is_null = true }"); + try writer.writeAll("){ .is_null = true }"); } else { - try writer.writeAll("{ .is_null = false, .payload = "); + try writer.writeAll("){ .is_null = false, .payload = "); try dg.renderValue(writer, child_type, val); try writer.writeAll(" }"); } @@ -319,7 +321,11 @@ pub const DeclGen = struct { if (t.isPtrLikeOptional()) { return dg.renderType(w, child_type); } - return dg.fail(dg.decl.src(), "TODO: C backend: more optional types", .{}); + + // TODO this needs to be typedeffed since different structs are different types. + try w.writeAll("struct { "); + try dg.renderType(w, child_type); + try w.writeAll(" payload; bool is_null; }"); }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ @@ -834,6 +840,7 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue { const writer = o.writer(); const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr; + const operator = if (invert_logic) "!=" else "=="; const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else ""; const operand = try o.resolveInst(inst.operand); @@ -843,10 +850,8 @@ fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue { if (inst.operand.ty.isPtrLikeOptional()) { // operand is a regular pointer, test `operand !=/== NULL` - const operator = if (invert_logic) "!=" else "=="; try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator }); } else { - const operator = if (invert_logic) "!=" else "=="; try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator }); } return local; @@ -856,17 +861,26 @@ fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue { const writer = o.writer(); const operand = try o.resolveInst(inst.operand); - if (opt_ty.isPtrLikeOptional()) { - // the operand is just a regular pointer, no need to do anything special. - return operand; - } - const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer) inst.operand.ty.elemType() else inst.operand.ty; - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: genOptionalPayload non ptr-like optionals", .{}); + if (opt_ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + // *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C + return operand; + } + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else ""; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {s}(", .{maybe_addrof}); + try o.writeCValue(writer, operand); + + try writer.print("){s}payload;\n", .{maybe_deref}); + return local; } fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { @@ -878,7 +892,12 @@ fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { return operand; } - return o.dg.fail(o.dg.decl.src(), "TODO: C backend: genWrapOptional non ptr-like optionals", .{}); + // .wrap_optional is used to convert non-optionals into optionals so it can never be null. + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .is_null = false, .payload ="); + try o.writeCValue(writer, operand); + try writer.writeAll("};\n"); + return local; } fn IndentWriter(comptime UnderlyingWriter: type) type { From cfc19eace71c92ecd7e138db6d961271a1b6c126 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 29 Jan 2021 18:20:35 +0200 Subject: [PATCH 3/8] stage2 cbe: errors --- src/codegen/c.zig | 29 +++++++++++++++++++++++++++++ src/link/C.zig | 13 +++++++++++++ src/type.zig | 12 ++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 642166092e..fe87a5994a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -217,6 +217,11 @@ pub const DeclGen = struct { try writer.writeAll(" }"); } }, + .ErrorSet => { + const payload = val.castTag(.@"error").?; + // error values will be #defined at the top of the file + return writer.print("zig_error_{s}", .{payload.data.name}); + }, else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -327,6 +332,16 @@ pub const DeclGen = struct { try dg.renderType(w, child_type); try w.writeAll(" payload; bool is_null; }"); }, + .ErrorSet => { + comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2); + try w.writeAll("uint16_t"); + }, + .ErrorUnion => { + // TODO this needs to be typedeffed since different structs are different types. + try w.writeAll("struct { "); + try dg.renderType(w, t.errorUnionChild()); + try w.writeAll(" payload; uint16_t error; }"); + }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ @tagName(e), @@ -464,6 +479,8 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?), .optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?), .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload).?), + .is_err => try genIsErr(o, inst.castTag(.is_err).?), + .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -900,6 +917,18 @@ fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { return local; } +fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const maybe_deref = if (inst.base.tag == .is_err_ptr) "[0]" else ""; + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(Type.initTag(.bool), .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + try writer.print("){s}.error != 0;\n", .{maybe_deref}); + return local; +} + fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); diff --git a/src/link/C.zig b/src/link/C.zig index 8fb3637cbe..1860fdb051 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -150,6 +150,19 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { .iov_len = zig_h.len, }); + var error_defs_buf = std.ArrayList(u8).init(comp.gpa); + defer error_defs_buf.deinit(); + + var it = module.global_error_set.iterator(); + while (it.next()) |entry| { + try error_defs_buf.writer().print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value }); + } + try error_defs_buf.writer().writeByte('\n'); + all_buffers.appendAssumeCapacity(.{ + .iov_base = error_defs_buf.items.ptr, + .iov_len = error_defs_buf.items.len, + }); + var fn_count: usize = 0; // Forward decls and non-functions first. diff --git a/src/type.zig b/src/type.zig index bb8dcea390..50d55a0c54 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1813,6 +1813,18 @@ pub const Type = extern union { } } + /// Asserts that the type is an error union. + pub fn errorUnionChild(self: Type) Type { + return switch (self.tag()) { + .anyerror_void_error_union => Type.initTag(.anyerror), + .error_union => { + const payload = self.castTag(.error_union).?; + return payload.data.payload; + }, + else => unreachable, + }; + } + /// Asserts the type is an array or vector. pub fn arrayLen(self: Type) u64 { return switch (self.tag()) { From 0a7be71bc2e58a5375ceed0b1b9850bd33717a0b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sat, 30 Jan 2021 14:56:36 +0200 Subject: [PATCH 4/8] stage2 cbe: non pointer optionals --- lib/std/hash_map.zig | 45 +++++++++++++------------ src/Compilation.zig | 2 ++ src/codegen/c.zig | 78 +++++++++++++++++++++++++++++++++++++++----- src/link/C.zig | 69 ++++++++++++++++++++++++++++++--------- src/test.zig | 3 +- test/stage2/cbe.zig | 12 +++++++ 6 files changed, 163 insertions(+), 46 deletions(-) diff --git a/lib/std/hash_map.zig b/lib/std/hash_map.zig index e2e0f056e1..62f430a672 100644 --- a/lib/std/hash_map.zig +++ b/lib/std/hash_map.zig @@ -50,20 +50,20 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) { } pub fn AutoHashMap(comptime K: type, comptime V: type) type { - return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage); + return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage); } pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type { - return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage); + return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage); } /// Builtin hashmap for strings as keys. pub fn StringHashMap(comptime V: type) type { - return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage); + return HashMap([]const u8, V, hashString, eqlString, default_max_load_percentage); } pub fn StringHashMapUnmanaged(comptime V: type) type { - return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage); + return HashMapUnmanaged([]const u8, V, hashString, eqlString, default_max_load_percentage); } pub fn eqlString(a: []const u8, b: []const u8) bool { @@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 { return std.hash.Wyhash.hash(0, s); } -pub const DefaultMaxLoadPercentage = 80; +/// Deprecated use `default_max_load_percentage` +pub const DefaultMaxLoadPercentage = default_max_load_percentage; + +pub const default_max_load_percentage = 80; /// General purpose hash table. /// No order is guaranteed and any modification invalidates live iterators. @@ -89,13 +92,13 @@ pub fn HashMap( comptime V: type, comptime hashFn: fn (key: K) u64, comptime eqlFn: fn (a: K, b: K) bool, - comptime MaxLoadPercentage: u64, + comptime max_load_percentage: u64, ) type { return struct { unmanaged: Unmanaged, allocator: *Allocator, - pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage); + pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage); pub const Entry = Unmanaged.Entry; pub const Hash = Unmanaged.Hash; pub const Iterator = Unmanaged.Iterator; @@ -251,9 +254,9 @@ pub fn HashMapUnmanaged( comptime V: type, hashFn: fn (key: K) u64, eqlFn: fn (a: K, b: K) bool, - comptime MaxLoadPercentage: u64, + comptime max_load_percentage: u64, ) type { - comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100); + comptime assert(max_load_percentage > 0 and max_load_percentage < 100); return struct { const Self = @This(); @@ -274,12 +277,12 @@ pub fn HashMapUnmanaged( // Having a countdown to grow reduces the number of instructions to // execute when determining if the hashmap has enough capacity already. /// Number of available slots before a grow is needed to satisfy the - /// `MaxLoadPercentage`. + /// `max_load_percentage`. available: Size = 0, // This is purely empirical and not a /very smart magic constantâ„¢/. /// Capacity of the first grow when bootstrapping the hashmap. - const MinimalCapacity = 8; + const minimal_capacity = 8; // This hashmap is specially designed for sizes that fit in a u32. const Size = u32; @@ -382,7 +385,7 @@ pub fn HashMapUnmanaged( found_existing: bool, }; - pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage); + pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage); pub fn promote(self: Self, allocator: *Allocator) Managed { return .{ @@ -392,7 +395,7 @@ pub fn HashMapUnmanaged( } fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool { - return size * 100 < MaxLoadPercentage * cap; + return size * 100 < max_load_percentage * cap; } pub fn init(allocator: *Allocator) Self { @@ -425,7 +428,7 @@ pub fn HashMapUnmanaged( } fn capacityForSize(size: Size) Size { - var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1); + var new_cap = @truncate(u32, (@as(u64, size) * 100) / max_load_percentage + 1); new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable; return new_cap; } @@ -439,7 +442,7 @@ pub fn HashMapUnmanaged( if (self.metadata) |_| { self.initMetadatas(); self.size = 0; - self.available = @truncate(u32, (self.capacity() * MaxLoadPercentage) / 100); + self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100); } } @@ -712,9 +715,9 @@ pub fn HashMapUnmanaged( } // This counts the number of occupied slots, used + tombstones, which is - // what has to stay under the MaxLoadPercentage of capacity. + // what has to stay under the max_load_percentage of capacity. fn load(self: *const Self) Size { - const max_load = (self.capacity() * MaxLoadPercentage) / 100; + const max_load = (self.capacity() * max_load_percentage) / 100; assert(max_load >= self.available); return @truncate(Size, max_load - self.available); } @@ -733,7 +736,7 @@ pub fn HashMapUnmanaged( const new_cap = capacityForSize(self.size); try other.allocate(allocator, new_cap); other.initMetadatas(); - other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); + other.available = @truncate(u32, (new_cap * max_load_percentage) / 100); var i: Size = 0; var metadata = self.metadata.?; @@ -751,7 +754,7 @@ pub fn HashMapUnmanaged( } fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void { - const new_cap = std.math.max(new_capacity, MinimalCapacity); + const new_cap = std.math.max(new_capacity, minimal_capacity); assert(new_cap > self.capacity()); assert(std.math.isPowerOfTwo(new_cap)); @@ -759,7 +762,7 @@ pub fn HashMapUnmanaged( defer map.deinit(allocator); try map.allocate(allocator, new_cap); map.initMetadatas(); - map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); + map.available = @truncate(u32, (new_cap * max_load_percentage) / 100); if (self.size != 0) { const old_capacity = self.capacity(); @@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" { try map.put(0, 0); expectEqual(map.count(), 1); - expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity); + expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity); try map.ensureCapacity(65); expectEqual(map.count(), 1); diff --git a/src/Compilation.zig b/src/Compilation.zig index 39e10becec..786280f9ef 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .error_msg = null, .decl = decl, .fwd_decl = fwd_decl.toManaged(module.gpa), + // we don't want to emit optionals and error unions to headers since they have no ABI + .typedefs = undefined, }; defer dg.fwd_decl.deinit(); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index fe87a5994a..084c653d1c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -32,6 +32,34 @@ pub const CValue = union(enum) { }; pub const CValueMap = std.AutoHashMap(*Inst, CValue); +pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); + +fn formatTypeAsCIdentifier( + data: Type, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, +) !void { + var buffer = [1]u8{0} ** 128; + // We don't care if it gets cut off, it's still more unique than a number + var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer; + + for (buf) |c, i| { + switch (c) { + 0 => return writer.writeAll(buf[0..i]), + 'a'...'z', 'A'...'Z', '_', '$' => {}, + '0'...'9' => if (i == 0) { + buf[i] = '_'; + }, + else => buf[i] = '_', + } + } + return writer.writeAll(buf); +} + +pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) { + return .{ .data = t }; +} /// This data is available when outputting .c code for a Module. /// It is not available when generating .h file. @@ -115,6 +143,7 @@ pub const DeclGen = struct { decl: *Decl, fwd_decl: std.ArrayList(u8), error_msg: ?*Module.ErrorMsg, + typedefs: TypedefMap, fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ @@ -325,22 +354,55 @@ pub const DeclGen = struct { const child_type = t.optionalChild(&opt_buf); if (t.isPtrLikeOptional()) { return dg.renderType(w, child_type); + } else if (dg.typedefs.get(t)) |some| { + return w.writeAll(some.name); } - // TODO this needs to be typedeffed since different structs are different types. - try w.writeAll("struct { "); - try dg.renderType(w, child_type); - try w.writeAll(" payload; bool is_null; }"); + var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); + defer buffer.deinit(); + const bw = buffer.writer(); + + try bw.writeAll("typedef struct { "); + try dg.renderType(bw, child_type); + try bw.writeAll(" payload; bool is_null; } "); + const name_index = buffer.items.len; + try bw.print("zig_opt_{s}_t;\n", .{typeToCIdentifier(child_type)}); + + const rendered = buffer.toOwnedSlice(); + errdefer dg.typedefs.allocator.free(rendered); + const name = rendered[name_index .. rendered.len - 2]; + + try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1); + try w.writeAll(name); + dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); }, .ErrorSet => { comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2); try w.writeAll("uint16_t"); }, .ErrorUnion => { - // TODO this needs to be typedeffed since different structs are different types. - try w.writeAll("struct { "); - try dg.renderType(w, t.errorUnionChild()); - try w.writeAll(" payload; uint16_t error; }"); + if (dg.typedefs.get(t)) |some| { + return w.writeAll(some.name); + } + const child_type = t.errorUnionChild(); + + var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); + defer buffer.deinit(); + const bw = buffer.writer(); + + try bw.writeAll("typedef struct { "); + try dg.renderType(bw, t.errorUnionChild()); + try bw.writeAll(" payload; uint16_t error; } "); + const name_index = buffer.items.len; + try bw.print("zig_err_union_{s}_t;\n", .{typeToCIdentifier(child_type)}); + + const rendered = buffer.toOwnedSlice(); + errdefer dg.typedefs.allocator.free(rendered); + const name = rendered[name_index .. rendered.len - 2]; + + try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1); + try w.writeAll(name); + dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered }); }, .Null, .Undefined => unreachable, // must be const or comptime else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ diff --git a/src/link/C.zig b/src/link/C.zig index 1860fdb051..60844ce43b 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; const C = @This(); +const Type = @import("../type.zig").Type; pub const base_tag: link.File.Tag = .c; pub const zig_h = @embedFile("C/zig.h"); @@ -28,9 +29,11 @@ pub const DeclBlock = struct { /// Per-function data. pub const FnBlock = struct { fwd_decl: std.ArrayListUnmanaged(u8), + typedefs: codegen.TypedefMap.Unmanaged, pub const empty: FnBlock = .{ .fwd_decl = .{}, + .typedefs = .{}, }; }; @@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} pub fn freeDecl(self: *C, decl: *Module.Decl) void { decl.link.c.code.deinit(self.base.allocator); decl.fn_link.c.fwd_decl.deinit(self.base.allocator); + var it = decl.fn_link.c.typedefs.iterator(); + while (it.next()) |some| { + self.base.allocator.free(some.value.rendered); + } + decl.fn_link.c.typedefs.deinit(self.base.allocator); } pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { @@ -81,8 +89,10 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { defer tracy.end(); const fwd_decl = &decl.fn_link.c.fwd_decl; + const typedefs = &decl.fn_link.c.typedefs; const code = &decl.link.c.code; fwd_decl.shrinkRetainingCapacity(0); + typedefs.clearRetainingCapacity(); code.shrinkRetainingCapacity(0); var object: codegen.Object = .{ @@ -91,6 +101,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { .error_msg = null, .decl = decl, .fwd_decl = fwd_decl.toManaged(module.gpa), + .typedefs = typedefs.promote(module.gpa), }, .gpa = module.gpa, .code = code.toManaged(module.gpa), @@ -98,9 +109,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { .indent_writer = undefined, // set later so we can get a pointer to object.code }; object.indent_writer = .{ .underlying_writer = object.code.writer() }; - defer object.value_map.deinit(); - defer object.code.deinit(); - defer object.dg.fwd_decl.deinit(); + defer { + object.value_map.deinit(); + object.code.deinit(); + object.dg.fwd_decl.deinit(); + var it = object.dg.typedefs.iterator(); + while (it.next()) |some| { + module.gpa.free(some.value.rendered); + } + object.dg.typedefs.deinit(); + } codegen.genDecl(&object) catch |err| switch (err) { error.AnalysisFail => { @@ -111,6 +129,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { }; fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + typedefs.* = object.dg.typedefs.unmanaged; + object.dg.typedefs.unmanaged = .{}; code.* = object.code.moveToUnmanaged(); // Free excess allocated memory for this Decl. @@ -142,7 +162,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { defer all_buffers.deinit(); // This is at least enough until we get to the function bodies without error handling. - try all_buffers.ensureCapacity(module.decl_table.count() + 1); + try all_buffers.ensureCapacity(module.decl_table.count() + 2); var file_size: u64 = zig_h.len; all_buffers.appendAssumeCapacity(.{ @@ -150,22 +170,25 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { .iov_len = zig_h.len, }); - var error_defs_buf = std.ArrayList(u8).init(comp.gpa); - defer error_defs_buf.deinit(); + var err_typedef_buf = std.ArrayList(u8).init(comp.gpa); + defer err_typedef_buf.deinit(); + const err_typedef_writer = err_typedef_buf.writer(); + const err_typedef_item = all_buffers.addOneAssumeCapacity(); - var it = module.global_error_set.iterator(); - while (it.next()) |entry| { - try error_defs_buf.writer().print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value }); + render_errors: { + if (module.global_error_set.size == 0) break :render_errors; + var it = module.global_error_set.iterator(); + while (it.next()) |entry| { + try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value }); + } + try err_typedef_writer.writeByte('\n'); } - try error_defs_buf.writer().writeByte('\n'); - all_buffers.appendAssumeCapacity(.{ - .iov_base = error_defs_buf.items.ptr, - .iov_len = error_defs_buf.items.len, - }); var fn_count: usize = 0; + var typedefs = std.HashMap(Type, []const u8, Type.hash, Type.eql, std.hash_map.default_max_load_percentage).init(comp.gpa); + defer typedefs.deinit(); - // Forward decls and non-functions first. + // Typedefs, forward decls and non-functions first. // TODO: performance investigation: would keeping a list of Decls that we should // generate, rather than querying here, be faster? for (module.decl_table.items()) |kv| { @@ -174,6 +197,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { .most_recent => |tvm| { const buf = buf: { if (tvm.typed_value.val.castTag(.function)) |_| { + var it = decl.fn_link.c.typedefs.iterator(); + while (it.next()) |new| { + if (typedefs.get(new.key)) |previous| { + try err_typedef_writer.print("typedef {s} {s};\n", .{ previous, new.value.name }); + } else { + try typedefs.ensureCapacity(typedefs.capacity() + 1); + try err_typedef_writer.writeAll(new.value.rendered); + typedefs.putAssumeCapacityNoClobber(new.key, new.value.name); + } + } fn_count += 1; break :buf decl.fn_link.c.fwd_decl.items; } else { @@ -190,6 +223,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { } } + err_typedef_item.* = .{ + .iov_base = err_typedef_buf.items.ptr, + .iov_len = err_typedef_buf.items.len, + }; + file_size += err_typedef_buf.items.len; + // Now the function bodies. try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); for (module.decl_table.items()) |kv| { diff --git a/src/test.zig b/src/test.zig index 47aeef3385..d6c78281dc 100644 --- a/src/test.zig +++ b/src/test.zig @@ -868,11 +868,10 @@ pub const TestContext = struct { std.testing.zig_exe_path, "run", "-cflags", - "-std=c89", + "-std=c99", "-pedantic", "-Werror", "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 - "-Wno-declaration-after-statement", "--", "-lc", exe_path, diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index b22ef44148..ba679f49a0 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -258,6 +258,18 @@ pub fn addCases(ctx: *TestContext) !void { \\ return count - 5; \\} , ""); + + // Same with non pointer optionals + case.addCompareOutput( + \\export fn main() c_int { + \\ var count: c_int = 0; + \\ var opt_ptr: ?c_int = count; + \\ while (opt_ptr) |_| : (count += 1) { + \\ if (count == 4) opt_ptr = null; + \\ } + \\ return count - 5; + \\} + , ""); } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { From 6467ef6d3b5648d47c13b877d3d4fe6a5b5efb7d Mon Sep 17 00:00:00 2001 From: jacob gw Date: Mon, 1 Mar 2021 11:25:50 -0500 Subject: [PATCH 5/8] cbe: add error comparison support --- src/codegen.zig | 2 ++ src/type.zig | 2 +- src/zir_sema.zig | 3 ++- test/stage2/cbe.zig | 56 +++++++++++++++++++++++++++++---------------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index c3cd64cf73..cfe605b567 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2237,6 +2237,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // No side effects, so if it's unreferenced, do nothing. if (inst.base.isUnused()) return MCValue{ .dead = {} }; + if (inst.lhs.ty.zigTypeTag() == .ErrorSet or inst.rhs.ty.zigTypeTag() == .ErrorSet) + return self.fail(inst.base.src, "TODO implement cmp for errors", .{}); switch (arch) { .x86_64 => { try self.code.ensureCapacity(self.code.items.len + 8); diff --git a/src/type.zig b/src/type.zig index 50d55a0c54..c3a99bb184 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1663,6 +1663,7 @@ pub const Type = extern union { .Int, .Float, .ErrorSet, + .ErrorUnion, .Enum, .Frame, .AnyFrame, @@ -1687,7 +1688,6 @@ pub const Type = extern union { }, .Pointer, .Array => ty = ty.elemType(), - .ErrorUnion => @panic("TODO fn isValidVarType"), .Fn => @panic("TODO fn isValidVarType"), .Struct => @panic("TODO struct isValidVarType"), .Union => @panic("TODO union isValidVarType"), diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 9cbdfd07dd..a1b79879b8 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -2326,7 +2326,8 @@ fn zirCmp( return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq)); } } - return mod.fail(scope, inst.base.src, "TODO implement equality comparison between runtime errors", .{}); + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addBinOp(b, inst.base.src, Type.initTag(.bool), if (op == .eq) .cmp_eq else .cmp_neq, lhs, rhs); } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { // This operation allows any combination of integer and float types, regardless of the // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index ba679f49a0..1487b15e12 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -244,30 +244,46 @@ pub fn addCases(ctx: *TestContext) !void { \\} , ""); } + //{ + // var case = ctx.exeFromCompiledC("optionals", .{}); + + // // Simple while loop + // case.addCompareOutput( + // \\export fn main() c_int { + // \\ var count: c_int = 0; + // \\ var opt_ptr: ?*c_int = &count; + // \\ while (opt_ptr) |_| : (count += 1) { + // \\ if (count == 4) opt_ptr = null; + // \\ } + // \\ return count - 5; + // \\} + // , ""); + + // // Same with non pointer optionals + // case.addCompareOutput( + // \\export fn main() c_int { + // \\ var count: c_int = 0; + // \\ var opt_ptr: ?c_int = count; + // \\ while (opt_ptr) |_| : (count += 1) { + // \\ if (count == 4) opt_ptr = null; + // \\ } + // \\ return count - 5; + // \\} + // , ""); + //} { - var case = ctx.exeFromCompiledC("optionals", .{}); - - // Simple while loop + var case = ctx.exeFromCompiledC("errors", .{}); case.addCompareOutput( \\export fn main() c_int { - \\ var count: c_int = 0; - \\ var opt_ptr: ?*c_int = &count; - \\ while (opt_ptr) |_| : (count += 1) { - \\ if (count == 4) opt_ptr = null; - \\ } - \\ return count - 5; + \\ var e1 = error.Foo; + \\ var e2 = error.Bar; + \\ assert(e1 != e2); + \\ assert(e1 == error.Foo); + \\ assert(e2 == error.Bar); + \\ return 0; \\} - , ""); - - // Same with non pointer optionals - case.addCompareOutput( - \\export fn main() c_int { - \\ var count: c_int = 0; - \\ var opt_ptr: ?c_int = count; - \\ while (opt_ptr) |_| : (count += 1) { - \\ if (count == 4) opt_ptr = null; - \\ } - \\ return count - 5; + \\fn assert(b: bool) void { + \\ if (!b) unreachable; \\} , ""); } From 30ffa052f2b53d9a01aedbe672bec3c5e28d5118 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Mon, 1 Mar 2021 15:16:18 -0500 Subject: [PATCH 6/8] stage2 cbe: add error union and error union operations --- src/codegen/c.zig | 85 +++++++++++++++++++++++++++++++++++++++++++-- src/link/C.zig | 3 +- src/type.zig | 11 ++++++ test/stage2/cbe.zig | 14 ++++++++ 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 084c653d1c..ceafe01d35 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -251,6 +251,31 @@ pub const DeclGen = struct { // error values will be #defined at the top of the file return writer.print("zig_error_{s}", .{payload.data.name}); }, + .ErrorUnion => { + const error_type = t.errorUnionSet(); + const payload_type = t.errorUnionChild(); + const data = val.castTag(.error_union).?.data; + try writer.writeByte('('); + try dg.renderType(writer, t); + try writer.writeAll("){"); + if (val.getError()) |_| { + try writer.writeAll(" .error = "); + try dg.renderValue( + writer, + error_type, + data, + ); + try writer.writeAll(" }"); + } else { + try writer.writeAll(" .payload = "); + try dg.renderValue( + writer, + payload_type, + data, + ); + try writer.writeAll(", .error = 0 }"); + } + }, else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ @tagName(e), }), @@ -385,16 +410,17 @@ pub const DeclGen = struct { return w.writeAll(some.name); } const child_type = t.errorUnionChild(); + const set_type = t.errorUnionSet(); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef struct { "); - try dg.renderType(bw, t.errorUnionChild()); + try dg.renderType(bw, child_type); try bw.writeAll(" payload; uint16_t error; } "); const name_index = buffer.items.len; - try bw.print("zig_err_union_{s}_t;\n", .{typeToCIdentifier(child_type)}); + try bw.print("zig_err_union_{s}_{s}_t;\n", .{ typeToCIdentifier(set_type), typeToCIdentifier(child_type) }); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); @@ -543,6 +569,12 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload).?), .is_err => try genIsErr(o, inst.castTag(.is_err).?), .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?), + .unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?), + .unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?), + .unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?), + .unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?), + .wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?), + .wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; switch (result_value) { @@ -962,6 +994,35 @@ fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue { return local; } +// *(E!T) -> E NOT *E +fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = ("); + try o.writeCValue(writer, operand); + + try writer.print("){s}error;\n", .{maybe_deref}); + return local; +} +fn genUnwrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else "."; + const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else ""; + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {s}(", .{maybe_addrof}); + try o.writeCValue(writer, operand); + + try writer.print("){s}payload;\n", .{maybe_deref}); + return local; +} + fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { const writer = o.writer(); const operand = try o.resolveInst(inst.operand); @@ -978,6 +1039,26 @@ fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue { try writer.writeAll("};\n"); return local; } +fn genWrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .error = "); + try o.writeCValue(writer, operand); + try writer.writeAll(" };\n"); + return local; +} +fn genWrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue { + const writer = o.writer(); + const operand = try o.resolveInst(inst.operand); + + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.writeAll(" = { .error = 0, .payload = "); + try o.writeCValue(writer, operand); + try writer.writeAll(" };\n"); + return local; +} fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue { const writer = o.writer(); diff --git a/src/link/C.zig b/src/link/C.zig index 60844ce43b..655a044394 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -179,7 +179,8 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { if (module.global_error_set.size == 0) break :render_errors; var it = module.global_error_set.iterator(); while (it.next()) |entry| { - try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value }); + // + 1 because 0 represents no error + try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value + 1 }); } try err_typedef_writer.writeByte('\n'); } diff --git a/src/type.zig b/src/type.zig index c3a99bb184..b9f9207d4a 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1825,6 +1825,17 @@ pub const Type = extern union { }; } + pub fn errorUnionSet(self: Type) Type { + return switch (self.tag()) { + .anyerror_void_error_union => Type.initTag(.anyerror), + .error_union => { + const payload = self.castTag(.error_union).?; + return payload.data.error_set; + }, + else => unreachable, + }; + } + /// Asserts the type is an array or vector. pub fn arrayLen(self: Type) u64 { return switch (self.tag()) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 1487b15e12..e9082f57fa 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -286,6 +286,20 @@ pub fn addCases(ctx: *TestContext) !void { \\ if (!b) unreachable; \\} , ""); + case.addCompareOutput( + \\export fn main() c_int { + \\ var e: anyerror!c_int = 0; + \\ const i = e catch 69; + \\ return i; + \\} + , ""); + case.addCompareOutput( + \\export fn main() c_int { + \\ var e: anyerror!c_int = error.Foo; + \\ const i = e catch 69; + \\ return 69 - i; + \\} + , ""); } ctx.c("empty start function", linux_x64, \\export fn _start() noreturn { From d9e46a4b901fad0e6d684102ea08444dcec30909 Mon Sep 17 00:00:00 2001 From: jacob gw Date: Mon, 1 Mar 2021 15:40:48 -0500 Subject: [PATCH 7/8] stage2: fix memory leak in the cbe --- src/link/C.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/link/C.zig b/src/link/C.zig index 655a044394..440f52c49f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -92,6 +92,12 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { const typedefs = &decl.fn_link.c.typedefs; const code = &decl.link.c.code; fwd_decl.shrinkRetainingCapacity(0); + { + var it = typedefs.iterator(); + while (it.next()) |entry| { + module.gpa.free(entry.value.rendered); + } + } typedefs.clearRetainingCapacity(); code.shrinkRetainingCapacity(0); From fc62ff77c3921758624a81970f3098300992ee47 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 1 Mar 2021 23:03:26 +0200 Subject: [PATCH 8/8] stage2: error union payload must also be a valid variable type --- src/codegen/c.zig | 2 +- src/type.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ceafe01d35..af8d2d272d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -566,7 +566,7 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi .is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?), .wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?), .optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?), - .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload).?), + .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?), .is_err => try genIsErr(o, inst.castTag(.is_err).?), .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?), .unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?), diff --git a/src/type.zig b/src/type.zig index b9f9207d4a..9599a165fd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1663,7 +1663,6 @@ pub const Type = extern union { .Int, .Float, .ErrorSet, - .ErrorUnion, .Enum, .Frame, .AnyFrame, @@ -1687,6 +1686,7 @@ pub const Type = extern union { return ty.optionalChild(&buf).isValidVarType(is_extern); }, .Pointer, .Array => ty = ty.elemType(), + .ErrorUnion => ty = ty.errorUnionChild(), .Fn => @panic("TODO fn isValidVarType"), .Struct => @panic("TODO struct isValidVarType"),