| author | |
| committer | |
| log | e9a038c33bbf171695b08540536f307b9e418173 |
| tree | d2ca77448fca354101e96040b83a7f7edf408647 |
| parent | a5cb4ab95e80c4f75356b80251c3628811956b19 |
| parent | fc62ff77c3921758624a81970f3098300992ee47 |
| signature |
Stage2 cbe: optionals and errors9 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 | 50 | } |
| 51 | 51 | |
| 52 | 52 | pub 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 | } |
| 55 | 55 | |
| 56 | 56 | pub 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 | } |
| 59 | 59 | |
| 60 | 60 | /// Builtin hashmap for strings as keys. |
| 61 | 61 | pub 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 | } |
| 64 | 64 | |
| 65 | 65 | pub 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 | } |
| 68 | 68 | |
| 69 | 69 | pub fn eqlString(a: []const u8, b: []const u8) bool { |
| ... | ... | @@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 { |
| 74 | 74 | return std.hash.Wyhash.hash(0, s); |
| 75 | 75 | } |
| 76 | 76 | |
| 77 | pub const DefaultMaxLoadPercentage = 80; | |
| 77 | /// Deprecated use `default_max_load_percentage` | |
| 78 | pub const DefaultMaxLoadPercentage = default_max_load_percentage; | |
| 79 | ||
| 80 | pub const default_max_load_percentage = 80; | |
| 78 | 81 | |
| 79 | 82 | /// General purpose hash table. |
| 80 | 83 | /// No order is guaranteed and any modification invalidates live iterators. |
| ... | ... | @@ -89,13 +92,13 @@ pub fn HashMap( |
| 89 | 92 | comptime V: type, |
| 90 | 93 | comptime hashFn: fn (key: K) u64, |
| 91 | 94 | comptime eqlFn: fn (a: K, b: K) bool, |
| 92 | comptime MaxLoadPercentage: u64, | |
| 95 | comptime max_load_percentage: u64, | |
| 93 | 96 | ) type { |
| 94 | 97 | return struct { |
| 95 | 98 | unmanaged: Unmanaged, |
| 96 | 99 | allocator: *Allocator, |
| 97 | 100 | |
| 98 | pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage); | |
| 101 | pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage); | |
| 99 | 102 | pub const Entry = Unmanaged.Entry; |
| 100 | 103 | pub const Hash = Unmanaged.Hash; |
| 101 | 104 | pub const Iterator = Unmanaged.Iterator; |
| ... | ... | @@ -251,9 +254,9 @@ pub fn HashMapUnmanaged( |
| 251 | 254 | comptime V: type, |
| 252 | 255 | hashFn: fn (key: K) u64, |
| 253 | 256 | eqlFn: fn (a: K, b: K) bool, |
| 254 | comptime MaxLoadPercentage: u64, | |
| 257 | comptime max_load_percentage: u64, | |
| 255 | 258 | ) type { |
| 256 | comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100); | |
| 259 | comptime assert(max_load_percentage > 0 and max_load_percentage < 100); | |
| 257 | 260 | |
| 258 | 261 | return struct { |
| 259 | 262 | const Self = @This(); |
| ... | ... | @@ -274,12 +277,12 @@ pub fn HashMapUnmanaged( |
| 274 | 277 | // Having a countdown to grow reduces the number of instructions to |
| 275 | 278 | // execute when determining if the hashmap has enough capacity already. |
| 276 | 279 | /// Number of available slots before a grow is needed to satisfy the |
| 277 | /// `MaxLoadPercentage`. | |
| 280 | /// `max_load_percentage`. | |
| 278 | 281 | available: Size = 0, |
| 279 | 282 | |
| 280 | 283 | // This is purely empirical and not a /very smart magic constant™/. |
| 281 | 284 | /// Capacity of the first grow when bootstrapping the hashmap. |
| 282 | const MinimalCapacity = 8; | |
| 285 | const minimal_capacity = 8; | |
| 283 | 286 | |
| 284 | 287 | // This hashmap is specially designed for sizes that fit in a u32. |
| 285 | 288 | const Size = u32; |
| ... | ... | @@ -382,7 +385,7 @@ pub fn HashMapUnmanaged( |
| 382 | 385 | found_existing: bool, |
| 383 | 386 | }; |
| 384 | 387 | |
| 385 | pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage); | |
| 388 | pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage); | |
| 386 | 389 | |
| 387 | 390 | pub fn promote(self: Self, allocator: *Allocator) Managed { |
| 388 | 391 | return .{ |
| ... | ... | @@ -392,7 +395,7 @@ pub fn HashMapUnmanaged( |
| 392 | 395 | } |
| 393 | 396 | |
| 394 | 397 | fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool { |
| 395 | return size * 100 < MaxLoadPercentage * cap; | |
| 398 | return size * 100 < max_load_percentage * cap; | |
| 396 | 399 | } |
| 397 | 400 | |
| 398 | 401 | pub fn init(allocator: *Allocator) Self { |
| ... | ... | @@ -425,7 +428,7 @@ pub fn HashMapUnmanaged( |
| 425 | 428 | } |
| 426 | 429 | |
| 427 | 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 | 432 | new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable; |
| 430 | 433 | return new_cap; |
| 431 | 434 | } |
| ... | ... | @@ -439,7 +442,7 @@ pub fn HashMapUnmanaged( |
| 439 | 442 | if (self.metadata) |_| { |
| 440 | 443 | self.initMetadatas(); |
| 441 | 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 | } |
| 445 | 448 | |
| ... | ... | @@ -712,9 +715,9 @@ pub fn HashMapUnmanaged( |
| 712 | 715 | } |
| 713 | 716 | |
| 714 | 717 | // 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 | 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 | 721 | assert(max_load >= self.available); |
| 719 | 722 | return @truncate(Size, max_load - self.available); |
| 720 | 723 | } |
| ... | ... | @@ -733,7 +736,7 @@ pub fn HashMapUnmanaged( |
| 733 | 736 | const new_cap = capacityForSize(self.size); |
| 734 | 737 | try other.allocate(allocator, new_cap); |
| 735 | 738 | other.initMetadatas(); |
| 736 | other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); | |
| 739 | other.available = @truncate(u32, (new_cap * max_load_percentage) / 100); | |
| 737 | 740 | |
| 738 | 741 | var i: Size = 0; |
| 739 | 742 | var metadata = self.metadata.?; |
| ... | ... | @@ -751,7 +754,7 @@ pub fn HashMapUnmanaged( |
| 751 | 754 | } |
| 752 | 755 | |
| 753 | 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 | 758 | assert(new_cap > self.capacity()); |
| 756 | 759 | assert(std.math.isPowerOfTwo(new_cap)); |
| 757 | 760 | |
| ... | ... | @@ -759,7 +762,7 @@ pub fn HashMapUnmanaged( |
| 759 | 762 | defer map.deinit(allocator); |
| 760 | 763 | try map.allocate(allocator, new_cap); |
| 761 | 764 | map.initMetadatas(); |
| 762 | map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100); | |
| 765 | map.available = @truncate(u32, (new_cap * max_load_percentage) / 100); | |
| 763 | 766 | |
| 764 | 767 | if (self.size != 0) { |
| 765 | 768 | const old_capacity = self.capacity(); |
| ... | ... | @@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" { |
| 943 | 946 | |
| 944 | 947 | try map.put(0, 0); |
| 945 | 948 | expectEqual(map.count(), 1); |
| 946 | expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity); | |
| 949 | expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity); | |
| 947 | 950 | |
| 948 | 951 | try map.ensureCapacity(65); |
| 949 | 952 | expectEqual(map.count(), 1); |
src/Compilation.zig+2| ... | ... | @@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1653 | 1653 | .error_msg = null, |
| 1654 | 1654 | .decl = decl, |
| 1655 | 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 | 1659 | defer dg.fwd_decl.deinit(); |
| 1658 | 1660 |
src/codegen.zig+2| ... | ... | @@ -2267,6 +2267,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2267 | 2267 | // No side effects, so if it's unreferenced, do nothing. |
| 2268 | 2268 | if (inst.base.isUnused()) |
| 2269 | 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 | 2272 | switch (arch) { |
| 2271 | 2273 | .x86_64 => { |
| 2272 | 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 | 32 | }; |
| 33 | 33 | |
| 34 | 34 | pub const CValueMap = std.AutoHashMap(*Inst, CValue); |
| 35 | pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage); | |
| 36 | ||
| 37 | fn 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 | ||
| 60 | pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) { | |
| 61 | return .{ .data = t }; | |
| 62 | } | |
| 35 | 63 | |
| 36 | 64 | /// This data is available when outputting .c code for a Module. |
| 37 | 65 | /// It is not available when generating .h file. |
| ... | ... | @@ -115,6 +143,7 @@ pub const DeclGen = struct { |
| 115 | 143 | decl: *Decl, |
| 116 | 144 | fwd_decl: std.ArrayList(u8), |
| 117 | 145 | error_msg: ?*Module.ErrorMsg, |
| 146 | typedefs: TypedefMap, | |
| 118 | 147 | |
| 119 | 148 | fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { |
| 120 | 149 | dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{ |
| ... | ... | @@ -140,7 +169,7 @@ pub const DeclGen = struct { |
| 140 | 169 | return writer.print("{d}", .{val.toUnsignedInt()}); |
| 141 | 170 | }, |
| 142 | 171 | .Pointer => switch (val.tag()) { |
| 143 | .undef, .zero => try writer.writeAll("0"), | |
| 172 | .null_value, .zero => try writer.writeAll("NULL"), | |
| 144 | 173 | .one => try writer.writeAll("1"), |
| 145 | 174 | .decl_ref => { |
| 146 | 175 | const decl = val.castTag(.decl_ref).?.data; |
| ... | ... | @@ -201,6 +230,52 @@ pub const DeclGen = struct { |
| 201 | 230 | } |
| 202 | 231 | }, |
| 203 | 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 | 279 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ |
| 205 | 280 | @tagName(e), |
| 206 | 281 | }), |
| ... | ... | @@ -299,6 +374,62 @@ pub const DeclGen = struct { |
| 299 | 374 | try dg.renderType(w, t.elemType()); |
| 300 | 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 | 433 | .Null, .Undefined => unreachable, // must be const or comptime |
| 303 | 434 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ |
| 304 | 435 | @tagName(e), |
| ... | ... | @@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi |
| 429 | 560 | .bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "), |
| 430 | 561 | .xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "), |
| 431 | 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 | 578 | else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), |
| 433 | 579 | }; |
| 434 | 580 | switch (result_value) { |
| ... | ... | @@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { |
| 802 | 948 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); |
| 803 | 949 | } |
| 804 | 950 | |
| 951 | fn 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 | ||
| 971 | fn 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 | |
| 998 | fn 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 | } | |
| 1011 | fn 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 | ||
| 1026 | fn 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 | } | |
| 1042 | fn 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 | } | |
| 1052 | fn 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 | ||
| 1063 | fn 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 | ||
| 805 | 1075 | fn IndentWriter(comptime UnderlyingWriter: type) type { |
| 806 | 1076 | return struct { |
| 807 | 1077 | const Self = @This(); |
src/link/C.zig+64-5| ... | ... | @@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig"); |
| 9 | 9 | const link = @import("../link.zig"); |
| 10 | 10 | const trace = @import("../tracy.zig").trace; |
| 11 | 11 | const C = @This(); |
| 12 | const Type = @import("../type.zig").Type; | |
| 12 | 13 | |
| 13 | 14 | pub const base_tag: link.File.Tag = .c; |
| 14 | 15 | pub const zig_h = @embedFile("C/zig.h"); |
| ... | ... | @@ -28,9 +29,11 @@ pub const DeclBlock = struct { |
| 28 | 29 | /// Per-function data. |
| 29 | 30 | pub const FnBlock = struct { |
| 30 | 31 | fwd_decl: std.ArrayListUnmanaged(u8), |
| 32 | typedefs: codegen.TypedefMap.Unmanaged, | |
| 31 | 33 | |
| 32 | 34 | pub const empty: FnBlock = .{ |
| 33 | 35 | .fwd_decl = .{}, |
| 36 | .typedefs = .{}, | |
| 34 | 37 | }; |
| 35 | 38 | }; |
| 36 | 39 | |
| ... | ... | @@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} |
| 74 | 77 | pub fn freeDecl(self: *C, decl: *Module.Decl) void { |
| 75 | 78 | decl.link.c.code.deinit(self.base.allocator); |
| 76 | 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 | } |
| 78 | 86 | |
| 79 | 87 | 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 | 89 | defer tracy.end(); |
| 82 | 90 | |
| 83 | 91 | const fwd_decl = &decl.fn_link.c.fwd_decl; |
| 92 | const typedefs = &decl.fn_link.c.typedefs; | |
| 84 | 93 | const code = &decl.link.c.code; |
| 85 | 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 | 102 | code.shrinkRetainingCapacity(0); |
| 87 | 103 | |
| 88 | 104 | var object: codegen.Object = .{ |
| ... | ... | @@ -91,6 +107,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 91 | 107 | .error_msg = null, |
| 92 | 108 | .decl = decl, |
| 93 | 109 | .fwd_decl = fwd_decl.toManaged(module.gpa), |
| 110 | .typedefs = typedefs.promote(module.gpa), | |
| 94 | 111 | }, |
| 95 | 112 | .gpa = module.gpa, |
| 96 | 113 | .code = code.toManaged(module.gpa), |
| ... | ... | @@ -98,9 +115,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 98 | 115 | .indent_writer = undefined, // set later so we can get a pointer to object.code |
| 99 | 116 | }; |
| 100 | 117 | 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 | } | |
| 104 | 128 | |
| 105 | 129 | codegen.genDecl(&object) catch |err| switch (err) { |
| 106 | 130 | error.AnalysisFail => { |
| ... | ... | @@ -111,6 +135,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 111 | 135 | }; |
| 112 | 136 | |
| 113 | 137 | fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); |
| 138 | typedefs.* = object.dg.typedefs.unmanaged; | |
| 139 | object.dg.typedefs.unmanaged = .{}; | |
| 114 | 140 | code.* = object.code.moveToUnmanaged(); |
| 115 | 141 | |
| 116 | 142 | // Free excess allocated memory for this Decl. |
| ... | ... | @@ -142,7 +168,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 142 | 168 | defer all_buffers.deinit(); |
| 143 | 169 | |
| 144 | 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); | |
| 146 | 172 | |
| 147 | 173 | var file_size: u64 = zig_h.len; |
| 148 | 174 | all_buffers.appendAssumeCapacity(.{ |
| ... | ... | @@ -150,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 150 | 176 | .iov_len = zig_h.len, |
| 151 | 177 | }); |
| 152 | 178 | |
| 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 | 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(); | |
| 154 | 197 | |
| 155 | // Forward decls and non-functions first. | |
| 198 | // Typedefs, forward decls and non-functions first. | |
| 156 | 199 | // TODO: performance investigation: would keeping a list of Decls that we should |
| 157 | 200 | // generate, rather than querying here, be faster? |
| 158 | 201 | for (module.decl_table.items()) |kv| { |
| ... | ... | @@ -161,6 +204,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 161 | 204 | .most_recent => |tvm| { |
| 162 | 205 | const buf = buf: { |
| 163 | 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 | 217 | fn_count += 1; |
| 165 | 218 | break :buf decl.fn_link.c.fwd_decl.items; |
| 166 | 219 | } else { |
| ... | ... | @@ -177,6 +230,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 177 | 230 | } |
| 178 | 231 | } |
| 179 | 232 | |
| 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 | 239 | // Now the function bodies. |
| 181 | 240 | try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); |
| 182 | 241 | for (module.decl_table.items()) |kv| { |
src/test.zig+1-2| ... | ... | @@ -868,11 +868,10 @@ pub const TestContext = struct { |
| 868 | 868 | std.testing.zig_exe_path, |
| 869 | 869 | "run", |
| 870 | 870 | "-cflags", |
| 871 | "-std=c89", | |
| 871 | "-std=c99", | |
| 872 | 872 | "-pedantic", |
| 873 | 873 | "-Werror", |
| 874 | 874 | "-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875 |
| 875 | "-Wno-declaration-after-statement", | |
| 876 | 875 | "--", |
| 877 | 876 | "-lc", |
| 878 | 877 | exe_path, |
src/type.zig+24-1| ... | ... | @@ -1686,8 +1686,8 @@ pub const Type = extern union { |
| 1686 | 1686 | return ty.optionalChild(&buf).isValidVarType(is_extern); |
| 1687 | 1687 | }, |
| 1688 | 1688 | .Pointer, .Array => ty = ty.elemType(), |
| 1689 | .ErrorUnion => ty = ty.errorUnionChild(), | |
| 1689 | 1690 | |
| 1690 | .ErrorUnion => @panic("TODO fn isValidVarType"), | |
| 1691 | 1691 | .Fn => @panic("TODO fn isValidVarType"), |
| 1692 | 1692 | .Struct => @panic("TODO struct isValidVarType"), |
| 1693 | 1693 | .Union => @panic("TODO union isValidVarType"), |
| ... | ... | @@ -1813,6 +1813,29 @@ pub const Type = extern union { |
| 1813 | 1813 | } |
| 1814 | 1814 | } |
| 1815 | 1815 | |
| 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 | 1839 | /// Asserts the type is an array or vector. |
| 1817 | 1840 | pub fn arrayLen(self: Type) u64 { |
| 1818 | 1841 | return switch (self.tag()) { |
src/zir_sema.zig+2-1| ... | ... | @@ -2329,7 +2329,8 @@ fn zirCmp( |
| 2329 | 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 | 2334 | } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) { |
| 2334 | 2335 | // This operation allows any combination of integer and float types, regardless of the |
| 2335 | 2336 | // 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 | 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 | 304 | ctx.c("empty start function", linux_x64, |
| 248 | 305 | \\export fn _start() noreturn { |
| 249 | 306 | \\ unreachable; |