Commit 47531b7d93
Changed files (6)
src/ir.zig
@@ -255,6 +255,9 @@ pub const Inst = struct {
}
/// Returns `null` if runtime-known.
+ /// Should be called by codegen, not by Sema. Sema functions should call
+ /// `resolvePossiblyUndefinedValue` or `resolveDefinedValue` instead.
+ /// TODO audit Sema code for violations to the above guidance.
pub fn value(base: *Inst) ?Value {
if (base.ty.onePossibleValue()) |opv| return opv;
src/Module.zig
@@ -456,6 +456,16 @@ pub const Decl = struct {
return struct_obj;
}
+ /// If the Decl has a value and it is a union, return it,
+ /// otherwise null.
+ pub fn getUnion(decl: *Decl) ?*Union {
+ if (!decl.has_tv) return null;
+ const ty = (decl.val.castTag(.ty) orelse return null).data;
+ const union_obj = (ty.cast(Type.Payload.Union) orelse return null).data;
+ if (union_obj.owner_decl != decl) return null;
+ return union_obj;
+ }
+
/// If the Decl has a value and it is a function, return it,
/// otherwise null.
pub fn getFunction(decl: *Decl) ?*Fn {
@@ -571,6 +581,18 @@ pub const Struct = struct {
.lazy = .{ .node_offset = s.node_offset },
};
}
+
+ pub fn haveFieldTypes(s: Struct) bool {
+ return switch (s.status) {
+ .none,
+ .field_types_wip,
+ => false,
+ .have_field_types,
+ .layout_wip,
+ .have_layout,
+ => true,
+ };
+ }
};
/// Represents the data that an enum declaration provides, when the fields
@@ -624,6 +646,52 @@ pub const EnumFull = struct {
}
};
+pub const Union = struct {
+ /// The Decl that corresponds to the union itself.
+ owner_decl: *Decl,
+ /// An enum type which is used for the tag of the union.
+ /// This type is created even for untagged unions, even when the memory
+ /// layout does not store the tag.
+ /// Whether zig chooses this type or the user specifies it, it is stored here.
+ /// This will be set to the null type until status is `have_field_types`.
+ tag_ty: Type,
+ /// Set of field names in declaration order.
+ fields: std.StringArrayHashMapUnmanaged(Field),
+ /// Represents the declarations inside this union.
+ namespace: Scope.Namespace,
+ /// Offset from `owner_decl`, points to the union decl AST node.
+ node_offset: i32,
+ /// Index of the union_decl ZIR instruction.
+ zir_index: Zir.Inst.Index,
+
+ layout: std.builtin.TypeInfo.ContainerLayout,
+ status: enum {
+ none,
+ field_types_wip,
+ have_field_types,
+ layout_wip,
+ have_layout,
+ },
+
+ pub const Field = struct {
+ /// undefined until `status` is `have_field_types` or `have_layout`.
+ ty: Type,
+ abi_align: Value,
+ };
+
+ pub fn getFullyQualifiedName(s: *Union, gpa: *Allocator) ![]u8 {
+ return s.owner_decl.getFullyQualifiedName(gpa);
+ }
+
+ pub fn srcLoc(self: Union) SrcLoc {
+ return .{
+ .file_scope = self.owner_decl.getFileScope(),
+ .parent_decl_node = self.owner_decl.src_node,
+ .lazy = .{ .node_offset = self.node_offset },
+ };
+ }
+};
+
/// Some Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
/// Extern functions do not have this data structure; they are represented by
/// the `Decl` only, with a `Value` tag of `extern_fn`.
@@ -2401,6 +2469,7 @@ pub fn astGenFile(mod: *Module, file: *Scope.File, prog_node: *std.Progress.Node
/// Patch ups:
/// * Struct.zir_index
+/// * Decl.zir_index
/// * Fn.zir_body_inst
/// * Decl.zir_decl_index
/// * Decl.name
@@ -2479,6 +2548,13 @@ fn updateZirRefs(gpa: *Allocator, file: *Scope.File, old_zir: Zir) !void {
};
}
+ if (decl.getUnion()) |union_obj| {
+ union_obj.zir_index = inst_map.get(union_obj.zir_index) orelse {
+ try file.deleted_decls.append(gpa, decl);
+ continue;
+ };
+ }
+
if (decl.getFunction()) |func| {
func.zir_body_inst = inst_map.get(func.zir_body_inst) orelse {
try file.deleted_decls.append(gpa, decl);
@@ -2769,7 +2845,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
};
if (decl.isRoot()) {
- log.debug("semaDecl root {*} ({s})", .{decl, decl.name});
+ log.debug("semaDecl root {*} ({s})", .{ decl, decl.name });
const main_struct_inst = zir.getMainStruct();
const struct_obj = decl.getStruct().?;
try sema.analyzeStructDecl(decl, main_struct_inst, struct_obj);
@@ -4271,7 +4347,7 @@ pub const SwitchProngSrc = union(enum) {
}
};
-pub fn analyzeStructFields(mod: *Module, struct_obj: *Module.Struct) InnerError!void {
+pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -4284,26 +4360,9 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Module.Struct) InnerError!
const decls_len = extra.data.decls_len;
// Skip over decls.
- var extra_index = extra.end;
- {
- const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable;
- var bit_bag_index: usize = extra_index;
- extra_index += bit_bags_count;
- var cur_bit_bag: u32 = undefined;
- var decl_i: u32 = 0;
- while (decl_i < decls_len) : (decl_i += 1) {
- if (decl_i % 8 == 0) {
- cur_bit_bag = zir.extra[bit_bag_index];
- bit_bag_index += 1;
- }
- const flags = @truncate(u4, cur_bit_bag);
- cur_bit_bag >>= 4;
-
- extra_index += 7; // src_hash(4) + line(1) + name(1) + value(1)
- extra_index += @truncate(u1, flags >> 2);
- extra_index += @truncate(u1, flags >> 3);
- }
- }
+ var decls_it = zir.declIterator(struct_obj.zir_index);
+ while (decls_it.next()) |_| {}
+ var extra_index = decls_it.extra_index;
const body = zir.extra[extra_index..][0..extra.data.body_len];
if (fields_len == 0) {
@@ -4417,6 +4476,141 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Module.Struct) InnerError!
}
}
+pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const gpa = mod.gpa;
+ const zir = union_obj.owner_decl.namespace.file_scope.zir;
+ const inst_data = zir.instructions.items(.data)[union_obj.zir_index].pl_node;
+ const src = inst_data.src();
+ const extra = zir.extraData(Zir.Inst.UnionDecl, inst_data.payload_index);
+ const fields_len = extra.data.fields_len;
+ const decls_len = extra.data.decls_len;
+
+ // Skip over decls.
+ var decls_it = zir.declIterator(union_obj.zir_index);
+ while (decls_it.next()) |_| {}
+ var extra_index = decls_it.extra_index;
+
+ const body = zir.extra[extra_index..][0..extra.data.body_len];
+ if (fields_len == 0) {
+ assert(body.len == 0);
+ return;
+ }
+ extra_index += body.len;
+
+ var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa);
+ defer union_obj.owner_decl.value_arena.?.* = decl_arena.state;
+
+ try union_obj.fields.ensureCapacity(&decl_arena.allocator, fields_len);
+
+ // We create a block for the field type instructions because they
+ // may need to reference Decls from inside the struct namespace.
+ // Within the field type, default value, and alignment expressions, the "owner decl"
+ // should be the struct itself. Thus we need a new Sema.
+ var sema: Sema = .{
+ .mod = mod,
+ .gpa = gpa,
+ .arena = &decl_arena.allocator,
+ .code = zir,
+ .inst_map = try gpa.alloc(*ir.Inst, zir.instructions.len),
+ .owner_decl = union_obj.owner_decl,
+ .namespace = &union_obj.namespace,
+ .owner_func = null,
+ .func = null,
+ .param_inst_list = &.{},
+ };
+ defer gpa.free(sema.inst_map);
+
+ var block: Scope.Block = .{
+ .parent = null,
+ .sema = &sema,
+ .src_decl = union_obj.owner_decl,
+ .instructions = .{},
+ .inlining = null,
+ .is_comptime = true,
+ };
+ defer assert(block.instructions.items.len == 0); // should all be comptime instructions
+
+ _ = try sema.analyzeBody(&block, body);
+
+ var auto_enum_tag: ?bool = null;
+
+ const bits_per_field = 4;
+ const fields_per_u32 = 32 / bits_per_field;
+ const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
+ var bit_bag_index: usize = extra_index;
+ extra_index += bit_bags_count;
+ var cur_bit_bag: u32 = undefined;
+ var field_i: u32 = 0;
+ while (field_i < fields_len) : (field_i += 1) {
+ if (field_i % fields_per_u32 == 0) {
+ cur_bit_bag = zir.extra[bit_bag_index];
+ bit_bag_index += 1;
+ }
+ const has_type = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+ const has_align = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+ const has_tag = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+ const unused = @truncate(u1, cur_bit_bag) != 0;
+ cur_bit_bag >>= 1;
+
+ if (auto_enum_tag == null) {
+ auto_enum_tag = unused;
+ }
+
+ const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
+ extra_index += 1;
+
+ const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
+ const field_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+ extra_index += 1;
+ break :blk field_type_ref;
+ } else .none;
+
+ const align_ref: Zir.Inst.Ref = if (has_align) blk: {
+ const align_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+ extra_index += 1;
+ break :blk align_ref;
+ } else .none;
+
+ const tag_ref: Zir.Inst.Ref = if (has_tag) blk: {
+ const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+ extra_index += 1;
+ break :blk tag_ref;
+ } else .none;
+
+ // This string needs to outlive the ZIR code.
+ const field_name = try decl_arena.allocator.dupe(u8, field_name_zir);
+ const field_ty: Type = if (field_type_ref == .none)
+ Type.initTag(.void)
+ else
+ // TODO: if we need to report an error here, use a source location
+ // that points to this type expression rather than the union.
+ // But only resolve the source location if we need to emit a compile error.
+ try sema.resolveType(&block, src, field_type_ref);
+
+ const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
+ assert(!gop.found_existing);
+ gop.entry.value = .{
+ .ty = field_ty,
+ .abi_align = Value.initTag(.abi_align_default),
+ };
+
+ if (align_ref != .none) {
+ // TODO: if we need to report an error here, use a source location
+ // that points to this alignment expression rather than the struct.
+ // But only resolve the source location if we need to emit a compile error.
+ gop.entry.value.abi_align = (try sema.resolveInstConst(&block, src, align_ref)).val;
+ }
+ }
+
+ // TODO resolve the union tag type
+}
+
/// Called from `performAllTheWork`, after all AstGen workers have finished,
/// and before the main semantic analysis loop begins.
pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
src/Sema.zig
@@ -591,7 +591,7 @@ fn resolveConstValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *i
}
fn resolveDefinedValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base: *ir.Inst) !?Value {
- if (base.value()) |val| {
+ if (try sema.resolvePossiblyUndefinedValue(block, src, base)) |val| {
if (val.isUndef()) {
return sema.failWithUseOfUndef(block, src);
}
@@ -600,6 +600,19 @@ fn resolveDefinedValue(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, base:
return null;
}
+fn resolvePossiblyUndefinedValue(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ base: *ir.Inst,
+) !?Value {
+ if (try sema.typeHasOnePossibleValue(block, src, base.ty)) |opv| {
+ return opv;
+ }
+ const inst = base.castTag(.constant) orelse return null;
+ return inst.val;
+}
+
fn failWithNeededComptime(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) InnerError {
return sema.mod.fail(&block.base, src, "unable to resolve comptime value", .{});
}
@@ -889,9 +902,40 @@ fn zirUnionDecl(
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
- const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
+ const extra = sema.code.extraData(Zir.Inst.UnionDecl, inst_data.payload_index);
+ const decls_len = extra.data.decls_len;
- return sema.mod.fail(&block.base, sema.src, "TODO implement zirUnionDecl", .{});
+ var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
+
+ const union_obj = try new_decl_arena.allocator.create(Module.Union);
+ const union_ty = try Type.Tag.@"union".create(&new_decl_arena.allocator, union_obj);
+ const union_val = try Value.Tag.ty.create(&new_decl_arena.allocator, union_ty);
+ const new_decl = try sema.mod.createAnonymousDecl(&block.base, .{
+ .ty = Type.initTag(.type),
+ .val = union_val,
+ });
+ union_obj.* = .{
+ .owner_decl = new_decl,
+ .tag_ty = Type.initTag(.@"null"),
+ .fields = .{},
+ .node_offset = inst_data.src_node,
+ .zir_index = inst,
+ .layout = layout,
+ .status = .none,
+ .namespace = .{
+ .parent = sema.owner_decl.namespace,
+ .ty = union_ty,
+ .file_scope = block.getFileScope(),
+ },
+ };
+ std.log.scoped(.module).debug("create union {*} owned by {*} ({s})", .{
+ &union_obj.namespace, new_decl, new_decl.name,
+ });
+
+ _ = try sema.mod.scanNamespace(&union_obj.namespace, extra.end, decls_len, new_decl);
+
+ try new_decl.finalizeNewArena(&new_decl_arena);
+ return sema.analyzeDeclVal(block, src, new_decl);
}
fn zirOpaqueDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
@@ -1277,6 +1321,33 @@ fn failWithBadFieldAccess(
return mod.failWithOwnedErrorMsg(&block.base, msg);
}
+fn failWithBadUnionFieldAccess(
+ sema: *Sema,
+ block: *Scope.Block,
+ union_obj: *Module.Union,
+ field_src: LazySrcLoc,
+ field_name: []const u8,
+) InnerError {
+ const mod = sema.mod;
+ const gpa = sema.gpa;
+
+ const fqn = try union_obj.getFullyQualifiedName(gpa);
+ defer gpa.free(fqn);
+
+ const msg = msg: {
+ const msg = try mod.errMsg(
+ &block.base,
+ field_src,
+ "no field named '{s}' in union '{s}'",
+ .{ field_name, fqn },
+ );
+ errdefer msg.destroy(gpa);
+ try mod.errNoteNonLazy(union_obj.srcLoc(), msg, "union declared here", .{});
+ break :msg msg;
+ };
+ return mod.failWithOwnedErrorMsg(&block.base, msg);
+}
+
fn zirStoreToBlockPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -1484,7 +1555,7 @@ fn zirCompileLog(
if (i != 0) try writer.print(", ", .{});
const arg = try sema.resolveInst(arg_ref);
- if (arg.value()) |val| {
+ if (try sema.resolvePossiblyUndefinedValue(block, src, arg)) |val| {
try writer.print("@as({}, {})", .{ arg.ty, val });
} else {
try writer.print("@as({}, [runtime value])", .{arg.ty});
@@ -2204,21 +2275,25 @@ fn zirErrorToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerEr
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const op = try sema.resolveInst(inst_data.operand);
const op_coerced = try sema.coerce(block, Type.initTag(.anyerror), op, operand_src);
+ const result_ty = Type.initTag(.u16);
- if (op_coerced.value()) |val| {
+ if (try sema.resolvePossiblyUndefinedValue(block, src, op_coerced)) |val| {
+ if (val.isUndef()) {
+ return sema.mod.constUndef(sema.arena, src, result_ty);
+ }
const payload = try sema.arena.create(Value.Payload.U64);
payload.* = .{
.base = .{ .tag = .int_u64 },
.data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value,
};
return sema.mod.constInst(sema.arena, src, .{
- .ty = Type.initTag(.u16),
+ .ty = result_ty,
.val = Value.initPayload(&payload.base),
});
}
try sema.requireRuntimeBlock(block, src);
- return block.addUnOp(src, Type.initTag(.u16), .error_to_int, op_coerced);
+ return block.addUnOp(src, result_ty, .error_to_int, op_coerced);
}
fn zirIntToError(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
@@ -2377,7 +2452,7 @@ fn zirEnumToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErr
var int_tag_type_buffer: Type.Payload.Bits = undefined;
const int_tag_ty = try enum_tag.ty.intTagType(&int_tag_type_buffer).copy(arena);
- if (enum_tag.ty.onePossibleValue()) |opv| {
+ if (try sema.typeHasOnePossibleValue(block, src, enum_tag.ty)) |opv| {
return mod.constInst(arena, src, .{
.ty = int_tag_ty,
.val = opv,
@@ -2729,13 +2804,18 @@ fn zirFunc(
src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data;
}
+ const cc: std.builtin.CallingConvention = if (sema.owner_decl.is_exported)
+ .C
+ else
+ .Unspecified;
+
return sema.funcCommon(
block,
inst_data.src_node,
param_types,
body_inst,
extra.data.return_type,
- .Unspecified,
+ cc,
Value.initTag(.null_value),
false,
inferred_error_set,
@@ -4268,10 +4348,7 @@ fn zirBitwise(
if (casted_lhs.value()) |lhs_val| {
if (casted_rhs.value()) |rhs_val| {
if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.mod.constInst(sema.arena, src, .{
- .ty = resolved_type,
- .val = Value.initTag(.undef),
- });
+ return sema.mod.constUndef(sema.arena, src, resolved_type);
}
return sema.mod.fail(&block.base, src, "TODO implement comptime bitwise operations", .{});
}
@@ -4395,10 +4472,7 @@ fn analyzeArithmetic(
if (casted_lhs.value()) |lhs_val| {
if (casted_rhs.value()) |rhs_val| {
if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.mod.constInst(sema.arena, src, .{
- .ty = resolved_type,
- .val = Value.initTag(.undef),
- });
+ return sema.mod.constUndef(sema.arena, src, resolved_type);
}
// incase rhs is 0, simply return lhs without doing any calculations
// TODO Once division is implemented we should throw an error when dividing by 0.
@@ -4635,10 +4709,7 @@ fn zirCmp(
if (casted_lhs.value()) |lhs_val| {
if (casted_rhs.value()) |rhs_val| {
if (lhs_val.isUndef() or rhs_val.isUndef()) {
- return sema.mod.constInst(sema.arena, src, .{
- .ty = resolved_type,
- .val = Value.initTag(.undef),
- });
+ return sema.mod.constUndef(sema.arena, src, resolved_type);
}
const result = lhs_val.compare(op, rhs_val);
return sema.mod.constBool(sema.arena, src, result);
@@ -4721,7 +4792,7 @@ fn zirTypeInfo(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErro
@enumToInt(ty.fnCallingConvention()),
);
// alignment: comptime_int,
- field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.ptrAlignment(target));
+ field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target));
// is_generic: bool,
field_values[2] = Value.initTag(.bool_false); // TODO
// is_var_args: bool,
@@ -6033,6 +6104,7 @@ fn namedFieldPtr(
}
},
.Struct => return sema.analyzeStructFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
+ .Union => return sema.analyzeUnionFieldPtr(block, src, object_ptr, field_name, field_name_src, elem_ty),
else => {},
}
return mod.fail(&block.base, src, "type '{}' does not support field access", .{elem_ty});
@@ -6083,11 +6155,58 @@ fn analyzeStructFieldPtr(
return sema.failWithBadFieldAccess(block, struct_obj, field_name_src, field_name);
const field = struct_obj.fields.entries.items[field_index].value;
const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One);
- // TODO comptime field access
+
+ if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
+ return mod.constInst(arena, src, .{
+ .ty = ptr_field_ty,
+ .val = try Value.Tag.field_ptr.create(arena, .{
+ .container_ptr = struct_ptr_val,
+ .field_index = field_index,
+ }),
+ });
+ }
+
try sema.requireRuntimeBlock(block, src);
return block.addStructFieldPtr(src, ptr_field_ty, struct_ptr, @intCast(u32, field_index));
}
+fn analyzeUnionFieldPtr(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ union_ptr: *Inst,
+ field_name: []const u8,
+ field_name_src: LazySrcLoc,
+ unresolved_union_ty: Type,
+) InnerError!*Inst {
+ const mod = sema.mod;
+ const arena = sema.arena;
+ assert(unresolved_union_ty.zigTypeTag() == .Union);
+
+ const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty);
+ const union_obj = union_ty.cast(Type.Payload.Union).?.data;
+
+ const field_index = union_obj.fields.getIndex(field_name) orelse
+ return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name);
+
+ const field = union_obj.fields.entries.items[field_index].value;
+ const ptr_field_ty = try mod.simplePtrType(arena, field.ty, true, .One);
+
+ if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| {
+ // TODO detect inactive union field and emit compile error
+ return mod.constInst(arena, src, .{
+ .ty = ptr_field_ty,
+ .val = try Value.Tag.field_ptr.create(arena, .{
+ .container_ptr = union_ptr_val,
+ .field_index = field_index,
+ }),
+ });
+ }
+
+ try sema.requireRuntimeBlock(block, src);
+ return mod.fail(&block.base, src, "TODO implement runtime union field access", .{});
+}
+
fn elemPtr(
sema: *Sema,
block: *Scope.Block,
@@ -6382,7 +6501,7 @@ fn storePtr(
const elem_ty = ptr.ty.elemType();
const value = try sema.coerce(block, elem_ty, uncasted_value, src);
- if (elem_ty.onePossibleValue() != null)
+ if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null)
return;
// TODO handle comptime pointer writes
@@ -6477,7 +6596,7 @@ fn analyzeRef(
) InnerError!*Inst {
const ptr_type = try sema.mod.simplePtrType(sema.arena, operand.ty, false, .One);
- if (operand.value()) |val| {
+ if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |val| {
return sema.mod.constInst(sema.arena, src, .{
.ty = ptr_type,
.val = try Value.Tag.ref_val.create(sema.arena, val),
@@ -6499,10 +6618,10 @@ fn analyzeLoad(
.Pointer => ptr.ty.elemType(),
else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr.ty}),
};
- if (ptr.value()) |val| {
+ if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
return sema.mod.constInst(sema.arena, src, .{
.ty = elem_ty,
- .val = try val.pointerDeref(sema.arena),
+ .val = try ptr_val.pointerDeref(sema.arena),
});
}
@@ -6517,14 +6636,18 @@ fn analyzeIsNull(
operand: *Inst,
invert_logic: bool,
) InnerError!*Inst {
- if (operand.value()) |opt_val| {
+ const result_ty = Type.initTag(.bool);
+ if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |opt_val| {
+ if (opt_val.isUndef()) {
+ return sema.mod.constUndef(sema.arena, src, result_ty);
+ }
const is_null = opt_val.isNull();
const bool_value = if (invert_logic) !is_null else is_null;
return sema.mod.constBool(sema.arena, src, bool_value);
}
try sema.requireRuntimeBlock(block, src);
const inst_tag: Inst.Tag = if (invert_logic) .is_non_null else .is_null;
- return block.addUnOp(src, Type.initTag(.bool), inst_tag, operand);
+ return block.addUnOp(src, result_ty, inst_tag, operand);
}
fn analyzeIsErr(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, operand: *Inst) InnerError!*Inst {
@@ -6532,11 +6655,15 @@ fn analyzeIsErr(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, operand: *Ins
if (ot != .ErrorSet and ot != .ErrorUnion) return sema.mod.constBool(sema.arena, src, false);
if (ot == .ErrorSet) return sema.mod.constBool(sema.arena, src, true);
assert(ot == .ErrorUnion);
- if (operand.value()) |err_union| {
+ const result_ty = Type.initTag(.bool);
+ if (try sema.resolvePossiblyUndefinedValue(block, src, operand)) |err_union| {
+ if (err_union.isUndef()) {
+ return sema.mod.constUndef(sema.arena, src, result_ty);
+ }
return sema.mod.constBool(sema.arena, src, err_union.getError() != null);
}
try sema.requireRuntimeBlock(block, src);
- return block.addUnOp(src, Type.initTag(.bool), .is_err, operand);
+ return block.addUnOp(src, result_ty, .is_err, operand);
}
fn analyzeSlice(
@@ -6953,6 +7080,23 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type
.float_mode => return sema.resolveBuiltinTypeFields(block, src, ty, "FloatMode"),
.reduce_op => return sema.resolveBuiltinTypeFields(block, src, ty, "ReduceOp"),
.call_options => return sema.resolveBuiltinTypeFields(block, src, ty, "CallOptions"),
+
+ .@"union", .union_tagged => {
+ const union_obj = ty.cast(Type.Payload.Union).?.data;
+ switch (union_obj.status) {
+ .none => {},
+ .field_types_wip => {
+ return sema.mod.fail(&block.base, src, "union {} depends on itself", .{
+ ty,
+ });
+ },
+ .have_field_types, .have_layout, .layout_wip => return ty,
+ }
+ union_obj.status = .field_types_wip;
+ try sema.mod.analyzeUnionFields(union_obj);
+ union_obj.status = .have_field_types;
+ return ty;
+ },
else => return ty,
}
}
@@ -6994,3 +7138,150 @@ fn getBuiltinType(
const ty_inst = try sema.analyzeLoad(block, src, opt_ty_inst.?, src);
return sema.resolveAirAsType(block, src, ty_inst);
}
+
+/// There is another implementation of this in `Type.onePossibleValue`. This one
+/// in `Sema` is for calling during semantic analysis, and peforms field resolution
+/// to get the answer. The one in `Type` is for calling during codegen and asserts
+/// that the types are already resolved.
+fn typeHasOnePossibleValue(
+ sema: *Sema,
+ block: *Scope.Block,
+ src: LazySrcLoc,
+ starting_type: Type,
+) InnerError!?Value {
+ var ty = starting_type;
+ while (true) switch (ty.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .comptime_int,
+ .comptime_float,
+ .u8,
+ .i8,
+ .u16,
+ .i16,
+ .u32,
+ .i32,
+ .u64,
+ .i64,
+ .u128,
+ .i128,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .bool,
+ .type,
+ .anyerror,
+ .fn_noreturn_no_args,
+ .fn_void_no_args,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .function,
+ .single_const_pointer_to_comptime_int,
+ .array_sentinel,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .const_slice,
+ .mut_slice,
+ .c_void,
+ .optional,
+ .optional_single_mut_pointer,
+ .optional_single_const_pointer,
+ .enum_literal,
+ .anyerror_void_error_union,
+ .error_union,
+ .error_set,
+ .error_set_single,
+ .@"opaque",
+ .var_args_param,
+ .manyptr_u8,
+ .manyptr_const_u8,
+ .atomic_ordering,
+ .atomic_rmw_op,
+ .calling_convention,
+ .float_mode,
+ .reduce_op,
+ .call_options,
+ .export_options,
+ .extern_options,
+ .@"anyframe",
+ .anyframe_T,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .single_const_pointer,
+ .single_mut_pointer,
+ .pointer,
+ => return null,
+
+ .@"struct" => {
+ const resolved_ty = try sema.resolveTypeFields(block, src, ty);
+ const s = resolved_ty.castTag(.@"struct").?.data;
+ for (s.fields.entries.items) |entry| {
+ const field_ty = entry.value.ty;
+ if ((try sema.typeHasOnePossibleValue(block, src, field_ty)) == null) {
+ return null;
+ }
+ }
+ return Value.initTag(.empty_struct_value);
+ },
+ .enum_full => {
+ const resolved_ty = try sema.resolveTypeFields(block, src, ty);
+ const enum_full = resolved_ty.castTag(.enum_full).?.data;
+ if (enum_full.fields.count() == 1) {
+ return enum_full.values.entries.items[0].key;
+ } else {
+ return null;
+ }
+ },
+ .enum_simple => {
+ const resolved_ty = try sema.resolveTypeFields(block, src, ty);
+ const enum_simple = resolved_ty.castTag(.enum_simple).?.data;
+ if (enum_simple.fields.count() == 1) {
+ return Value.initTag(.zero);
+ } else {
+ return null;
+ }
+ },
+ .enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty,
+ .@"union" => {
+ return null; // TODO
+ },
+ .union_tagged => {
+ return null; // TODO
+ },
+
+ .empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value),
+ .void => return Value.initTag(.void_value),
+ .noreturn => return Value.initTag(.unreachable_value),
+ .@"null" => return Value.initTag(.null_value),
+ .@"undefined" => return Value.initTag(.undef),
+
+ .int_unsigned, .int_signed => {
+ if (ty.cast(Type.Payload.Bits).?.data == 0) {
+ return Value.initTag(.zero);
+ } else {
+ return null;
+ }
+ },
+ .vector, .array, .array_u8 => {
+ if (ty.arrayLen() == 0)
+ return Value.initTag(.empty_array);
+ ty = ty.elemType();
+ continue;
+ },
+
+ .inferred_alloc_const => unreachable,
+ .inferred_alloc_mut => unreachable,
+ };
+}
src/type.zig
@@ -122,6 +122,10 @@ pub const Type = extern union {
.reduce_op,
=> return .Enum,
+ .@"union",
+ .union_tagged,
+ => return .Union,
+
.var_args_param => unreachable, // can be any type
}
}
@@ -506,11 +510,18 @@ pub const Type = extern union {
}
return a.tag() == b.tag();
},
+ .Union => {
+ if (a.cast(Payload.Union)) |a_payload| {
+ if (b.cast(Payload.Union)) |b_payload| {
+ return a_payload.data == b_payload.data;
+ }
+ }
+ return a.tag() == b.tag();
+ },
.Opaque,
.Float,
.ErrorUnion,
.ErrorSet,
- .Union,
.BoundFn,
.Frame,
=> std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }),
@@ -735,6 +746,7 @@ pub const Type = extern union {
.error_set_single => return self.copyPayloadShallow(allocator, Payload.Name),
.empty_struct => return self.copyPayloadShallow(allocator, Payload.ContainerScope),
.@"struct" => return self.copyPayloadShallow(allocator, Payload.Struct),
+ .@"union", .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union),
.enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple),
.enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull),
.@"opaque" => return self.copyPayloadShallow(allocator, Payload.Opaque),
@@ -806,6 +818,10 @@ pub const Type = extern union {
const struct_obj = ty.castTag(.@"struct").?.data;
return struct_obj.owner_decl.renderFullyQualifiedName(writer);
},
+ .@"union", .union_tagged => {
+ const union_obj = ty.cast(Payload.Union).?.data;
+ return union_obj.owner_decl.renderFullyQualifiedName(writer);
+ },
.enum_full, .enum_nonexhaustive => {
const enum_full = ty.cast(Payload.EnumFull).?.data;
return enum_full.owner_decl.renderFullyQualifiedName(writer);
@@ -1151,6 +1167,27 @@ pub const Type = extern union {
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.hasCodeGenBits();
},
+ .@"union" => {
+ const union_obj = self.castTag(.@"union").?.data;
+ for (union_obj.fields.entries.items) |entry| {
+ if (entry.value.ty.hasCodeGenBits())
+ return true;
+ } else {
+ return false;
+ }
+ },
+ .union_tagged => {
+ const union_obj = self.castTag(.@"union").?.data;
+ if (union_obj.tag_ty.hasCodeGenBits()) {
+ return true;
+ }
+ for (union_obj.fields.entries.items) |entry| {
+ if (entry.value.ty.hasCodeGenBits())
+ return true;
+ } else {
+ return false;
+ }
+ },
// TODO lazy types
.array, .vector => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
@@ -1359,6 +1396,34 @@ pub const Type = extern union {
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.abiAlignment(target);
},
+ .union_tagged => {
+ const union_obj = self.castTag(.union_tagged).?.data;
+ var biggest: u32 = union_obj.tag_ty.abiAlignment(target);
+ for (union_obj.fields.entries.items) |entry| {
+ const field_ty = entry.value.ty;
+ if (!field_ty.hasCodeGenBits()) continue;
+ const field_align = field_ty.abiAlignment(target);
+ if (field_align > biggest) {
+ return field_align;
+ }
+ }
+ assert(biggest != 0);
+ return biggest;
+ },
+ .@"union" => {
+ const union_obj = self.castTag(.@"union").?.data;
+ var biggest: u32 = 0;
+ for (union_obj.fields.entries.items) |entry| {
+ const field_ty = entry.value.ty;
+ if (!field_ty.hasCodeGenBits()) continue;
+ const field_align = field_ty.abiAlignment(target);
+ if (field_align > biggest) {
+ return field_align;
+ }
+ }
+ assert(biggest != 0);
+ return biggest;
+ },
.c_void,
.void,
.type,
@@ -1411,6 +1476,9 @@ pub const Type = extern union {
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.abiSize(target);
},
+ .@"union", .union_tagged => {
+ @panic("TODO abiSize unions");
+ },
.u8,
.i8,
@@ -1570,6 +1638,9 @@ pub const Type = extern union {
const int_tag_ty = self.intTagType(&buffer);
return int_tag_ty.bitSize(target);
},
+ .@"union", .union_tagged => {
+ @panic("TODO bitSize unions");
+ },
.u8, .i8 => 8,
@@ -2263,6 +2334,8 @@ pub const Type = extern union {
};
}
+ /// During semantic analysis, instead call `Sema.typeHasOnePossibleValue` which
+ /// resolves field types rather than asserting they are already resolved.
pub fn onePossibleValue(starting_type: Type) ?Value {
var ty = starting_type;
while (true) switch (ty.tag()) {
@@ -2330,10 +2403,18 @@ pub const Type = extern union {
.extern_options,
.@"anyframe",
.anyframe_T,
+ .many_const_pointer,
+ .many_mut_pointer,
+ .c_const_pointer,
+ .c_mut_pointer,
+ .single_const_pointer,
+ .single_mut_pointer,
+ .pointer,
=> return null,
.@"struct" => {
const s = ty.castTag(.@"struct").?.data;
+ assert(s.haveFieldTypes());
for (s.fields.entries.items) |entry| {
const field_ty = entry.value.ty;
if (field_ty.onePossibleValue() == null) {
@@ -2359,6 +2440,12 @@ pub const Type = extern union {
}
},
.enum_nonexhaustive => ty = ty.castTag(.enum_nonexhaustive).?.data.tag_ty,
+ .@"union" => {
+ return null; // TODO
+ },
+ .union_tagged => {
+ return null; // TODO
+ },
.empty_struct, .empty_struct_literal => return Value.initTag(.empty_struct_value),
.void => return Value.initTag(.void_value),
@@ -2379,20 +2466,7 @@ pub const Type = extern union {
ty = ty.elemType();
continue;
},
- .many_const_pointer,
- .many_mut_pointer,
- .c_const_pointer,
- .c_mut_pointer,
- .single_const_pointer,
- .single_mut_pointer,
- => {
- ty = ty.castPointer().?.data;
- continue;
- },
- .pointer => {
- ty = ty.castTag(.pointer).?.data.pointee_type;
- continue;
- },
+
.inferred_alloc_const => unreachable,
.inferred_alloc_mut => unreachable,
};
@@ -2412,6 +2486,8 @@ pub const Type = extern union {
.enum_full => &self.castTag(.enum_full).?.data.namespace,
.empty_struct => self.castTag(.empty_struct).?.data,
.@"opaque" => &self.castTag(.@"opaque").?.data,
+ .@"union" => &self.castTag(.@"union").?.data.namespace,
+ .union_tagged => &self.castTag(.union_tagged).?.data.namespace,
else => null,
};
@@ -2612,6 +2688,10 @@ pub const Type = extern union {
const error_set = ty.castTag(.error_set).?.data;
return error_set.srcLoc();
},
+ .@"union", .union_tagged => {
+ const union_obj = ty.cast(Payload.Union).?.data;
+ return union_obj.srcLoc();
+ },
.atomic_ordering,
.atomic_rmw_op,
.calling_convention,
@@ -2643,6 +2723,10 @@ pub const Type = extern union {
const error_set = ty.castTag(.error_set).?.data;
return error_set.owner_decl;
},
+ .@"union", .union_tagged => {
+ const union_obj = ty.cast(Payload.Union).?.data;
+ return union_obj.owner_decl;
+ },
.@"opaque" => @panic("TODO"),
.atomic_ordering,
.atomic_rmw_op,
@@ -2801,6 +2885,8 @@ pub const Type = extern union {
empty_struct,
@"opaque",
@"struct",
+ @"union",
+ union_tagged,
enum_simple,
enum_full,
enum_nonexhaustive,
@@ -2902,6 +2988,7 @@ pub const Type = extern union {
.error_set_single => Payload.Name,
.@"opaque" => Payload.Opaque,
.@"struct" => Payload.Struct,
+ .@"union", .union_tagged => Payload.Union,
.enum_full, .enum_nonexhaustive => Payload.EnumFull,
.enum_simple => Payload.EnumSimple,
.empty_struct => Payload.ContainerScope,
@@ -3040,6 +3127,11 @@ pub const Type = extern union {
data: *Module.Struct,
};
+ pub const Union = struct {
+ base: Payload,
+ data: *Module.Union,
+ };
+
pub const EnumFull = struct {
base: Payload,
data: *Module.EnumFull,
src/value.zig
@@ -104,6 +104,7 @@ pub const Value = extern union {
/// Represents a pointer to a decl, not the value of the decl.
decl_ref,
elem_ptr,
+ field_ptr,
/// A slice of u8 whose memory is managed externally.
bytes,
/// This value is repeated some number of times. The amount of times to repeat
@@ -223,6 +224,7 @@ pub const Value = extern union {
.function => Payload.Function,
.variable => Payload.Variable,
.elem_ptr => Payload.ElemPtr,
+ .field_ptr => Payload.FieldPtr,
.float_16 => Payload.Float_16,
.float_32 => Payload.Float_32,
.float_64 => Payload.Float_64,
@@ -414,6 +416,18 @@ pub const Value = extern union {
};
return Value{ .ptr_otherwise = &new_payload.base };
},
+ .field_ptr => {
+ const payload = self.castTag(.field_ptr).?;
+ const new_payload = try allocator.create(Payload.FieldPtr);
+ new_payload.* = .{
+ .base = payload.base,
+ .data = .{
+ .container_ptr = try payload.data.container_ptr.copy(allocator),
+ .field_index = payload.data.field_index,
+ },
+ };
+ return Value{ .ptr_otherwise = &new_payload.base };
+ },
.bytes => return self.copyPayloadShallow(allocator, Payload.Bytes),
.repeated => {
const payload = self.castTag(.repeated).?;
@@ -569,6 +583,11 @@ pub const Value = extern union {
try out_stream.print("&[{}] ", .{elem_ptr.index});
val = elem_ptr.array_ptr;
},
+ .field_ptr => {
+ const field_ptr = val.castTag(.field_ptr).?.data;
+ try out_stream.print("fieldptr({d}) ", .{field_ptr.field_index});
+ val = field_ptr.container_ptr;
+ },
.empty_array => return out_stream.writeAll(".{}"),
.enum_literal => return out_stream.print(".{}", .{std.zig.fmtId(self.castTag(.enum_literal).?.data)}),
.enum_field_index => return out_stream.print("(enum field {d})", .{self.castTag(.enum_field_index).?.data}),
@@ -704,6 +723,7 @@ pub const Value = extern union {
.ref_val,
.decl_ref,
.elem_ptr,
+ .field_ptr,
.bytes,
.repeated,
.float_16,
@@ -1196,6 +1216,11 @@ pub const Value = extern union {
std.hash.autoHash(&hasher, payload.array_ptr.hash());
std.hash.autoHash(&hasher, payload.index);
},
+ .field_ptr => {
+ const payload = self.castTag(.field_ptr).?.data;
+ std.hash.autoHash(&hasher, payload.container_ptr.hash());
+ std.hash.autoHash(&hasher, payload.field_index);
+ },
.decl_ref => {
const decl = self.castTag(.decl_ref).?.data;
std.hash.autoHash(&hasher, decl);
@@ -1250,6 +1275,11 @@ pub const Value = extern union {
const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
return array_val.elemValue(allocator, elem_ptr.index);
},
+ .field_ptr => {
+ const field_ptr = self.castTag(.field_ptr).?.data;
+ const container_val = try field_ptr.container_ptr.pointerDeref(allocator);
+ return container_val.fieldValue(allocator, field_ptr.field_index);
+ },
else => unreachable,
};
@@ -1270,6 +1300,22 @@ pub const Value = extern union {
}
}
+ pub fn fieldValue(val: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
+ switch (val.tag()) {
+ .@"struct" => {
+ const field_values = val.castTag(.@"struct").?.data;
+ return field_values[index];
+ },
+ .@"union" => {
+ const payload = val.castTag(.@"union").?.data;
+ // TODO assert the tag is correct
+ return payload.val;
+ },
+
+ else => unreachable,
+ }
+ }
+
/// Returns a pointer to the element value at the index.
pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
if (self.castTag(.elem_ptr)) |elem_ptr| {
@@ -1409,6 +1455,7 @@ pub const Value = extern union {
.ref_val,
.decl_ref,
.elem_ptr,
+ .field_ptr,
.bytes,
.repeated,
.float_16,
@@ -1496,6 +1543,16 @@ pub const Value = extern union {
},
};
+ pub const FieldPtr = struct {
+ pub const base_tag = Tag.field_ptr;
+
+ base: Payload = Payload{ .tag = base_tag },
+ data: struct {
+ container_ptr: Value,
+ field_index: usize,
+ },
+ };
+
pub const Bytes = struct {
base: Payload,
data: []const u8,
BRANCH_TODO
@@ -1,4 +1,3 @@
- * start.zig should support pub export fn main with -ofmt=c
* get stage2 tests passing
* modify stage2 tests so that only 1 uses _start and the rest use
pub fn main
@@ -61,6 +60,3 @@
* AstGen threadlocal
* extern "foo" for vars
-
- * TODO all decls should probably store source hash. Without this,
- we currently unnecessarily mark all anon decls outdated here.