Merge pull request #7934 from Vexu/stage2-cbe

Stage2 cbe: optionals and errors
This commit is contained in:
Andrew Kelley 2021-03-11 22:02:35 -05:00 committed by GitHub
commit e9a038c33b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 448 additions and 32 deletions

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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();

View File

@ -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| {

View File

@ -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,

View File

@ -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()) {

View File

@ -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

View File

@ -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;