diff --git a/BRANCH_TODO b/BRANCH_TODO index 87fd335803..19d43daacb 100644 --- a/BRANCH_TODO +++ b/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. diff --git a/src/Module.zig b/src/Module.zig index df2c80720a..d84b4e2f35 100644 --- a/src/Module.zig +++ b/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 { diff --git a/src/Sema.zig b/src/Sema.zig index eb9400e7b1..bb0a61854a 100644 --- a/src/Sema.zig +++ b/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, + }; +} diff --git a/src/ir.zig b/src/ir.zig index 7dcdd2f286..992835f89c 100644 --- a/src/ir.zig +++ b/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; diff --git a/src/type.zig b/src/type.zig index f492eeb3ec..6c4b4273c3 100644 --- a/src/type.zig +++ b/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, diff --git a/src/value.zig b/src/value.zig index 2c7002402b..65d8a8f6fa 100644 --- a/src/value.zig +++ b/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,