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) {...@@ -50,20 +50,20 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
50}50}
5151
52pub fn AutoHashMap(comptime K: type, comptime V: type) type {52pub 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);
54}54}
5555
56pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {56pub 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);
58}58}
5959
60/// Builtin hashmap for strings as keys.60/// Builtin hashmap for strings as keys.
61pub fn StringHashMap(comptime V: type) type {61pub 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);
63}63}
6464
65pub fn StringHashMapUnmanaged(comptime V: type) type {65pub 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);
67}67}
6868
69pub fn eqlString(a: []const u8, b: []const u8) bool {69pub fn eqlString(a: []const u8, b: []const u8) bool {
...@@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 {...@@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 {
74 return std.hash.Wyhash.hash(0, s);74 return std.hash.Wyhash.hash(0, s);
75}75}
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
79/// General purpose hash table.82/// General purpose hash table.
80/// No order is guaranteed and any modification invalidates live iterators.83/// No order is guaranteed and any modification invalidates live iterators.
...@@ -89,13 +92,13 @@ pub fn HashMap(...@@ -89,13 +92,13 @@ pub fn HashMap(
89 comptime V: type,92 comptime V: type,
90 comptime hashFn: fn (key: K) u64,93 comptime hashFn: fn (key: K) u64,
91 comptime eqlFn: fn (a: K, b: K) bool,94 comptime eqlFn: fn (a: K, b: K) bool,
92 comptime MaxLoadPercentage: u64,95 comptime max_load_percentage: u64,
93) type {96) type {
94 return struct {97 return struct {
95 unmanaged: Unmanaged,98 unmanaged: Unmanaged,
96 allocator: *Allocator,99 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);
99 pub const Entry = Unmanaged.Entry;102 pub const Entry = Unmanaged.Entry;
100 pub const Hash = Unmanaged.Hash;103 pub const Hash = Unmanaged.Hash;
101 pub const Iterator = Unmanaged.Iterator;104 pub const Iterator = Unmanaged.Iterator;
...@@ -251,9 +254,9 @@ pub fn HashMapUnmanaged(...@@ -251,9 +254,9 @@ pub fn HashMapUnmanaged(
251 comptime V: type,254 comptime V: type,
252 hashFn: fn (key: K) u64,255 hashFn: fn (key: K) u64,
253 eqlFn: fn (a: K, b: K) bool,256 eqlFn: fn (a: K, b: K) bool,
254 comptime MaxLoadPercentage: u64,257 comptime max_load_percentage: u64,
255) type {258) type {
256 comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);259 comptime assert(max_load_percentage > 0 and max_load_percentage < 100);
257260
258 return struct {261 return struct {
259 const Self = @This();262 const Self = @This();
...@@ -274,12 +277,12 @@ pub fn HashMapUnmanaged(...@@ -274,12 +277,12 @@ pub fn HashMapUnmanaged(
274 // Having a countdown to grow reduces the number of instructions to277 // Having a countdown to grow reduces the number of instructions to
275 // execute when determining if the hashmap has enough capacity already.278 // execute when determining if the hashmap has enough capacity already.
276 /// Number of available slots before a grow is needed to satisfy the279 /// Number of available slots before a grow is needed to satisfy the
277 /// `MaxLoadPercentage`.280 /// `max_load_percentage`.
278 available: Size = 0,281 available: Size = 0,
279282
280 // This is purely empirical and not a /very smart magic constant™/.283 // This is purely empirical and not a /very smart magic constant™/.
281 /// Capacity of the first grow when bootstrapping the hashmap.284 /// Capacity of the first grow when bootstrapping the hashmap.
282 const MinimalCapacity = 8;285 const minimal_capacity = 8;
283286
284 // This hashmap is specially designed for sizes that fit in a u32.287 // This hashmap is specially designed for sizes that fit in a u32.
285 const Size = u32;288 const Size = u32;
...@@ -382,7 +385,7 @@ pub fn HashMapUnmanaged(...@@ -382,7 +385,7 @@ pub fn HashMapUnmanaged(
382 found_existing: bool,385 found_existing: bool,
383 };386 };
384387
385 pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);388 pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage);
386389
387 pub fn promote(self: Self, allocator: *Allocator) Managed {390 pub fn promote(self: Self, allocator: *Allocator) Managed {
388 return .{391 return .{
...@@ -392,7 +395,7 @@ pub fn HashMapUnmanaged(...@@ -392,7 +395,7 @@ pub fn HashMapUnmanaged(
392 }395 }
393396
394 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {397 fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
395 return size * 100 < MaxLoadPercentage * cap;398 return size * 100 < max_load_percentage * cap;
396 }399 }
397400
398 pub fn init(allocator: *Allocator) Self {401 pub fn init(allocator: *Allocator) Self {
...@@ -425,7 +428,7 @@ pub fn HashMapUnmanaged(...@@ -425,7 +428,7 @@ pub fn HashMapUnmanaged(
425 }428 }
426429
427 fn capacityForSize(size: Size) Size {430 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);
429 new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;432 new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
430 return new_cap;433 return new_cap;
431 }434 }
...@@ -439,7 +442,7 @@ pub fn HashMapUnmanaged(...@@ -439,7 +442,7 @@ pub fn HashMapUnmanaged(
439 if (self.metadata) |_| {442 if (self.metadata) |_| {
440 self.initMetadatas();443 self.initMetadatas();
441 self.size = 0;444 self.size = 0;
442 self.available = @truncate(u32, (self.capacity() * MaxLoadPercentage) / 100);445 self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100);
443 }446 }
444 }447 }
445448
...@@ -712,9 +715,9 @@ pub fn HashMapUnmanaged(...@@ -712,9 +715,9 @@ pub fn HashMapUnmanaged(
712 }715 }
713716
714 // This counts the number of occupied slots, used + tombstones, which is717 // 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.
716 fn load(self: *const Self) Size {719 fn load(self: *const Self) Size {
717 const max_load = (self.capacity() * MaxLoadPercentage) / 100;720 const max_load = (self.capacity() * max_load_percentage) / 100;
718 assert(max_load >= self.available);721 assert(max_load >= self.available);
719 return @truncate(Size, max_load - self.available);722 return @truncate(Size, max_load - self.available);
720 }723 }
...@@ -733,7 +736,7 @@ pub fn HashMapUnmanaged(...@@ -733,7 +736,7 @@ pub fn HashMapUnmanaged(
733 const new_cap = capacityForSize(self.size);736 const new_cap = capacityForSize(self.size);
734 try other.allocate(allocator, new_cap);737 try other.allocate(allocator, new_cap);
735 other.initMetadatas();738 other.initMetadatas();
736 other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);739 other.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
737740
738 var i: Size = 0;741 var i: Size = 0;
739 var metadata = self.metadata.?;742 var metadata = self.metadata.?;
...@@ -751,7 +754,7 @@ pub fn HashMapUnmanaged(...@@ -751,7 +754,7 @@ pub fn HashMapUnmanaged(
751 }754 }
752755
753 fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {756 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);
755 assert(new_cap > self.capacity());758 assert(new_cap > self.capacity());
756 assert(std.math.isPowerOfTwo(new_cap));759 assert(std.math.isPowerOfTwo(new_cap));
757760
...@@ -759,7 +762,7 @@ pub fn HashMapUnmanaged(...@@ -759,7 +762,7 @@ pub fn HashMapUnmanaged(
759 defer map.deinit(allocator);762 defer map.deinit(allocator);
760 try map.allocate(allocator, new_cap);763 try map.allocate(allocator, new_cap);
761 map.initMetadatas();764 map.initMetadatas();
762 map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);765 map.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
763766
764 if (self.size != 0) {767 if (self.size != 0) {
765 const old_capacity = self.capacity();768 const old_capacity = self.capacity();
...@@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" {...@@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" {
943946
944 try map.put(0, 0);947 try map.put(0, 0);
945 expectEqual(map.count(), 1);948 expectEqual(map.count(), 1);
946 expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity);949 expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity);
947950
948 try map.ensureCapacity(65);951 try map.ensureCapacity(65);
949 expectEqual(map.count(), 1);952 expectEqual(map.count(), 1);
src/Compilation.zig+2
...@@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor...@@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
1653 .error_msg = null,1653 .error_msg = null,
1654 .decl = decl,1654 .decl = decl,
1655 .fwd_decl = fwd_decl.toManaged(module.gpa),1655 .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,
1656 };1658 };
1657 defer dg.fwd_decl.deinit();1659 defer dg.fwd_decl.deinit();
16581660
src/codegen.zig+2
...@@ -2267,6 +2267,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2267,6 +2267,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2267 // No side effects, so if it's unreferenced, do nothing.2267 // No side effects, so if it's unreferenced, do nothing.
2268 if (inst.base.isUnused())2268 if (inst.base.isUnused())
2269 return MCValue{ .dead = {} };2269 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", .{});
2270 switch (arch) {2272 switch (arch) {
2271 .x86_64 => {2273 .x86_64 => {
2272 try self.code.ensureCapacity(self.code.items.len + 8);2274 try self.code.ensureCapacity(self.code.items.len + 8);
src/codegen/c.zig+271-1
...@@ -32,6 +32,34 @@ pub const CValue = union(enum) {...@@ -32,6 +32,34 @@ pub const CValue = union(enum) {
32};32};
3333
34pub const CValueMap = std.AutoHashMap(*Inst, CValue);34pub 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
36/// This data is available when outputting .c code for a Module.64/// This data is available when outputting .c code for a Module.
37/// It is not available when generating .h file.65/// It is not available when generating .h file.
...@@ -115,6 +143,7 @@ pub const DeclGen = struct {...@@ -115,6 +143,7 @@ pub const DeclGen = struct {
115 decl: *Decl,143 decl: *Decl,
116 fwd_decl: std.ArrayList(u8),144 fwd_decl: std.ArrayList(u8),
117 error_msg: ?*Module.ErrorMsg,145 error_msg: ?*Module.ErrorMsg,
146 typedefs: TypedefMap,
118147
119 fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {148 fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
120 dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{149 dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
...@@ -140,7 +169,7 @@ pub const DeclGen = struct {...@@ -140,7 +169,7 @@ pub const DeclGen = struct {
140 return writer.print("{d}", .{val.toUnsignedInt()});169 return writer.print("{d}", .{val.toUnsignedInt()});
141 },170 },
142 .Pointer => switch (val.tag()) {171 .Pointer => switch (val.tag()) {
143 .undef, .zero => try writer.writeAll("0"),172 .null_value, .zero => try writer.writeAll("NULL"),
144 .one => try writer.writeAll("1"),173 .one => try writer.writeAll("1"),
145 .decl_ref => {174 .decl_ref => {
146 const decl = val.castTag(.decl_ref).?.data;175 const decl = val.castTag(.decl_ref).?.data;
...@@ -201,6 +230,52 @@ pub const DeclGen = struct {...@@ -201,6 +230,52 @@ pub const DeclGen = struct {
201 }230 }
202 },231 },
203 .Bool => return writer.print("{}", .{val.toBool()}),232 .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 },
204 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{279 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
205 @tagName(e),280 @tagName(e),
206 }),281 }),
...@@ -299,6 +374,62 @@ pub const DeclGen = struct {...@@ -299,6 +374,62 @@ pub const DeclGen = struct {
299 try dg.renderType(w, t.elemType());374 try dg.renderType(w, t.elemType());
300 try w.writeAll(" *");375 try w.writeAll(" *");
301 },376 },
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 },
302 .Null, .Undefined => unreachable, // must be const or comptime433 .Null, .Undefined => unreachable, // must be const or comptime
303 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{434 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
304 @tagName(e),435 @tagName(e),
...@@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi...@@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
429 .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),560 .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
430 .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),561 .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
431 .not => try genUnOp(o, inst.castTag(.not).?, "!"),562 .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).?),
432 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),578 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
433 };579 };
434 switch (result_value) {580 switch (result_value) {
...@@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {...@@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
802 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});948 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
803}949}
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
805fn IndentWriter(comptime UnderlyingWriter: type) type {1075fn IndentWriter(comptime UnderlyingWriter: type) type {
806 return struct {1076 return struct {
807 const Self = @This();1077 const Self = @This();
src/link/C.zig+64-5
...@@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig");...@@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig");
9const link = @import("../link.zig");9const link = @import("../link.zig");
10const trace = @import("../tracy.zig").trace;10const trace = @import("../tracy.zig").trace;
11const C = @This();11const C = @This();
12const Type = @import("../type.zig").Type;
1213
13pub const base_tag: link.File.Tag = .c;14pub const base_tag: link.File.Tag = .c;
14pub const zig_h = @embedFile("C/zig.h");15pub const zig_h = @embedFile("C/zig.h");
...@@ -28,9 +29,11 @@ pub const DeclBlock = struct {...@@ -28,9 +29,11 @@ pub const DeclBlock = struct {
28/// Per-function data.29/// Per-function data.
29pub const FnBlock = struct {30pub const FnBlock = struct {
30 fwd_decl: std.ArrayListUnmanaged(u8),31 fwd_decl: std.ArrayListUnmanaged(u8),
32 typedefs: codegen.TypedefMap.Unmanaged,
3133
32 pub const empty: FnBlock = .{34 pub const empty: FnBlock = .{
33 .fwd_decl = .{},35 .fwd_decl = .{},
36 .typedefs = .{},
34 };37 };
35};38};
3639
...@@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}...@@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
74pub fn freeDecl(self: *C, decl: *Module.Decl) void {77pub fn freeDecl(self: *C, decl: *Module.Decl) void {
75 decl.link.c.code.deinit(self.base.allocator);78 decl.link.c.code.deinit(self.base.allocator);
76 decl.fn_link.c.fwd_decl.deinit(self.base.allocator);79 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);
77}85}
7886
79pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {87pub 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 {...@@ -81,8 +89,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
81 defer tracy.end();89 defer tracy.end();
8290
83 const fwd_decl = &decl.fn_link.c.fwd_decl;91 const fwd_decl = &decl.fn_link.c.fwd_decl;
92 const typedefs = &decl.fn_link.c.typedefs;
84 const code = &decl.link.c.code;93 const code = &decl.link.c.code;
85 fwd_decl.shrinkRetainingCapacity(0);94 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();
86 code.shrinkRetainingCapacity(0);102 code.shrinkRetainingCapacity(0);
87103
88 var object: codegen.Object = .{104 var object: codegen.Object = .{
...@@ -91,6 +107,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {...@@ -91,6 +107,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
91 .error_msg = null,107 .error_msg = null,
92 .decl = decl,108 .decl = decl,
93 .fwd_decl = fwd_decl.toManaged(module.gpa),109 .fwd_decl = fwd_decl.toManaged(module.gpa),
110 .typedefs = typedefs.promote(module.gpa),
94 },111 },
95 .gpa = module.gpa,112 .gpa = module.gpa,
96 .code = code.toManaged(module.gpa),113 .code = code.toManaged(module.gpa),
...@@ -98,9 +115,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {...@@ -98,9 +115,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
98 .indent_writer = undefined, // set later so we can get a pointer to object.code115 .indent_writer = undefined, // set later so we can get a pointer to object.code
99 };116 };
100 object.indent_writer = .{ .underlying_writer = object.code.writer() };117 object.indent_writer = .{ .underlying_writer = object.code.writer() };
101 defer object.value_map.deinit();118 defer {
102 defer object.code.deinit();119 object.value_map.deinit();
103 defer object.dg.fwd_decl.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
105 codegen.genDecl(&object) catch |err| switch (err) {129 codegen.genDecl(&object) catch |err| switch (err) {
106 error.AnalysisFail => {130 error.AnalysisFail => {
...@@ -111,6 +135,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {...@@ -111,6 +135,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
111 };135 };
112136
113 fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();137 fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
138 typedefs.* = object.dg.typedefs.unmanaged;
139 object.dg.typedefs.unmanaged = .{};
114 code.* = object.code.moveToUnmanaged();140 code.* = object.code.moveToUnmanaged();
115141
116 // Free excess allocated memory for this Decl.142 // Free excess allocated memory for this Decl.
...@@ -142,7 +168,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {...@@ -142,7 +168,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
142 defer all_buffers.deinit();168 defer all_buffers.deinit();
143169
144 // This is at least enough until we get to the function bodies without error handling.170 // 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
147 var file_size: u64 = zig_h.len;173 var file_size: u64 = zig_h.len;
148 all_buffers.appendAssumeCapacity(.{174 all_buffers.appendAssumeCapacity(.{
...@@ -150,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {...@@ -150,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
150 .iov_len = zig_h.len,176 .iov_len = zig_h.len,
151 });177 });
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
153 var fn_count: usize = 0;194 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.
156 // TODO: performance investigation: would keeping a list of Decls that we should199 // TODO: performance investigation: would keeping a list of Decls that we should
157 // generate, rather than querying here, be faster?200 // generate, rather than querying here, be faster?
158 for (module.decl_table.items()) |kv| {201 for (module.decl_table.items()) |kv| {
...@@ -161,6 +204,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {...@@ -161,6 +204,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
161 .most_recent => |tvm| {204 .most_recent => |tvm| {
162 const buf = buf: {205 const buf = buf: {
163 if (tvm.typed_value.val.castTag(.function)) |_| {206 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 }
164 fn_count += 1;217 fn_count += 1;
165 break :buf decl.fn_link.c.fwd_decl.items;218 break :buf decl.fn_link.c.fwd_decl.items;
166 } else {219 } else {
...@@ -177,6 +230,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {...@@ -177,6 +230,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
177 }230 }
178 }231 }
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
180 // Now the function bodies.239 // Now the function bodies.
181 try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);240 try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);
182 for (module.decl_table.items()) |kv| {241 for (module.decl_table.items()) |kv| {
src/test.zig+1-2
...@@ -868,11 +868,10 @@ pub const TestContext = struct {...@@ -868,11 +868,10 @@ pub const TestContext = struct {
868 std.testing.zig_exe_path,868 std.testing.zig_exe_path,
869 "run",869 "run",
870 "-cflags",870 "-cflags",
871 "-std=c89",871 "-std=c99",
872 "-pedantic",872 "-pedantic",
873 "-Werror",873 "-Werror",
874 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875874 "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
875 "-Wno-declaration-after-statement",
876 "--",875 "--",
877 "-lc",876 "-lc",
878 exe_path,877 exe_path,
src/type.zig+24-1
...@@ -1686,8 +1686,8 @@ pub const Type = extern union {...@@ -1686,8 +1686,8 @@ pub const Type = extern union {
1686 return ty.optionalChild(&buf).isValidVarType(is_extern);1686 return ty.optionalChild(&buf).isValidVarType(is_extern);
1687 },1687 },
1688 .Pointer, .Array => ty = ty.elemType(),1688 .Pointer, .Array => ty = ty.elemType(),
1689 .ErrorUnion => ty = ty.errorUnionChild(),
16891690
1690 .ErrorUnion => @panic("TODO fn isValidVarType"),
1691 .Fn => @panic("TODO fn isValidVarType"),1691 .Fn => @panic("TODO fn isValidVarType"),
1692 .Struct => @panic("TODO struct isValidVarType"),1692 .Struct => @panic("TODO struct isValidVarType"),
1693 .Union => @panic("TODO union isValidVarType"),1693 .Union => @panic("TODO union isValidVarType"),
...@@ -1813,6 +1813,29 @@ pub const Type = extern union {...@@ -1813,6 +1813,29 @@ pub const Type = extern union {
1813 }1813 }
1814 }1814 }
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
1816 /// Asserts the type is an array or vector.1839 /// Asserts the type is an array or vector.
1817 pub fn arrayLen(self: Type) u64 {1840 pub fn arrayLen(self: Type) u64 {
1818 return switch (self.tag()) {1841 return switch (self.tag()) {
src/zir_sema.zig+2-1
...@@ -2329,7 +2329,8 @@ fn zirCmp(...@@ -2329,7 +2329,8 @@ fn zirCmp(
2329 return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));2329 return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
2330 }2330 }
2331 }2331 }
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);
2333 } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {2334 } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
2334 // This operation allows any combination of integer and float types, regardless of the2335 // This operation allows any combination of integer and float types, regardless of the
2335 // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for2336 // 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 {...@@ -244,6 +244,63 @@ pub fn addCases(ctx: *TestContext) !void {
244 \\}244 \\}
245 , "");245 , "");
246 }246 }
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 }
247 ctx.c("empty start function", linux_x64,304 ctx.c("empty start function", linux_x64,
248 \\export fn _start() noreturn {305 \\export fn _start() noreturn {
249 \\ unreachable;306 \\ unreachable;