mirror of
https://github.com/ziglang/zig.git
synced 2026-02-14 13:30:45 +00:00
Merge pull request #7934 from Vexu/stage2-cbe
Stage2 cbe: optionals and errors
This commit is contained in:
commit
e9a038c33b
@ -50,20 +50,20 @@ pub fn getAutoEqlFn(comptime K: type) (fn (K, K) bool) {
|
||||
}
|
||||
|
||||
pub fn AutoHashMap(comptime K: type, comptime V: type) type {
|
||||
return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
|
||||
return HashMap(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn AutoHashMapUnmanaged(comptime K: type, comptime V: type) type {
|
||||
return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), DefaultMaxLoadPercentage);
|
||||
return HashMapUnmanaged(K, V, getAutoHashFn(K), getAutoEqlFn(K), default_max_load_percentage);
|
||||
}
|
||||
|
||||
/// Builtin hashmap for strings as keys.
|
||||
pub fn StringHashMap(comptime V: type) type {
|
||||
return HashMap([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
|
||||
return HashMap([]const u8, V, hashString, eqlString, default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn StringHashMapUnmanaged(comptime V: type) type {
|
||||
return HashMapUnmanaged([]const u8, V, hashString, eqlString, DefaultMaxLoadPercentage);
|
||||
return HashMapUnmanaged([]const u8, V, hashString, eqlString, default_max_load_percentage);
|
||||
}
|
||||
|
||||
pub fn eqlString(a: []const u8, b: []const u8) bool {
|
||||
@ -74,7 +74,10 @@ pub fn hashString(s: []const u8) u64 {
|
||||
return std.hash.Wyhash.hash(0, s);
|
||||
}
|
||||
|
||||
pub const DefaultMaxLoadPercentage = 80;
|
||||
/// Deprecated use `default_max_load_percentage`
|
||||
pub const DefaultMaxLoadPercentage = default_max_load_percentage;
|
||||
|
||||
pub const default_max_load_percentage = 80;
|
||||
|
||||
/// General purpose hash table.
|
||||
/// No order is guaranteed and any modification invalidates live iterators.
|
||||
@ -89,13 +92,13 @@ pub fn HashMap(
|
||||
comptime V: type,
|
||||
comptime hashFn: fn (key: K) u64,
|
||||
comptime eqlFn: fn (a: K, b: K) bool,
|
||||
comptime MaxLoadPercentage: u64,
|
||||
comptime max_load_percentage: u64,
|
||||
) type {
|
||||
return struct {
|
||||
unmanaged: Unmanaged,
|
||||
allocator: *Allocator,
|
||||
|
||||
pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, MaxLoadPercentage);
|
||||
pub const Unmanaged = HashMapUnmanaged(K, V, hashFn, eqlFn, max_load_percentage);
|
||||
pub const Entry = Unmanaged.Entry;
|
||||
pub const Hash = Unmanaged.Hash;
|
||||
pub const Iterator = Unmanaged.Iterator;
|
||||
@ -251,9 +254,9 @@ pub fn HashMapUnmanaged(
|
||||
comptime V: type,
|
||||
hashFn: fn (key: K) u64,
|
||||
eqlFn: fn (a: K, b: K) bool,
|
||||
comptime MaxLoadPercentage: u64,
|
||||
comptime max_load_percentage: u64,
|
||||
) type {
|
||||
comptime assert(MaxLoadPercentage > 0 and MaxLoadPercentage < 100);
|
||||
comptime assert(max_load_percentage > 0 and max_load_percentage < 100);
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
@ -274,12 +277,12 @@ pub fn HashMapUnmanaged(
|
||||
// Having a countdown to grow reduces the number of instructions to
|
||||
// execute when determining if the hashmap has enough capacity already.
|
||||
/// Number of available slots before a grow is needed to satisfy the
|
||||
/// `MaxLoadPercentage`.
|
||||
/// `max_load_percentage`.
|
||||
available: Size = 0,
|
||||
|
||||
// This is purely empirical and not a /very smart magic constant™/.
|
||||
/// Capacity of the first grow when bootstrapping the hashmap.
|
||||
const MinimalCapacity = 8;
|
||||
const minimal_capacity = 8;
|
||||
|
||||
// This hashmap is specially designed for sizes that fit in a u32.
|
||||
const Size = u32;
|
||||
@ -382,7 +385,7 @@ pub fn HashMapUnmanaged(
|
||||
found_existing: bool,
|
||||
};
|
||||
|
||||
pub const Managed = HashMap(K, V, hashFn, eqlFn, MaxLoadPercentage);
|
||||
pub const Managed = HashMap(K, V, hashFn, eqlFn, max_load_percentage);
|
||||
|
||||
pub fn promote(self: Self, allocator: *Allocator) Managed {
|
||||
return .{
|
||||
@ -392,7 +395,7 @@ pub fn HashMapUnmanaged(
|
||||
}
|
||||
|
||||
fn isUnderMaxLoadPercentage(size: Size, cap: Size) bool {
|
||||
return size * 100 < MaxLoadPercentage * cap;
|
||||
return size * 100 < max_load_percentage * cap;
|
||||
}
|
||||
|
||||
pub fn init(allocator: *Allocator) Self {
|
||||
@ -425,7 +428,7 @@ pub fn HashMapUnmanaged(
|
||||
}
|
||||
|
||||
fn capacityForSize(size: Size) Size {
|
||||
var new_cap = @truncate(u32, (@as(u64, size) * 100) / MaxLoadPercentage + 1);
|
||||
var new_cap = @truncate(u32, (@as(u64, size) * 100) / max_load_percentage + 1);
|
||||
new_cap = math.ceilPowerOfTwo(u32, new_cap) catch unreachable;
|
||||
return new_cap;
|
||||
}
|
||||
@ -439,7 +442,7 @@ pub fn HashMapUnmanaged(
|
||||
if (self.metadata) |_| {
|
||||
self.initMetadatas();
|
||||
self.size = 0;
|
||||
self.available = @truncate(u32, (self.capacity() * MaxLoadPercentage) / 100);
|
||||
self.available = @truncate(u32, (self.capacity() * max_load_percentage) / 100);
|
||||
}
|
||||
}
|
||||
|
||||
@ -712,9 +715,9 @@ pub fn HashMapUnmanaged(
|
||||
}
|
||||
|
||||
// This counts the number of occupied slots, used + tombstones, which is
|
||||
// what has to stay under the MaxLoadPercentage of capacity.
|
||||
// what has to stay under the max_load_percentage of capacity.
|
||||
fn load(self: *const Self) Size {
|
||||
const max_load = (self.capacity() * MaxLoadPercentage) / 100;
|
||||
const max_load = (self.capacity() * max_load_percentage) / 100;
|
||||
assert(max_load >= self.available);
|
||||
return @truncate(Size, max_load - self.available);
|
||||
}
|
||||
@ -733,7 +736,7 @@ pub fn HashMapUnmanaged(
|
||||
const new_cap = capacityForSize(self.size);
|
||||
try other.allocate(allocator, new_cap);
|
||||
other.initMetadatas();
|
||||
other.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
|
||||
other.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
|
||||
|
||||
var i: Size = 0;
|
||||
var metadata = self.metadata.?;
|
||||
@ -751,7 +754,7 @@ pub fn HashMapUnmanaged(
|
||||
}
|
||||
|
||||
fn grow(self: *Self, allocator: *Allocator, new_capacity: Size) !void {
|
||||
const new_cap = std.math.max(new_capacity, MinimalCapacity);
|
||||
const new_cap = std.math.max(new_capacity, minimal_capacity);
|
||||
assert(new_cap > self.capacity());
|
||||
assert(std.math.isPowerOfTwo(new_cap));
|
||||
|
||||
@ -759,7 +762,7 @@ pub fn HashMapUnmanaged(
|
||||
defer map.deinit(allocator);
|
||||
try map.allocate(allocator, new_cap);
|
||||
map.initMetadatas();
|
||||
map.available = @truncate(u32, (new_cap * MaxLoadPercentage) / 100);
|
||||
map.available = @truncate(u32, (new_cap * max_load_percentage) / 100);
|
||||
|
||||
if (self.size != 0) {
|
||||
const old_capacity = self.capacity();
|
||||
@ -943,7 +946,7 @@ test "std.hash_map ensureCapacity with existing elements" {
|
||||
|
||||
try map.put(0, 0);
|
||||
expectEqual(map.count(), 1);
|
||||
expectEqual(map.capacity(), @TypeOf(map).Unmanaged.MinimalCapacity);
|
||||
expectEqual(map.capacity(), @TypeOf(map).Unmanaged.minimal_capacity);
|
||||
|
||||
try map.ensureCapacity(65);
|
||||
expectEqual(map.count(), 1);
|
||||
|
||||
@ -1653,6 +1653,8 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
// we don't want to emit optionals and error unions to headers since they have no ABI
|
||||
.typedefs = undefined,
|
||||
};
|
||||
defer dg.fwd_decl.deinit();
|
||||
|
||||
|
||||
@ -2267,6 +2267,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
|
||||
// No side effects, so if it's unreferenced, do nothing.
|
||||
if (inst.base.isUnused())
|
||||
return MCValue{ .dead = {} };
|
||||
if (inst.lhs.ty.zigTypeTag() == .ErrorSet or inst.rhs.ty.zigTypeTag() == .ErrorSet)
|
||||
return self.fail(inst.base.src, "TODO implement cmp for errors", .{});
|
||||
switch (arch) {
|
||||
.x86_64 => {
|
||||
try self.code.ensureCapacity(self.code.items.len + 8);
|
||||
|
||||
@ -32,6 +32,34 @@ pub const CValue = union(enum) {
|
||||
};
|
||||
|
||||
pub const CValueMap = std.AutoHashMap(*Inst, CValue);
|
||||
pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
|
||||
|
||||
fn formatTypeAsCIdentifier(
|
||||
data: Type,
|
||||
comptime fmt: []const u8,
|
||||
options: std.fmt.FormatOptions,
|
||||
writer: anytype,
|
||||
) !void {
|
||||
var buffer = [1]u8{0} ** 128;
|
||||
// We don't care if it gets cut off, it's still more unique than a number
|
||||
var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer;
|
||||
|
||||
for (buf) |c, i| {
|
||||
switch (c) {
|
||||
0 => return writer.writeAll(buf[0..i]),
|
||||
'a'...'z', 'A'...'Z', '_', '$' => {},
|
||||
'0'...'9' => if (i == 0) {
|
||||
buf[i] = '_';
|
||||
},
|
||||
else => buf[i] = '_',
|
||||
}
|
||||
}
|
||||
return writer.writeAll(buf);
|
||||
}
|
||||
|
||||
pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) {
|
||||
return .{ .data = t };
|
||||
}
|
||||
|
||||
/// This data is available when outputting .c code for a Module.
|
||||
/// It is not available when generating .h file.
|
||||
@ -115,6 +143,7 @@ pub const DeclGen = struct {
|
||||
decl: *Decl,
|
||||
fwd_decl: std.ArrayList(u8),
|
||||
error_msg: ?*Module.ErrorMsg,
|
||||
typedefs: TypedefMap,
|
||||
|
||||
fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
|
||||
dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
|
||||
@ -140,7 +169,7 @@ pub const DeclGen = struct {
|
||||
return writer.print("{d}", .{val.toUnsignedInt()});
|
||||
},
|
||||
.Pointer => switch (val.tag()) {
|
||||
.undef, .zero => try writer.writeAll("0"),
|
||||
.null_value, .zero => try writer.writeAll("NULL"),
|
||||
.one => try writer.writeAll("1"),
|
||||
.decl_ref => {
|
||||
const decl = val.castTag(.decl_ref).?.data;
|
||||
@ -201,6 +230,52 @@ pub const DeclGen = struct {
|
||||
}
|
||||
},
|
||||
.Bool => return writer.print("{}", .{val.toBool()}),
|
||||
.Optional => {
|
||||
var opt_buf: Type.Payload.ElemType = undefined;
|
||||
const child_type = t.optionalChild(&opt_buf);
|
||||
if (t.isPtrLikeOptional()) {
|
||||
return dg.renderValue(writer, child_type, val);
|
||||
}
|
||||
try writer.writeByte('(');
|
||||
try dg.renderType(writer, t);
|
||||
if (val.tag() == .null_value) {
|
||||
try writer.writeAll("){ .is_null = true }");
|
||||
} else {
|
||||
try writer.writeAll("){ .is_null = false, .payload = ");
|
||||
try dg.renderValue(writer, child_type, val);
|
||||
try writer.writeAll(" }");
|
||||
}
|
||||
},
|
||||
.ErrorSet => {
|
||||
const payload = val.castTag(.@"error").?;
|
||||
// error values will be #defined at the top of the file
|
||||
return writer.print("zig_error_{s}", .{payload.data.name});
|
||||
},
|
||||
.ErrorUnion => {
|
||||
const error_type = t.errorUnionSet();
|
||||
const payload_type = t.errorUnionChild();
|
||||
const data = val.castTag(.error_union).?.data;
|
||||
try writer.writeByte('(');
|
||||
try dg.renderType(writer, t);
|
||||
try writer.writeAll("){");
|
||||
if (val.getError()) |_| {
|
||||
try writer.writeAll(" .error = ");
|
||||
try dg.renderValue(
|
||||
writer,
|
||||
error_type,
|
||||
data,
|
||||
);
|
||||
try writer.writeAll(" }");
|
||||
} else {
|
||||
try writer.writeAll(" .payload = ");
|
||||
try dg.renderValue(
|
||||
writer,
|
||||
payload_type,
|
||||
data,
|
||||
);
|
||||
try writer.writeAll(", .error = 0 }");
|
||||
}
|
||||
},
|
||||
else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
|
||||
@tagName(e),
|
||||
}),
|
||||
@ -299,6 +374,62 @@ pub const DeclGen = struct {
|
||||
try dg.renderType(w, t.elemType());
|
||||
try w.writeAll(" *");
|
||||
},
|
||||
.Optional => {
|
||||
var opt_buf: Type.Payload.ElemType = undefined;
|
||||
const child_type = t.optionalChild(&opt_buf);
|
||||
if (t.isPtrLikeOptional()) {
|
||||
return dg.renderType(w, child_type);
|
||||
} else if (dg.typedefs.get(t)) |some| {
|
||||
return w.writeAll(some.name);
|
||||
}
|
||||
|
||||
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
||||
defer buffer.deinit();
|
||||
const bw = buffer.writer();
|
||||
|
||||
try bw.writeAll("typedef struct { ");
|
||||
try dg.renderType(bw, child_type);
|
||||
try bw.writeAll(" payload; bool is_null; } ");
|
||||
const name_index = buffer.items.len;
|
||||
try bw.print("zig_opt_{s}_t;\n", .{typeToCIdentifier(child_type)});
|
||||
|
||||
const rendered = buffer.toOwnedSlice();
|
||||
errdefer dg.typedefs.allocator.free(rendered);
|
||||
const name = rendered[name_index .. rendered.len - 2];
|
||||
|
||||
try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.ErrorSet => {
|
||||
comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2);
|
||||
try w.writeAll("uint16_t");
|
||||
},
|
||||
.ErrorUnion => {
|
||||
if (dg.typedefs.get(t)) |some| {
|
||||
return w.writeAll(some.name);
|
||||
}
|
||||
const child_type = t.errorUnionChild();
|
||||
const set_type = t.errorUnionSet();
|
||||
|
||||
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
||||
defer buffer.deinit();
|
||||
const bw = buffer.writer();
|
||||
|
||||
try bw.writeAll("typedef struct { ");
|
||||
try dg.renderType(bw, child_type);
|
||||
try bw.writeAll(" payload; uint16_t error; } ");
|
||||
const name_index = buffer.items.len;
|
||||
try bw.print("zig_err_union_{s}_{s}_t;\n", .{ typeToCIdentifier(set_type), typeToCIdentifier(child_type) });
|
||||
|
||||
const rendered = buffer.toOwnedSlice();
|
||||
errdefer dg.typedefs.allocator.free(rendered);
|
||||
const name = rendered[name_index .. rendered.len - 2];
|
||||
|
||||
try dg.typedefs.ensureCapacity(dg.typedefs.capacity() + 1);
|
||||
try w.writeAll(name);
|
||||
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
||||
},
|
||||
.Null, .Undefined => unreachable, // must be const or comptime
|
||||
else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
|
||||
@tagName(e),
|
||||
@ -429,6 +560,21 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
|
||||
.bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
|
||||
.xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
|
||||
.not => try genUnOp(o, inst.castTag(.not).?, "!"),
|
||||
.is_null => try genIsNull(o, inst.castTag(.is_null).?),
|
||||
.is_non_null => try genIsNull(o, inst.castTag(.is_non_null).?),
|
||||
.is_null_ptr => try genIsNull(o, inst.castTag(.is_null_ptr).?),
|
||||
.is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?),
|
||||
.wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?),
|
||||
.optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?),
|
||||
.optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
|
||||
.is_err => try genIsErr(o, inst.castTag(.is_err).?),
|
||||
.is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
|
||||
.unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
|
||||
.unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
|
||||
.unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
|
||||
.unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
|
||||
.wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
|
||||
.wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
|
||||
else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
|
||||
};
|
||||
switch (result_value) {
|
||||
@ -802,6 +948,130 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
|
||||
return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
|
||||
}
|
||||
|
||||
fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr;
|
||||
const operator = if (invert_logic) "!=" else "==";
|
||||
const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else "";
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
||||
try writer.writeAll(" = (");
|
||||
try o.writeCValue(writer, operand);
|
||||
|
||||
if (inst.operand.ty.isPtrLikeOptional()) {
|
||||
// operand is a regular pointer, test `operand !=/== NULL`
|
||||
try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator });
|
||||
} else {
|
||||
try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator });
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer)
|
||||
inst.operand.ty.elemType()
|
||||
else
|
||||
inst.operand.ty;
|
||||
|
||||
if (opt_ty.isPtrLikeOptional()) {
|
||||
// the operand is just a regular pointer, no need to do anything special.
|
||||
// *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C
|
||||
return operand;
|
||||
}
|
||||
|
||||
const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
||||
const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.print(" = {s}(", .{maybe_addrof});
|
||||
try o.writeCValue(writer, operand);
|
||||
|
||||
try writer.print("){s}payload;\n", .{maybe_deref});
|
||||
return local;
|
||||
}
|
||||
|
||||
// *(E!T) -> E NOT *E
|
||||
fn genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = (");
|
||||
try o.writeCValue(writer, operand);
|
||||
|
||||
try writer.print("){s}error;\n", .{maybe_deref});
|
||||
return local;
|
||||
}
|
||||
fn genUnwrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
||||
const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.print(" = {s}(", .{maybe_addrof});
|
||||
try o.writeCValue(writer, operand);
|
||||
|
||||
try writer.print("){s}payload;\n", .{maybe_deref});
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
if (inst.base.ty.isPtrLikeOptional()) {
|
||||
// the operand is just a regular pointer, no need to do anything special.
|
||||
return operand;
|
||||
}
|
||||
|
||||
// .wrap_optional is used to convert non-optionals into optionals so it can never be null.
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = { .is_null = false, .payload =");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll("};\n");
|
||||
return local;
|
||||
}
|
||||
fn genWrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = { .error = ");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(" };\n");
|
||||
return local;
|
||||
}
|
||||
fn genWrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const local = try o.allocLocal(inst.base.ty, .Const);
|
||||
try writer.writeAll(" = { .error = 0, .payload = ");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.writeAll(" };\n");
|
||||
return local;
|
||||
}
|
||||
|
||||
fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
||||
const writer = o.writer();
|
||||
const maybe_deref = if (inst.base.tag == .is_err_ptr) "[0]" else "";
|
||||
const operand = try o.resolveInst(inst.operand);
|
||||
|
||||
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
||||
try writer.writeAll(" = (");
|
||||
try o.writeCValue(writer, operand);
|
||||
try writer.print("){s}.error != 0;\n", .{maybe_deref});
|
||||
return local;
|
||||
}
|
||||
|
||||
fn IndentWriter(comptime UnderlyingWriter: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
|
||||
@ -9,6 +9,7 @@ const codegen = @import("../codegen/c.zig");
|
||||
const link = @import("../link.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const C = @This();
|
||||
const Type = @import("../type.zig").Type;
|
||||
|
||||
pub const base_tag: link.File.Tag = .c;
|
||||
pub const zig_h = @embedFile("C/zig.h");
|
||||
@ -28,9 +29,11 @@ pub const DeclBlock = struct {
|
||||
/// Per-function data.
|
||||
pub const FnBlock = struct {
|
||||
fwd_decl: std.ArrayListUnmanaged(u8),
|
||||
typedefs: codegen.TypedefMap.Unmanaged,
|
||||
|
||||
pub const empty: FnBlock = .{
|
||||
.fwd_decl = .{},
|
||||
.typedefs = .{},
|
||||
};
|
||||
};
|
||||
|
||||
@ -74,6 +77,11 @@ pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
|
||||
pub fn freeDecl(self: *C, decl: *Module.Decl) void {
|
||||
decl.link.c.code.deinit(self.base.allocator);
|
||||
decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
|
||||
var it = decl.fn_link.c.typedefs.iterator();
|
||||
while (it.next()) |some| {
|
||||
self.base.allocator.free(some.value.rendered);
|
||||
}
|
||||
decl.fn_link.c.typedefs.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
@ -81,8 +89,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
defer tracy.end();
|
||||
|
||||
const fwd_decl = &decl.fn_link.c.fwd_decl;
|
||||
const typedefs = &decl.fn_link.c.typedefs;
|
||||
const code = &decl.link.c.code;
|
||||
fwd_decl.shrinkRetainingCapacity(0);
|
||||
{
|
||||
var it = typedefs.iterator();
|
||||
while (it.next()) |entry| {
|
||||
module.gpa.free(entry.value.rendered);
|
||||
}
|
||||
}
|
||||
typedefs.clearRetainingCapacity();
|
||||
code.shrinkRetainingCapacity(0);
|
||||
|
||||
var object: codegen.Object = .{
|
||||
@ -91,6 +107,7 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
.typedefs = typedefs.promote(module.gpa),
|
||||
},
|
||||
.gpa = module.gpa,
|
||||
.code = code.toManaged(module.gpa),
|
||||
@ -98,9 +115,16 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
||||
};
|
||||
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
||||
defer object.value_map.deinit();
|
||||
defer object.code.deinit();
|
||||
defer object.dg.fwd_decl.deinit();
|
||||
defer {
|
||||
object.value_map.deinit();
|
||||
object.code.deinit();
|
||||
object.dg.fwd_decl.deinit();
|
||||
var it = object.dg.typedefs.iterator();
|
||||
while (it.next()) |some| {
|
||||
module.gpa.free(some.value.rendered);
|
||||
}
|
||||
object.dg.typedefs.deinit();
|
||||
}
|
||||
|
||||
codegen.genDecl(&object) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
@ -111,6 +135,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
};
|
||||
|
||||
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
||||
typedefs.* = object.dg.typedefs.unmanaged;
|
||||
object.dg.typedefs.unmanaged = .{};
|
||||
code.* = object.code.moveToUnmanaged();
|
||||
|
||||
// Free excess allocated memory for this Decl.
|
||||
@ -142,7 +168,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
||||
defer all_buffers.deinit();
|
||||
|
||||
// This is at least enough until we get to the function bodies without error handling.
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 2);
|
||||
|
||||
var file_size: u64 = zig_h.len;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
@ -150,9 +176,26 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
||||
.iov_len = zig_h.len,
|
||||
});
|
||||
|
||||
var fn_count: usize = 0;
|
||||
var err_typedef_buf = std.ArrayList(u8).init(comp.gpa);
|
||||
defer err_typedef_buf.deinit();
|
||||
const err_typedef_writer = err_typedef_buf.writer();
|
||||
const err_typedef_item = all_buffers.addOneAssumeCapacity();
|
||||
|
||||
// Forward decls and non-functions first.
|
||||
render_errors: {
|
||||
if (module.global_error_set.size == 0) break :render_errors;
|
||||
var it = module.global_error_set.iterator();
|
||||
while (it.next()) |entry| {
|
||||
// + 1 because 0 represents no error
|
||||
try err_typedef_writer.print("#define zig_error_{s} {d}\n", .{ entry.key, entry.value + 1 });
|
||||
}
|
||||
try err_typedef_writer.writeByte('\n');
|
||||
}
|
||||
|
||||
var fn_count: usize = 0;
|
||||
var typedefs = std.HashMap(Type, []const u8, Type.hash, Type.eql, std.hash_map.default_max_load_percentage).init(comp.gpa);
|
||||
defer typedefs.deinit();
|
||||
|
||||
// Typedefs, forward decls and non-functions first.
|
||||
// TODO: performance investigation: would keeping a list of Decls that we should
|
||||
// generate, rather than querying here, be faster?
|
||||
for (module.decl_table.items()) |kv| {
|
||||
@ -161,6 +204,16 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
||||
.most_recent => |tvm| {
|
||||
const buf = buf: {
|
||||
if (tvm.typed_value.val.castTag(.function)) |_| {
|
||||
var it = decl.fn_link.c.typedefs.iterator();
|
||||
while (it.next()) |new| {
|
||||
if (typedefs.get(new.key)) |previous| {
|
||||
try err_typedef_writer.print("typedef {s} {s};\n", .{ previous, new.value.name });
|
||||
} else {
|
||||
try typedefs.ensureCapacity(typedefs.capacity() + 1);
|
||||
try err_typedef_writer.writeAll(new.value.rendered);
|
||||
typedefs.putAssumeCapacityNoClobber(new.key, new.value.name);
|
||||
}
|
||||
}
|
||||
fn_count += 1;
|
||||
break :buf decl.fn_link.c.fwd_decl.items;
|
||||
} else {
|
||||
@ -177,6 +230,12 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
||||
}
|
||||
}
|
||||
|
||||
err_typedef_item.* = .{
|
||||
.iov_base = err_typedef_buf.items.ptr,
|
||||
.iov_len = err_typedef_buf.items.len,
|
||||
};
|
||||
file_size += err_typedef_buf.items.len;
|
||||
|
||||
// Now the function bodies.
|
||||
try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);
|
||||
for (module.decl_table.items()) |kv| {
|
||||
|
||||
@ -868,11 +868,10 @@ pub const TestContext = struct {
|
||||
std.testing.zig_exe_path,
|
||||
"run",
|
||||
"-cflags",
|
||||
"-std=c89",
|
||||
"-std=c99",
|
||||
"-pedantic",
|
||||
"-Werror",
|
||||
"-Wno-incompatible-library-redeclaration", // https://github.com/ziglang/zig/issues/875
|
||||
"-Wno-declaration-after-statement",
|
||||
"--",
|
||||
"-lc",
|
||||
exe_path,
|
||||
|
||||
25
src/type.zig
25
src/type.zig
@ -1686,8 +1686,8 @@ pub const Type = extern union {
|
||||
return ty.optionalChild(&buf).isValidVarType(is_extern);
|
||||
},
|
||||
.Pointer, .Array => ty = ty.elemType(),
|
||||
.ErrorUnion => ty = ty.errorUnionChild(),
|
||||
|
||||
.ErrorUnion => @panic("TODO fn isValidVarType"),
|
||||
.Fn => @panic("TODO fn isValidVarType"),
|
||||
.Struct => @panic("TODO struct isValidVarType"),
|
||||
.Union => @panic("TODO union isValidVarType"),
|
||||
@ -1813,6 +1813,29 @@ pub const Type = extern union {
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that the type is an error union.
|
||||
pub fn errorUnionChild(self: Type) Type {
|
||||
return switch (self.tag()) {
|
||||
.anyerror_void_error_union => Type.initTag(.anyerror),
|
||||
.error_union => {
|
||||
const payload = self.castTag(.error_union).?;
|
||||
return payload.data.payload;
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn errorUnionSet(self: Type) Type {
|
||||
return switch (self.tag()) {
|
||||
.anyerror_void_error_union => Type.initTag(.anyerror),
|
||||
.error_union => {
|
||||
const payload = self.castTag(.error_union).?;
|
||||
return payload.data.error_set;
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts the type is an array or vector.
|
||||
pub fn arrayLen(self: Type) u64 {
|
||||
return switch (self.tag()) {
|
||||
|
||||
@ -2329,7 +2329,8 @@ fn zirCmp(
|
||||
return mod.constBool(scope, inst.base.src, std.mem.eql(u8, lval.castTag(.@"error").?.data.name, rval.castTag(.@"error").?.data.name) == (op == .eq));
|
||||
}
|
||||
}
|
||||
return mod.fail(scope, inst.base.src, "TODO implement equality comparison between runtime errors", .{});
|
||||
const b = try mod.requireRuntimeBlock(scope, inst.base.src);
|
||||
return mod.addBinOp(b, inst.base.src, Type.initTag(.bool), if (op == .eq) .cmp_eq else .cmp_neq, lhs, rhs);
|
||||
} else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
|
||||
// This operation allows any combination of integer and float types, regardless of the
|
||||
// signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
|
||||
|
||||
@ -244,6 +244,63 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
//{
|
||||
// var case = ctx.exeFromCompiledC("optionals", .{});
|
||||
|
||||
// // Simple while loop
|
||||
// case.addCompareOutput(
|
||||
// \\export fn main() c_int {
|
||||
// \\ var count: c_int = 0;
|
||||
// \\ var opt_ptr: ?*c_int = &count;
|
||||
// \\ while (opt_ptr) |_| : (count += 1) {
|
||||
// \\ if (count == 4) opt_ptr = null;
|
||||
// \\ }
|
||||
// \\ return count - 5;
|
||||
// \\}
|
||||
// , "");
|
||||
|
||||
// // Same with non pointer optionals
|
||||
// case.addCompareOutput(
|
||||
// \\export fn main() c_int {
|
||||
// \\ var count: c_int = 0;
|
||||
// \\ var opt_ptr: ?c_int = count;
|
||||
// \\ while (opt_ptr) |_| : (count += 1) {
|
||||
// \\ if (count == 4) opt_ptr = null;
|
||||
// \\ }
|
||||
// \\ return count - 5;
|
||||
// \\}
|
||||
// , "");
|
||||
//}
|
||||
{
|
||||
var case = ctx.exeFromCompiledC("errors", .{});
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var e1 = error.Foo;
|
||||
\\ var e2 = error.Bar;
|
||||
\\ assert(e1 != e2);
|
||||
\\ assert(e1 == error.Foo);
|
||||
\\ assert(e2 == error.Bar);
|
||||
\\ return 0;
|
||||
\\}
|
||||
\\fn assert(b: bool) void {
|
||||
\\ if (!b) unreachable;
|
||||
\\}
|
||||
, "");
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var e: anyerror!c_int = 0;
|
||||
\\ const i = e catch 69;
|
||||
\\ return i;
|
||||
\\}
|
||||
, "");
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ var e: anyerror!c_int = error.Foo;
|
||||
\\ const i = e catch 69;
|
||||
\\ return 69 - i;
|
||||
\\}
|
||||
, "");
|
||||
}
|
||||
ctx.c("empty start function", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ unreachable;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user