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.zig b/src/codegen.zig index e21626bdb6..a508885576 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2267,6 +2267,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/codegen/c.zig b/src/codegen/c.zig index 1a323441d9..af8d2d272d 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, .{ @@ -140,7 +169,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 +230,52 @@ 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); + } + try writer.writeByte('('); + try dg.renderType(writer, t); + 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(" }"); + } + }, + .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}); + }, + .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), }), @@ -299,6 +374,62 @@ 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); + } else if (dg.typedefs.get(t)) |some| { + return w.writeAll(some.name); + } + + 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 => { + if (dg.typedefs.get(t)) |some| { + 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, child_type); + try bw.writeAll(" payload; uint16_t error; } "); + const name_index = buffer.items.len; + 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); + 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}", .{ @tagName(e), @@ -429,6 +560,21 @@ 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_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).?), + .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) { @@ -802,6 +948,130 @@ 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 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); + + 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` + try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator }); + } 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); + + const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer) + inst.operand.ty.elemType() + else + inst.operand.ty; + + 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; +} + +// *(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); + + if (inst.base.ty.isPtrLikeOptional()) { + // the operand is just a regular pointer, no need to do anything special. + return operand; + } + + // .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 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(); + 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..440f52c49f 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,16 @@ 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); + { + var it = typedefs.iterator(); + while (it.next()) |entry| { + module.gpa.free(entry.value.rendered); + } + } + typedefs.clearRetainingCapacity(); code.shrinkRetainingCapacity(0); var object: codegen.Object = .{ @@ -91,6 +107,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 +115,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 +135,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 +168,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,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { .iov_len = zig_h.len, }); - var fn_count: usize = 0; + 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(); - // Forward decls and non-functions first. + render_errors: { + if (module.global_error_set.size == 0) break :render_errors; + var it = module.global_error_set.iterator(); + while (it.next()) |entry| { + // + 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'); + } + + 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(); + + // 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| { @@ -161,6 +204,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 { @@ -177,6 +230,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/src/type.zig b/src/type.zig index bb8dcea390..9599a165fd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1686,8 +1686,8 @@ pub const Type = extern union { return ty.optionalChild(&buf).isValidVarType(is_extern); }, .Pointer, .Array => ty = ty.elemType(), + .ErrorUnion => ty = ty.errorUnionChild(), - .ErrorUnion => @panic("TODO fn isValidVarType"), .Fn => @panic("TODO fn isValidVarType"), .Struct => @panic("TODO struct isValidVarType"), .Union => @panic("TODO union isValidVarType"), @@ -1813,6 +1813,29 @@ 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, + }; + } + + 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/src/zir_sema.zig b/src/zir_sema.zig index 2df32709c5..3f540be9cb 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -2329,7 +2329,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 873f0e1198..e9082f57fa 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -244,6 +244,63 @@ 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("errors", .{}); + case.addCompareOutput( + \\export fn main() c_int { + \\ var e1 = error.Foo; + \\ var e2 = error.Bar; + \\ assert(e1 != e2); + \\ assert(e1 == error.Foo); + \\ assert(e2 == error.Bar); + \\ return 0; + \\} + \\fn assert(b: bool) 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 { \\ unreachable;