authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-03-11 22:02:35-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-03-11 22:02:35-05:00
loge9a038c33bbf171695b08540536f307b9e418173
treed2ca77448fca354101e96040b83a7f7edf408647
parenta5cb4ab95e80c4f75356b80251c3628811956b19
parentfc62ff77c3921758624a81970f3098300992ee47
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7934 from Vexu/stage2-cbe

Stage2 cbe: optionals and errors

9 files changed, 447 insertions(+), 31 deletions(-)

lib/std/hash_map.zig+24-21
......@@ -50,20 +50,20 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
5050}
5151
5252pub fn AutoHashMap(comptime K: type, comptime V: type) type {
53 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
53 return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
5454}
5555
5656pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {
57 return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
57 return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
5858}
5959
6060/// Builtin hashmap for strings as keys.
6161pub fn StringHashMap(comptime V: type) type {
62 return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
62 return HashMap([]const u8, V, hashString, eqlString, default_max_load_percentage);
6363}
6464
6565pub fn StringHashMapUnmanaged(comptime V: type) type {
66 return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
66 return HashMapUnmanaged([]const u8, V, hashString, eqlString, default_max_load_percentage);
6767}
6868
6969pub fn eqlString(a: []const u8, b: []const u8) bool {
......@@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 {
7474 return std.hash.Wyhash.hash(0, s);
7575}
7676
77pub const DefaultMaxLoadPercentage = 80;
77/// Deprecated use `default_max_load_percentage`
78pub const DefaultMaxLoadPercentage = default_max_load_percentage;
79
80pub const default_max_load_percentage = 80;
7881
7982/// General purpose hash table.
8083/// No order is guaranteed and any modification invalidates live iterators.
......@@ -89,13 +92,13 @@ pub fn HashMap(
8992 comptime V: type,
9093 comptime hashFn: fn (key: K) u64,
9194 comptime eqlFn: fn (a: K, b: K) bool,
92 comptime MaxLoadPercentage: u64,
95 comptime max_load_percentage: u64,
9396) type {
9497 return struct {
9598 unmanaged: Unmanaged,
9699 allocator: *Allocator,
97100
98 pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage);
101 pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage);
99102 pub const Entry = Unmanaged.Entry;
100103 pub const Hash = Unmanaged.Hash;
101104 pub const Iterator = Unmanaged.Iterator;
......@@ -251,9 +254,9 @@ pub fn HashMapUnmanaged(
251254 comptime V: type,
252255 hashFn: fn (key: K) u64,
253256 eqlFn: fn (a: K, b: K) bool,
254 comptime MaxLoadPercentage: u64,
257 comptime max_load_percentage: u64,
255258) type {
256 comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);
259 comptime assert(max_load_percentage > 0 and max_load_percentage < 100);
257260
258261 return struct {
259262 const Self = @This();
......@@ -274,12 +277,12 @@ pub fn HashMapUnmanaged(
274277 // Having a countdown to grow reduces the number of instructions to
275278 // execute when determining if the hashmap has enough capacity already.
276279 /// Number of available slots before a grow is needed to satisfy the
277 /// `MaxLoadPercentage`.
280 /// `max_load_percentage`.
278281 available: Size = 0,
279282
280283 // This is purely empirical and not a /very smart magic constant™/.
281284 /// Capacity of the first grow when bootstrapping the hashmap.
282 const MinimalCapacity = 8;
285 const minimal_capacity = 8;
283286
284287 // This hashmap is specially designed for sizes that fit in a u32.
285288 const Size = u32;
......@@ -382,7 +385,7 @@ pub fn HashMapUnmanaged(
382385 found_existing: bool,
383386 };
384387
385 pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);
388 pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage);
386389
387390 pub fn promote(self: Self, allocator: *Allocator) Managed {
388391 return .{
......@@ -392,7 +395,7 @@ pub fn HashMapUnmanaged(
392395 }
393396
394397 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
395 return size * 100 < MaxLoadPercentage * cap;
398 return size * 100 < max_load_percentage * cap;
396399 }
397400
398401 pub fn init(allocator: *Allocator) Self {
......@@ -425,7 +428,7 @@ pub fn HashMapUnmanaged(
425428 }
426429
427430 fn capacityForSize(size: Size) Size {
428 var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1);
431 var new_cap = @truncate(u32, (@as(u64, size) * 100) / max_load_percentage + 1);
429432 new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
430433 return new_cap;
431434 }
......@@ -439,7 +442,7 @@ pub fn HashMapUnmanaged(
439442 if (self.metadata) |_| {
440443 self.initMetadatas();
441444 self.size = 0;
442 self.available = @truncate(u32, (self.capacity() * MaxLoadPercentage) / 100);
445 self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100);
443446 }
444447 }
445448
......@@ -712,9 +715,9 @@ pub fn HashMapUnmanaged(
712715 }
713716
714717 // This counts the number of occupied slots, used + tombstones, which is
715 // what has to stay under the MaxLoadPercentage of capacity.
718 // what has to stay under the max_load_percentage of capacity.
716719 fn load(self: *const Self) Size {
717 const max_load = (self.capacity() * MaxLoadPercentage) / 100;
720 const max_load = (self.capacity() * max_load_percentage) / 100;
718721 assert(max_load >= self.available);
719722 return @truncate(Size, max_load - self.available);
720723 }
......@@ -733,7 +736,7 @@ pub fn HashMapUnmanaged(
733736 const new_cap = capacityForSize(self.size);
734737 try other.allocate(allocator, new_cap);
735738 other.initMetadatas();
736 other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
739 other.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
737740
738741 var i: Size = 0;
739742 var metadata = self.metadata.?;
......@@ -751,7 +754,7 @@ pub fn HashMapUnmanaged(
751754 }
752755
753756 fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
754 const new_cap = std.math.max(new_capacity, MinimalCapacity);
757 const new_cap = std.math.max(new_capacity, minimal_capacity);
755758 assert(new_cap > self.capacity());
756759 assert(std.math.isPowerOfTwo(new_cap));
757760
......@@ -759,7 +762,7 @@ pub fn HashMapUnmanaged(
759762 defer map.deinit(allocator);
760763 try map.allocate(allocator, new_cap);
761764 map.initMetadatas();
762 map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
765 map.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
763766
764767 if (self.size != 0) {
765768 const old_capacity = self.capacity();
......@@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" {
943946
944947 try map.put(0, 0);
945948 expectEqual(map.count(), 1);
946 expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity);
949 expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity);
947950
948951 try map.ensureCapacity(65);
949952 expectEqual(map.count(), 1);
src/Compilation.zig+2
......@@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
16531653 .error_msg = null,
16541654 .decl = decl,
16551655 .fwd_decl = fwd_decl.toManaged(module.gpa),
1656 // we don't want to emit optionals and error unions to headers since they have no ABI
1657 .typedefs = undefined,
16561658 };
16571659 defer dg.fwd_decl.deinit();
16581660
src/codegen.zig+2
......@@ -2267,6 +2267,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
22672267 // No side effects, so if it's unreferenced, do nothing.
22682268 if (inst.base.isUnused())
22692269 return MCValue{ .dead = {} };
2270 if (inst.lhs.ty.zigTypeTag() == .ErrorSet or inst.rhs.ty.zigTypeTag() == .ErrorSet)
2271 return self.fail(inst.base.src, "TODO implement cmp for errors", .{});
22702272 switch (arch) {
22712273 .x86_64 => {
22722274 try self.code.ensureCapacity(self.code.items.len + 8);
src/codegen/c.zig+271-1
......@@ -32,6 +32,34 @@ pub const CValue = union(enum) {
3232};
3333
3434pub const CValueMap = std.AutoHashMap(*Inst, CValue);
35pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
36
37fn formatTypeAsCIdentifier(
38 data: Type,
39 comptime fmt: []const u8,
40 options: std.fmt.FormatOptions,
41 writer: anytype,
42) !void {
43 var buffer = [1]u8{0} ** 128;
44 // We don't care if it gets cut off, it's still more unique than a number
45 var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer;
46
47 for (buf) |c, i| {
48 switch (c) {
49 0 => return writer.writeAll(buf[0..i]),
50 'a'...'z', 'A'...'Z', '_', '$' => {},
51 '0'...'9' => if (i == 0) {
52 buf[i] = '_';
53 },
54 else => buf[i] = '_',
55 }
56 }
57 return writer.writeAll(buf);
58}
59
60pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
61 return .{ .data = t };
62}
3563
3664/// This data is available when outputting .c code for a Module.
3765/// It is not available when generating .h file.
......@@ -115,6 +143,7 @@ pub const DeclGen = struct {
115143 decl: *Decl,
116144 fwd_decl: std.ArrayList(u8),
117145 error_msg: ?*Module.ErrorMsg,
146 typedefs: TypedefMap,
118147
119148 fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
120149 dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
......@@ -140,7 +169,7 @@ pub const DeclGen = struct {
140169 return writer.print("{d}", .{val.toUnsignedInt()});
141170 },
142171 .Pointer => switch (val.tag()) {
143 .undef, .zero => try writer.writeAll("0"),
172 .null_value, .zero => try writer.writeAll("NULL"),
144173 .one => try writer.writeAll("1"),
145174 .decl_ref => {
146175 const decl = val.castTag(.decl_ref).?.data;
......@@ -201,6 +230,52 @@ pub const DeclGen = struct {
201230 }
202231 },
203232 .Bool => return writer.print("{}", .{val.toBool()}),
233 .Optional => {
234 var opt_buf: Type.Payload.ElemType = undefined;
235 const child_type = t.optionalChild(&opt_buf);
236 if (t.isPtrLikeOptional()) {
237 return dg.renderValue(writer, child_type, val);
238 }
239 try writer.writeByte('(');
240 try dg.renderType(writer, t);
241 if (val.tag() == .null_value) {
242 try writer.writeAll("){ .is_null = true }");
243 } else {
244 try writer.writeAll("){ .is_null = false, .payload = ");
245 try dg.renderValue(writer, child_type, val);
246 try writer.writeAll(" }");
247 }
248 },
249 .ErrorSet => {
250 const payload = val.castTag(.@"error").?;
251 // error values will be #defined at the top of the file
252 return writer.print("zig_error_{s}", .{payload.data.name});
253 },
254 .ErrorUnion => {
255 const error_type = t.errorUnionSet();
256 const payload_type = t.errorUnionChild();
257 const data = val.castTag(.error_union).?.data;
258 try writer.writeByte('(');
259 try dg.renderType(writer, t);
260 try writer.writeAll("){");
261 if (val.getError()) |_| {
262 try writer.writeAll(" .error = ");
263 try dg.renderValue(
264 writer,
265 error_type,
266 data,
267 );
268 try writer.writeAll(" }");
269 } else {
270 try writer.writeAll(" .payload = ");
271 try dg.renderValue(
272 writer,
273 payload_type,
274 data,
275 );
276 try writer.writeAll(", .error = 0 }");
277 }
278 },
204279 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
205280 @tagName(e),
206281 }),
......@@ -299,6 +374,62 @@ pub const DeclGen = struct {
299374 try dg.renderType(w, t.elemType());
300375 try w.writeAll(" *");
301376 },
377 .Optional => {
378 var opt_buf: Type.Payload.ElemType = undefined;
379 const child_type = t.optionalChild(&opt_buf);
380 if (t.isPtrLikeOptional()) {
381 return dg.renderType(w, child_type);
382 } else if (dg.typedefs.get(t)) |some| {
383 return w.writeAll(some.name);
384 }
385
386 var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
387 defer buffer.deinit();
388 const bw = buffer.writer();
389
390 try bw.writeAll("typedef struct { ");
391 try dg.renderType(bw, child_type);
392 try bw.writeAll(" payload; bool is_null; } ");
393 const name_index = buffer.items.len;
394 try bw.print("zig_opt_{s}_t;\n", .{typeToCIdentifier(child_type)});
395
396 const rendered = buffer.toOwnedSlice();
397 errdefer dg.typedefs.allocator.free(rendered);
398 const name = rendered[name_index .. rendered.len - 2];
399
400 try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
401 try w.writeAll(name);
402 dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
403 },
404 .ErrorSet => {
405 comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2);
406 try w.writeAll("uint16_t");
407 },
408 .ErrorUnion => {
409 if (dg.typedefs.get(t)) |some| {
410 return w.writeAll(some.name);
411 }
412 const child_type = t.errorUnionChild();
413 const set_type = t.errorUnionSet();
414
415 var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
416 defer buffer.deinit();
417 const bw = buffer.writer();
418
419 try bw.writeAll("typedef struct { ");
420 try dg.renderType(bw, child_type);
421 try bw.writeAll(" payload; uint16_t error; } ");
422 const name_index = buffer.items.len;
423 try bw.print("zig_err_union_{s}_{s}_t;\n", .{ typeToCIdentifier(set_type), typeToCIdentifier(child_type) });
424
425 const rendered = buffer.toOwnedSlice();
426 errdefer dg.typedefs.allocator.free(rendered);
427 const name = rendered[name_index .. rendered.len - 2];
428
429 try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
430 try w.writeAll(name);
431 dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
432 },
302433 .Null, .Undefined => unreachable, // must be const or comptime
303434 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
304435 @tagName(e),
......@@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
429560 .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
430561 .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
431562 .not => try genUnOp(o, inst.castTag(.not).?, "!"),
563 .is_null => try genIsNull(o, inst.castTag(.is_null).?),
564 .is_non_null => try genIsNull(o, inst.castTag(.is_non_null).?),
565 .is_null_ptr => try genIsNull(o, inst.castTag(.is_null_ptr).?),
566 .is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?),
567 .wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?),
568 .optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?),
569 .optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
570 .is_err => try genIsErr(o, inst.castTag(.is_err).?),
571 .is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
572 .unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
573 .unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
574 .unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
575 .unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
576 .wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
577 .wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
432578 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
433579 };
434580 switch (result_value) {
......@@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
802948 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
803949}
804950
951fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue {
952 const writer = o.writer();
953 const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr;
954 const operator = if (invert_logic) "!=" else "==";
955 const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else "";
956 const operand = try o.resolveInst(inst.operand);
957
958 const local = try o.allocLocal(Type.initTag(.bool), .Const);
959 try writer.writeAll(" = (");
960 try o.writeCValue(writer, operand);
961
962 if (inst.operand.ty.isPtrLikeOptional()) {
963 // operand is a regular pointer, test `operand !=/== NULL`
964 try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator });
965 } else {
966 try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator });
967 }
968 return local;
969}
970
971fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue {
972 const writer = o.writer();
973 const operand = try o.resolveInst(inst.operand);
974
975 const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer)
976 inst.operand.ty.elemType()
977 else
978 inst.operand.ty;
979
980 if (opt_ty.isPtrLikeOptional()) {
981 // the operand is just a regular pointer, no need to do anything special.
982 // *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C
983 return operand;
984 }
985
986 const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
987 const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
988
989 const local = try o.allocLocal(inst.base.ty, .Const);
990 try writer.print(" = {s}(", .{maybe_addrof});
991 try o.writeCValue(writer, operand);
992
993 try writer.print("){s}payload;\n", .{maybe_deref});
994 return local;
995}
996
997// *(E!T) -> E NOT *E
998fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
999 const writer = o.writer();
1000 const operand = try o.resolveInst(inst.operand);
1001
1002 const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
1003
1004 const local = try o.allocLocal(inst.base.ty, .Const);
1005 try writer.writeAll(" = (");
1006 try o.writeCValue(writer, operand);
1007
1008 try writer.print("){s}error;\n", .{maybe_deref});
1009 return local;
1010}
1011fn genUnwrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
1012 const writer = o.writer();
1013 const operand = try o.resolveInst(inst.operand);
1014
1015 const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
1016 const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
1017
1018 const local = try o.allocLocal(inst.base.ty, .Const);
1019 try writer.print(" = {s}(", .{maybe_addrof});
1020 try o.writeCValue(writer, operand);
1021
1022 try writer.print("){s}payload;\n", .{maybe_deref});
1023 return local;
1024}
1025
1026fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue {
1027 const writer = o.writer();
1028 const operand = try o.resolveInst(inst.operand);
1029
1030 if (inst.base.ty.isPtrLikeOptional()) {
1031 // the operand is just a regular pointer, no need to do anything special.
1032 return operand;
1033 }
1034
1035 // .wrap_optional is used to convert non-optionals into optionals so it can never be null.
1036 const local = try o.allocLocal(inst.base.ty, .Const);
1037 try writer.writeAll(" = { .is_null = false, .payload =");
1038 try o.writeCValue(writer, operand);
1039 try writer.writeAll("};\n");
1040 return local;
1041}
1042fn genWrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
1043 const writer = o.writer();
1044 const operand = try o.resolveInst(inst.operand);
1045
1046 const local = try o.allocLocal(inst.base.ty, .Const);
1047 try writer.writeAll(" = { .error = ");
1048 try o.writeCValue(writer, operand);
1049 try writer.writeAll(" };\n");
1050 return local;
1051}
1052fn genWrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
1053 const writer = o.writer();
1054 const operand = try o.resolveInst(inst.operand);
1055
1056 const local = try o.allocLocal(inst.base.ty, .Const);
1057 try writer.writeAll(" = { .error = 0, .payload = ");
1058 try o.writeCValue(writer, operand);
1059 try writer.writeAll(" };\n");
1060 return local;
1061}
1062
1063fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue {
1064 const writer = o.writer();
1065 const maybe_deref = if (inst.base.tag == .is_err_ptr) "[0]" else "";
1066 const operand = try o.resolveInst(inst.operand);
1067
1068 const local = try o.allocLocal(Type.initTag(.bool), .Const);
1069 try writer.writeAll(" = (");
1070 try o.writeCValue(writer, operand);
1071 try writer.print("){s}.error != 0;\n", .{maybe_deref});
1072 return local;
1073}
1074
8051075fn IndentWriter(comptime UnderlyingWriter: type) type {
8061076 return struct {
8071077 const Self = @This();
src/link/C.zig+64-5
......@@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig");
99const link = @import("../link.zig");
1010const trace = @import("../tracy.zig").trace;
1111const C = @This();
12const Type = @import("../type.zig").Type;
1213
1314pub const base_tag: link.File.Tag = .c;
1415pub const zig_h = @embedFile("C/zig.h");
......@@ -28,9 +29,11 @@ pub const DeclBlock = struct {
2829/// Per-function data.
2930pub const FnBlock = struct {
3031 fwd_decl: std.ArrayListUnmanaged(u8),
32 typedefs: codegen.TypedefMap.Unmanaged,
3133
3234 pub const empty: FnBlock = .{
3335 .fwd_decl = .{},
36 .typedefs = .{},
3437 };
3538};
3639
......@@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
7477pub fn freeDecl(self: *C, decl: *Module.Decl) void {
7578 decl.link.c.code.deinit(self.base.allocator);
7679 decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
80 var it = decl.fn_link.c.typedefs.iterator();
81 while (it.next()) |some| {
82 self.base.allocator.free(some.value.rendered);
83 }
84 decl.fn_link.c.typedefs.deinit(self.base.allocator);
7785}
7886
7987pub 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 {
8189 defer tracy.end();
8290
8391 const fwd_decl = &decl.fn_link.c.fwd_decl;
92 const typedefs = &decl.fn_link.c.typedefs;
8493 const code = &decl.link.c.code;
8594 fwd_decl.shrinkRetainingCapacity(0);
95 {
96 var it = typedefs.iterator();
97 while (it.next()) |entry| {
98 module.gpa.free(entry.value.rendered);
99 }
100 }
101 typedefs.clearRetainingCapacity();
86102 code.shrinkRetainingCapacity(0);
87103
88104 var object: codegen.Object = .{
......@@ -91,6 +107,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
91107 .error_msg = null,
92108 .decl = decl,
93109 .fwd_decl = fwd_decl.toManaged(module.gpa),
110 .typedefs = typedefs.promote(module.gpa),
94111 },
95112 .gpa = module.gpa,
96113 .code = code.toManaged(module.gpa),
......@@ -98,9 +115,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
98115 .indent_writer = undefined, // set later so we can get a pointer to object.code
99116 };
100117 object.indent_writer = .{ .underlying_writer = object.code.writer() };
101 defer object.value_map.deinit();
102 defer object.code.deinit();
103 defer object.dg.fwd_decl.deinit();
118 defer {
119 object.value_map.deinit();
120 object.code.deinit();
121 object.dg.fwd_decl.deinit();
122 var it = object.dg.typedefs.iterator();
123 while (it.next()) |some| {
124 module.gpa.free(some.value.rendered);
125 }
126 object.dg.typedefs.deinit();
127 }
104128
105129 codegen.genDecl(&object) catch |err| switch (err) {
106130 error.AnalysisFail => {
......@@ -111,6 +135,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
111135 };
112136
113137 fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
138 typedefs.* = object.dg.typedefs.unmanaged;
139 object.dg.typedefs.unmanaged = .{};
114140 code.* = object.code.moveToUnmanaged();
115141
116142 // Free excess allocated memory for this Decl.
......@@ -142,7 +168,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
142168 defer all_buffers.deinit();
143169
144170 // This is at least enough until we get to the function bodies without error handling.
145 try all_buffers.ensureCapacity(module.decl_table.count() + 1);
171 try all_buffers.ensureCapacity(module.decl_table.count() + 2);
146172
147173 var file_size: u64 = zig_h.len;
148174 all_buffers.appendAssumeCapacity(.{
......@@ -150,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
150176 .iov_len = zig_h.len,
151177 });
152178
179 var err_typedef_buf = std.ArrayList(u8).init(comp.gpa);
180 defer err_typedef_buf.deinit();
181 const err_typedef_writer = err_typedef_buf.writer();
182 const err_typedef_item = all_buffers.addOneAssumeCapacity();
183
184 render_errors: {
185 if (module.global_error_set.size == 0) break :render_errors;
186 var it = module.global_error_set.iterator();
187 while (it.next()) |entry| {
188 // + 1 because 0 represents no error
189 try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value + 1 });
190 }
191 try err_typedef_writer.writeByte('\n');
192 }
193
153194 var fn_count: usize = 0;
195 var typedefs = std.HashMap(Type, []const u8, Type.hash, Type.eql, std.hash_map.default_max_load_percentage).init(comp.gpa);
196 defer typedefs.deinit();
154197
155 // Forward decls and non-functions first.
198 // Typedefs, forward decls and non-functions first.
156199 // TODO: performance investigation: would keeping a list of Decls that we should
157200 // generate, rather than querying here, be faster?
158201 for (module.decl_table.items()) |kv| {
......@@ -161,6 +204,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
161204 .most_recent => |tvm| {
162205 const buf = buf: {
163206 if (tvm.typed_value.val.castTag(.function)) |_| {
207 var it = decl.fn_link.c.typedefs.iterator();
208 while (it.next()) |new| {
209 if (typedefs.get(new.key)) |previous| {
210 try err_typedef_writer.print("typedef {s} {s};\n", .{ previous, new.value.name });
211 } else {
212 try typedefs.ensureCapacity(typedefs.capacity() + 1);
213 try err_typedef_writer.writeAll(new.value.rendered);
214 typedefs.putAssumeCapacityNoClobber(new.key, new.value.name);
215 }
216 }
164217 fn_count += 1;
165218 break :buf decl.fn_link.c.fwd_decl.items;
166219 } else {
......@@ -177,6 +230,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
177230 }
178231 }
179232
233 err_typedef_item.* = .{
234 .iov_base = err_typedef_buf.items.ptr,
235 .iov_len = err_typedef_buf.items.len,
236 };
237 file_size += err_typedef_buf.items.len;
238
180239 // Now the function bodies.
181240 try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);
182241 for (module.decl_table.items()) |kv| {
src/test.zig+1-2
......@@ -868,11 +868,10 @@ pub const TestContext = struct {
868868 std.testing.zig_exe_path,
869869 "run",
870870 "-cflags",
871 "-std=c89",
871 "-std=c99",
872872 "-pedantic",
873873 "-Werror",
874874 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
875 "-Wno-declaration-after-statement",
876875 "--",
877876 "-lc",
878877 exe_path,
src/type.zig+24-1
......@@ -1686,8 +1686,8 @@ pub const Type = extern union {
16861686 return ty.optionalChild(&buf).isValidVarType(is_extern);
16871687 },
16881688 .Pointer, .Array => ty = ty.elemType(),
1689 .ErrorUnion => ty = ty.errorUnionChild(),
16891690
1690 .ErrorUnion => @panic("TODO fn isValidVarType"),
16911691 .Fn => @panic("TODO fn isValidVarType"),
16921692 .Struct => @panic("TODO struct isValidVarType"),
16931693 .Union => @panic("TODO union isValidVarType"),
......@@ -1813,6 +1813,29 @@ pub const Type = extern union {
18131813 }
18141814 }
18151815
1816 /// Asserts that the type is an error union.
1817 pub fn errorUnionChild(self: Type) Type {
1818 return switch (self.tag()) {
1819 .anyerror_void_error_union => Type.initTag(.anyerror),
1820 .error_union => {
1821 const payload = self.castTag(.error_union).?;
1822 return payload.data.payload;
1823 },
1824 else => unreachable,
1825 };
1826 }
1827
1828 pub fn errorUnionSet(self: Type) Type {
1829 return switch (self.tag()) {
1830 .anyerror_void_error_union => Type.initTag(.anyerror),
1831 .error_union => {
1832 const payload = self.castTag(.error_union).?;
1833 return payload.data.error_set;
1834 },
1835 else => unreachable,
1836 };
1837 }
1838
18161839 /// Asserts the type is an array or vector.
18171840 pub fn arrayLen(self: Type) u64 {
18181841 return switch (self.tag()) {
src/zir_sema.zig+2-1
......@@ -2329,7 +2329,8 @@ fn zirCmp(
23292329 return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
23302330 }
23312331 }
2332 return mod.fail(scope, inst.base.src, "TODO implement equality comparison between runtime errors", .{});
2332 const b = try mod.requireRuntimeBlock(scope, inst.base.src);
2333 return mod.addBinOp(b, inst.base.src, Type.initTag(.bool), if (op == .eq) .cmp_eq else .cmp_neq, lhs, rhs);
23332334 } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
23342335 // This operation allows any combination of integer and float types, regardless of the
23352336 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
test/stage2/cbe.zig+57
......@@ -244,6 +244,63 @@ pub fn addCases(ctx: *TestContext) !void {
244244 \\}
245245 , "");
246246 }
247 //{
248 // var case = ctx.exeFromCompiledC("optionals", .{});
249
250 // // Simple while loop
251 // case.addCompareOutput(
252 // \\export fn main() c_int {
253 // \\ var count: c_int = 0;
254 // \\ var opt_ptr: ?*c_int = &count;
255 // \\ while (opt_ptr) |_| : (count += 1) {
256 // \\ if (count == 4) opt_ptr = null;
257 // \\ }
258 // \\ return count - 5;
259 // \\}
260 // , "");
261
262 // // Same with non pointer optionals
263 // case.addCompareOutput(
264 // \\export fn main() c_int {
265 // \\ var count: c_int = 0;
266 // \\ var opt_ptr: ?c_int = count;
267 // \\ while (opt_ptr) |_| : (count += 1) {
268 // \\ if (count == 4) opt_ptr = null;
269 // \\ }
270 // \\ return count - 5;
271 // \\}
272 // , "");
273 //}
274 {
275 var case = ctx.exeFromCompiledC("errors", .{});
276 case.addCompareOutput(
277 \\export fn main() c_int {
278 \\ var e1 = error.Foo;
279 \\ var e2 = error.Bar;
280 \\ assert(e1 != e2);
281 \\ assert(e1 == error.Foo);
282 \\ assert(e2 == error.Bar);
283 \\ return 0;
284 \\}
285 \\fn assert(b: bool) void {
286 \\ if (!b) unreachable;
287 \\}
288 , "");
289 case.addCompareOutput(
290 \\export fn main() c_int {
291 \\ var e: anyerror!c_int = 0;
292 \\ const i = e catch 69;
293 \\ return i;
294 \\}
295 , "");
296 case.addCompareOutput(
297 \\export fn main() c_int {
298 \\ var e: anyerror!c_int = error.Foo;
299 \\ const i = e catch 69;
300 \\ return 69 - i;
301 \\}
302 , "");
303 }
247304 ctx.c("empty start function", linux_x64,
248305 \\export fn _start() noreturn {
249306 \\ unreachable;