commit e9a038c33bbf171695b08540536f307b9e418173 (tree)
parent a5cb4ab95e80c4f75356b80251c3628811956b19
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 11 Mar 2021 22:02:35 -0500
Merge pull request #7934 from Vexu/stage2-cbe
Stage2 cbe: optionals and errors
Diffstat:
9 files changed, 447 insertions(+), 31 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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 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();
+
+ 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();
- // 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| {
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;