diff --git a/src/AstGen.zig b/src/AstGen.zig index a0ba743c4e..fa1f72670a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1729,7 +1729,7 @@ fn structInitExprRlPtrInner( for (struct_init.ast.fields) |field_init| { const name_token = tree.firstToken(field_init) - 2; const str_index = try astgen.identAsString(name_token); - const field_ptr = try gz.addPlNode(.field_ptr, field_init, Zir.Inst.Field{ + const field_ptr = try gz.addPlNode(.field_ptr_init, field_init, Zir.Inst.Field{ .lhs = result_ptr, .field_name_start = str_index, }); @@ -2287,6 +2287,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .elem_ptr_imm, .elem_val_node, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, diff --git a/src/Module.zig b/src/Module.zig index 5f441790b7..da50fd9ae7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -787,7 +787,7 @@ pub const Decl = struct { const opaque_obj = ty.cast(Type.Payload.Opaque).?.data; return &opaque_obj.namespace; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; return &union_obj.namespace; }, diff --git a/src/Sema.zig b/src/Sema.zig index f8b9d044d0..22b12637f2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -739,7 +739,8 @@ fn analyzeBodyInner( .err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst, false), .error_union_type => try sema.zirErrorUnionType(block, inst), .error_value => try sema.zirErrorValue(block, inst), - .field_ptr => try sema.zirFieldPtr(block, inst), + .field_ptr => try sema.zirFieldPtr(block, inst, false), + .field_ptr_init => try sema.zirFieldPtr(block, inst, true), .field_ptr_named => try sema.zirFieldPtrNamed(block, inst), .field_val => try sema.zirFieldVal(block, inst), .field_val_named => try sema.zirFieldValNamed(block, inst), @@ -1547,11 +1548,11 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) const st_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty)); // st.instruction_addresses = &addrs; - const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src); + const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src, true); try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store); // st.index = 0; - const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src); + const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true); const zero = try sema.addConstant(Type.usize, Value.zero); try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store); @@ -2614,7 +2615,14 @@ fn zirUnionDecl( const new_decl_arena_allocator = new_decl_arena.allocator(); const union_obj = try new_decl_arena_allocator.create(Module.Union); - const type_tag: Type.Tag = if (small.has_tag_type or small.auto_enum_tag) .union_tagged else .@"union"; + const type_tag = if (small.has_tag_type or small.auto_enum_tag) + Type.Tag.union_tagged + else if (small.layout != .Auto) + Type.Tag.@"union" + else switch (block.sema.mod.optimizeMode()) { + .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged, + .ReleaseFast, .ReleaseSmall => Type.Tag.@"union", + }; const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union); union_payload.* = .{ .base = .{ .tag = type_tag }, @@ -7923,7 +7931,7 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai return sema.fieldVal(block, src, object, field_name, field_name_src); } -fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: bool) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -7933,7 +7941,7 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); const object_ptr = try sema.resolveInst(extra.lhs); - return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); + return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing); } fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7972,7 +7980,7 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; const object_ptr = try sema.resolveInst(extra.lhs); const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime known"); - return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); + return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false); } fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { @@ -14536,7 +14544,7 @@ fn zirStructInit( .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); const alloc = try block.addTy(.alloc, alloc_ty); - const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty); + const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty, true); try sema.storePtr(block, src, field_ptr, init_inst); const new_tag = try sema.addConstant(resolved_ty.unionTagTypeHypothetical(), tag_val); _ = try block.addBinOp(.set_union_tag, alloc, new_tag); @@ -15604,13 +15612,21 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I if (decls_val.sliceLen(mod) > 0) { return sema.fail(block, src, "reified unions must have no decls", .{}); } + const layout = layout_val.toEnum(std.builtin.Type.ContainerLayout); var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); errdefer new_decl_arena.deinit(); const new_decl_arena_allocator = new_decl_arena.allocator(); const union_obj = try new_decl_arena_allocator.create(Module.Union); - const type_tag: Type.Tag = if (!tag_type_val.isNull()) .union_tagged else .@"union"; + const type_tag = if (!tag_type_val.isNull()) + Type.Tag.union_tagged + else if (layout != .Auto) + Type.Tag.@"union" + else switch (block.sema.mod.optimizeMode()) { + .Debug, .ReleaseSafe => Type.Tag.union_safety_tagged, + .ReleaseFast, .ReleaseSmall => Type.Tag.@"union", + }; const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union); union_payload.* = .{ .base = .{ .tag = type_tag }, @@ -15631,7 +15647,7 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .fields = .{}, .node_offset = src.node_offset.x, .zir_index = inst, - .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .layout = layout, .status = .have_field_types, .namespace = .{ .parent = block.namespace, @@ -15641,11 +15657,15 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I }; // Tag type + var enum_field_names: ?*Module.EnumNumbered.NameMap = null; const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); - union_obj.tag_ty = if (tag_type_val.optionalValue()) |payload_val| blk: { + if (tag_type_val.optionalValue()) |payload_val| { var buffer: Value.ToTypeBuffer = undefined; - break :blk try payload_val.toType(&buffer).copy(new_decl_arena_allocator); - } else try sema.generateUnionTagTypeSimple(block, fields_len, null); + union_obj.tag_ty = try payload_val.toType(&buffer).copy(new_decl_arena_allocator); + } else { + union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, fields_len, null); + enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; + } // Fields if (fields_len > 0) { @@ -15669,6 +15689,10 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I sema.mod, ); + if (enum_field_names) |set| { + set.putAssumeCapacity(field_name, {}); + } + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); if (gop.found_existing) { // TODO: better source location @@ -18898,6 +18922,8 @@ pub const PanicId = enum { divide_by_zero, remainder_division_zero_negative, exact_division_remainder, + /// TODO make this call `std.builtin.panicInactiveUnionField`. + inactive_union_field, }; fn addSafetyCheck( @@ -19120,6 +19146,7 @@ fn safetyPanic( .divide_by_zero => "division by zero", .remainder_division_zero_negative => "remainder division by zero or negative value", .exact_division_remainder => "exact division produced remainder", + .inactive_union_field => "access of inactive union field", }; const msg_inst = msg_inst: { @@ -19339,7 +19366,7 @@ fn fieldVal( }, .Union => if (is_pointer_to) { // Avoid loading the entire union by fetching a pointer and loading that - const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty); + const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false); return sema.analyzeLoad(block, src, field_ptr, object_src); } else { return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty); @@ -19356,6 +19383,7 @@ fn fieldPtr( object_ptr: Air.Inst.Ref, field_name: []const u8, field_name_src: LazySrcLoc, + initializing: bool, ) CompileError!Air.Inst.Ref { // When editing this function, note that there is corresponding logic to be edited // in `fieldVal`. This function takes a pointer and returns a pointer. @@ -19547,7 +19575,7 @@ fn fieldPtr( try sema.analyzeLoad(block, src, object_ptr, object_ptr_src) else object_ptr; - return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty); + return sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing); }, else => {}, } @@ -19995,6 +20023,7 @@ fn unionFieldPtr( field_name: []const u8, field_name_src: LazySrcLoc, unresolved_union_ty: Type, + initializing: bool, ) CompileError!Air.Inst.Ref { const arena = sema.arena; assert(unresolved_union_ty.zigTypeTag() == .Union); @@ -20010,30 +20039,32 @@ fn unionFieldPtr( .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); - if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| { + if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: { switch (union_obj.layout) { - .Auto => { - // TODO emit the access of inactive union field error commented out below. - // In order to do that, we need to first solve the problem that AstGen - // emits field_ptr instructions in order to initialize union values. - // In such case we need to know that the field_ptr instruction (which is - // calling this unionFieldPtr function) is *initializing* the union, - // in which case we would skip this check, and in fact we would actually - // set the union tag here and the payload to undefined. - - //const tag_and_val = union_val.castTag(.@"union").?.data; - //var field_tag_buf: Value.Payload.U32 = .{ - // .base = .{ .tag = .enum_field_index }, - // .data = field_index, - //}; - //const field_tag = Value.initPayload(&field_tag_buf.base); - //const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, mod); - //if (!tag_matches) { - // // TODO enhance this saying which one was active - // // and which one was accessed, and showing where the union was declared. - // return sema.fail(block, src, "access of inactive union field", .{}); - //} - // TODO add runtime safety check for the active tag + .Auto => if (!initializing) { + const union_val = (try sema.pointerDeref(block, src, union_ptr_val, union_ptr_ty)) orelse + break :ct; + if (union_val.isUndef()) { + return sema.failWithUseOfUndef(block, src); + } + const tag_and_val = union_val.castTag(.@"union").?.data; + var field_tag_buf: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = field_index, + }; + const field_tag = Value.initPayload(&field_tag_buf.base); + const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); + if (!tag_matches) { + const msg = msg: { + const active_index = tag_and_val.tag.castTag(.enum_field_index).?.data; + const active_field_name = union_obj.fields.keys()[active_index]; + const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } }, .Packed, .Extern => {}, } @@ -20048,6 +20079,16 @@ fn unionFieldPtr( } try sema.requireRuntimeBlock(block, src, null); + if (!initializing and union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + const enum_ty = union_ty.unionTagTypeHypothetical(); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); + // TODO would it be better if get_union_tag supported pointers to unions? + const union_val = try block.addTyOp(.load, union_ty, union_ptr); + const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_val); + const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); + try sema.addSafetyCheck(block, ok, .inactive_union_field); + } return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty); } @@ -20106,6 +20147,14 @@ fn unionFieldVal( } try sema.requireRuntimeBlock(block, src, null); + if (union_obj.layout == .Auto and block.wantSafety() and union_ty.unionTagTypeSafety() != null) { + const enum_ty = union_ty.unionTagTypeHypothetical(); + const wanted_tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const wanted_tag = try sema.addConstant(enum_ty, wanted_tag_val); + const active_tag = try block.addTyOp(.get_union_tag, enum_ty, union_byval); + const ok = try block.addBinOp(.cmp_eq, active_tag, wanted_tag); + try sema.addSafetyCheck(block, ok, .inactive_union_field); + } return block.addStructFieldVal(union_byval, field_index, field.ty); } @@ -25424,7 +25473,7 @@ pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj); return ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; try sema.resolveTypeFieldsUnion(block, src, ty, union_obj); return ty; @@ -26449,7 +26498,7 @@ pub fn typeHasOnePossibleValue( return null; } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; const tag_val = (try sema.typeHasOnePossibleValue(block, src, union_obj.tag_ty)) orelse @@ -27081,7 +27130,7 @@ pub fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Typ } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; switch (union_obj.requires_comptime) { .no, .wip => return false, diff --git a/src/Zir.zig b/src/Zir.zig index 73ba396e75..d2c62bd7c0 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -410,6 +410,8 @@ pub const Inst = struct { /// to the named field. The field name is stored in string_bytes. Used by a.b syntax. /// Uses `pl_node` field. The AST node is the a.b syntax. Payload is Field. field_ptr, + /// Same as `field_ptr` but used for struct init. + field_ptr_init, /// Given a struct or object that contains virtual fields, returns the named field. /// The field name is stored in string_bytes. Used by a.b syntax. /// This instruction also accepts a pointer. @@ -1070,6 +1072,7 @@ pub const Inst = struct { .@"export", .export_value, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, @@ -1370,6 +1373,7 @@ pub const Inst = struct { .elem_ptr_imm, .elem_val_node, .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, .field_ptr_named, @@ -1629,6 +1633,7 @@ pub const Inst = struct { .@"export" = .pl_node, .export_value = .pl_node, .field_ptr = .pl_node, + .field_ptr_init = .pl_node, .field_val = .pl_node, .field_ptr_named = .pl_node, .field_val_named = .pl_node, diff --git a/src/arch/wasm/abi.zig b/src/arch/wasm/abi.zig index 63b319613b..77ba695a90 100644 --- a/src/arch/wasm/abi.zig +++ b/src/arch/wasm/abi.zig @@ -77,7 +77,7 @@ pub fn classifyType(ty: Type, target: Target) [2]Class { .Union => { const layout = ty.unionGetLayout(target); if (layout.payload_size == 0 and layout.tag_size != 0) { - return classifyType(ty.unionTagType().?, target); + return classifyType(ty.unionTagTypeSafety().?, target); } if (ty.unionFields().count() > 1) return memory; return classifyType(ty.unionFields().values()[0].ty, target); @@ -111,7 +111,7 @@ pub fn scalarType(ty: Type, target: std.Target) Type { .Union => { const layout = ty.unionGetLayout(target); if (layout.payload_size == 0 and layout.tag_size != 0) { - return scalarType(ty.unionTagType().?, target); + return scalarType(ty.unionTagTypeSafety().?, target); } std.debug.assert(ty.unionFields().count() == 1); return scalarType(ty.unionFields().values()[0].ty, target); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1e629d7cb0..98f7792a76 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -504,7 +504,7 @@ pub const DeclGen = struct { if (field_ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeAll("&("); try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); - if (field_ptr.container_ty.tag() == .union_tagged) { + if (field_ptr.container_ty.tag() == .union_tagged or field_ptr.container_ty.tag() == .union_safety_tagged) { try writer.print(")->payload.{ }", .{fmtIdent(field_name)}); } else { try writer.print(")->{ }", .{fmtIdent(field_name)}); @@ -842,7 +842,7 @@ pub const DeclGen = struct { try dg.renderTypecast(writer, ty); try writer.writeAll("){"); - if (ty.unionTagType()) |tag_ty| { + if (ty.unionTagTypeSafety()) |tag_ty| { if (layout.tag_size != 0) { try writer.writeAll(".tag = "); try dg.renderValue(writer, tag_ty, union_obj.tag, location); @@ -858,7 +858,7 @@ pub const DeclGen = struct { try writer.print(".{ } = ", .{fmtIdent(field_name)}); try dg.renderValue(writer, field_ty, union_obj.val, location); } - if (ty.unionTagType()) |_| { + if (ty.unionTagTypeSafety()) |_| { try writer.writeAll("}"); } try writer.writeAll("}"); @@ -1110,7 +1110,7 @@ pub const DeclGen = struct { defer buffer.deinit(); try buffer.appendSlice("typedef "); - if (t.unionTagType()) |tag_ty| { + if (t.unionTagTypeSafety()) |tag_ty| { const name: CValue = .{ .bytes = "tag" }; try buffer.appendSlice("struct {\n "); if (layout.tag_size != 0) { @@ -1134,7 +1134,7 @@ pub const DeclGen = struct { } try buffer.appendSlice("} "); - if (t.unionTagType()) |_| { + if (t.unionTagTypeSafety()) |_| { try buffer.appendSlice("payload;\n} "); } @@ -3368,7 +3368,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const fields = struct_ty.unionFields(); field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; @@ -3383,7 +3383,7 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc }, else => unreachable, } - const payload = if (struct_ty.tag() == .union_tagged) "payload." else ""; + const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); @@ -3415,7 +3415,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { defer buf.deinit(); const field_name = switch (struct_ty.tag()) { .@"struct" => struct_ty.structFields().keys()[extra.field_index], - .@"union", .union_tagged => struct_ty.unionFields().keys()[extra.field_index], + .@"union", .union_safety_tagged, .union_tagged => struct_ty.unionFields().keys()[extra.field_index], .tuple, .anon_struct => blk: { const tuple = struct_ty.tupleFields(); if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none; @@ -3425,7 +3425,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { }, else => unreachable, }; - const payload = if (struct_ty.tag() == .union_tagged) "payload." else ""; + const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6966d8b164..063e2ee0a2 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3404,7 +3404,7 @@ pub const DeclGen = struct { if (layout.payload_size == 0) { return lowerValue(dg, .{ - .ty = tv.ty.unionTagType().?, + .ty = tv.ty.unionTagTypeSafety().?, .val = tag_and_val.tag, }); } @@ -3446,7 +3446,7 @@ pub const DeclGen = struct { } } const llvm_tag_value = try lowerValue(dg, .{ - .ty = tv.ty.unionTagType().?, + .ty = tv.ty.unionTagTypeSafety().?, .val = tag_and_val.tag, }); var fields: [3]*const llvm.Value = undefined; diff --git a/src/print_zir.zig b/src/print_zir.zig index 96923c6d86..3a10c8c8cb 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -390,6 +390,7 @@ const Writer = struct { .switch_block => try self.writeSwitchBlock(stream, inst), .field_ptr, + .field_ptr_init, .field_val, .field_call_bind, => try self.writePlNodeField(stream, inst), diff --git a/src/type.zig b/src/type.zig index bdddaab070..d6feedcc31 100644 --- a/src/type.zig +++ b/src/type.zig @@ -149,6 +149,7 @@ pub const Type = extern union { => return .Enum, .@"union", + .union_safety_tagged, .union_tagged, .type_info, => return .Union, @@ -902,7 +903,7 @@ pub const Type = extern union { .reduce_op, => unreachable, // needed to resolve the type before now - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const a_union_obj = a.cast(Payload.Union).?.data; const b_union_obj = (b.cast(Payload.Union) orelse return false).data; return a_union_obj == b_union_obj; @@ -1210,7 +1211,7 @@ pub const Type = extern union { .reduce_op, => unreachable, // needed to resolve the type before now - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj: *const Module.Union = ty.cast(Payload.Union).?.data; std.hash.autoHash(hasher, std.builtin.TypeId.Union); std.hash.autoHash(hasher, union_obj); @@ -1479,7 +1480,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), + .@"union", .union_safety_tagged, .union_tagged => return self.copyPayloadShallow(allocator, Payload.Union), .enum_simple => return self.copyPayloadShallow(allocator, Payload.EnumSimple), .enum_numbered => return self.copyPayloadShallow(allocator, Payload.EnumNumbered), .enum_full, .enum_nonexhaustive => return self.copyPayloadShallow(allocator, Payload.EnumFull), @@ -1603,7 +1604,7 @@ pub const Type = extern union { @tagName(t), struct_obj.owner_decl, }); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return writer.print("({s} decl={d})", .{ @tagName(t), union_obj.owner_decl, @@ -1989,7 +1990,7 @@ pub const Type = extern union { const decl = mod.declPtr(struct_obj.owner_decl); try decl.renderFullyQualifiedName(mod, writer); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; const decl = mod.declPtr(union_obj.owner_decl); try decl.renderFullyQualifiedName(mod, writer); @@ -2485,8 +2486,8 @@ pub const Type = extern union { return false; } }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { return true; } @@ -2644,7 +2645,7 @@ pub const Type = extern union { .optional => ty.isPtrLikeOptional(), .@"struct" => ty.castTag(.@"struct").?.data.layout != .Auto, - .@"union" => ty.castTag(.@"union").?.data.layout != .Auto, + .@"union", .union_safety_tagged => ty.cast(Payload.Union).?.data.layout != .Auto, .union_tagged => false, }; } @@ -3050,11 +3051,10 @@ pub const Type = extern union { }, .@"union" => { const union_obj = ty.castTag(.@"union").?.data; - // TODO pass `true` for have_tag when unions have a safety tag return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return abiAlignmentAdvancedUnion(ty, target, strat, union_obj, true); }, @@ -3232,11 +3232,10 @@ pub const Type = extern union { }, .@"union" => { const union_obj = ty.castTag(.@"union").?.data; - // TODO pass `true` for have_tag when unions have a safety tag return abiSizeAdvancedUnion(ty, target, strat, union_obj, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return abiSizeAdvancedUnion(ty, target, strat, union_obj, true); }, @@ -3526,7 +3525,7 @@ pub const Type = extern union { return try bitSizeAdvanced(int_tag_ty, target, sema_kit); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { if (sema_kit) |sk| _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); const union_obj = ty.cast(Payload.Union).?.data; assert(union_obj.haveFieldTypes()); @@ -4194,6 +4193,33 @@ pub const Type = extern union { }; } + /// Same as `unionTagType` but includes safety tag. + /// Codegen should use this version. + pub fn unionTagTypeSafety(ty: Type) ?Type { + return switch (ty.tag()) { + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; + assert(union_obj.haveFieldTypes()); + return union_obj.tag_ty; + }, + + .atomic_order, + .atomic_rmw_op, + .calling_convention, + .address_space, + .float_mode, + .reduce_op, + .call_options, + .prefetch_options, + .export_options, + .extern_options, + .type_info, + => unreachable, // needed to call resolveTypeFields first + + else => null, + }; + } + /// Asserts the type is a union; returns the tag type, even if the tag will /// not be stored at runtime. pub fn unionTagTypeHypothetical(ty: Type) Type { @@ -4225,8 +4251,8 @@ pub const Type = extern union { const union_obj = ty.castTag(.@"union").?.data; return union_obj.getLayout(target, false); }, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; return union_obj.getLayout(target, true); }, else => unreachable, @@ -4238,6 +4264,7 @@ pub const Type = extern union { .tuple, .empty_struct_literal, .anon_struct => .Auto, .@"struct" => ty.castTag(.@"struct").?.data.layout, .@"union" => ty.castTag(.@"union").?.data.layout, + .union_safety_tagged => ty.castTag(.union_safety_tagged).?.data.layout, .union_tagged => ty.castTag(.union_tagged).?.data.layout, else => unreachable, }; @@ -4936,7 +4963,7 @@ pub const Type = extern union { return null; } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; const tag_val = union_obj.tag_ty.onePossibleValue() orelse return null; const only_field = union_obj.fields.values()[0]; @@ -5114,7 +5141,7 @@ pub const Type = extern union { } }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Type.Payload.Union).?.data; switch (union_obj.requires_comptime) { .wip, .unknown => unreachable, // This function asserts types already resolved. @@ -5167,6 +5194,7 @@ pub const Type = extern union { .empty_struct => self.castTag(.empty_struct).?.data, .@"opaque" => &self.castTag(.@"opaque").?.data.namespace, .@"union" => &self.castTag(.@"union").?.data.namespace, + .union_safety_tagged => &self.castTag(.union_safety_tagged).?.data.namespace, .union_tagged => &self.castTag(.union_tagged).?.data.namespace, else => null, @@ -5439,7 +5467,7 @@ pub const Type = extern union { const struct_obj = ty.castTag(.@"struct").?.data; return struct_obj.fields.values()[index].ty; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.fields.values()[index].ty; }, @@ -5456,7 +5484,7 @@ pub const Type = extern union { assert(struct_obj.layout != .Packed); return struct_obj.fields.values()[index].normalAlignment(target); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.fields.values()[index].normalAlignment(target); }, @@ -5619,8 +5647,8 @@ pub const Type = extern union { }, .@"union" => return 0, - .union_tagged => { - const union_obj = ty.castTag(.union_tagged).?.data; + .union_safety_tagged, .union_tagged => { + const union_obj = ty.cast(Payload.Union).?.data; const layout = union_obj.getLayout(target, true); if (layout.tag_align >= layout.payload_align) { // {Tag, Payload} @@ -5660,7 +5688,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.srcLoc(mod); }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.srcLoc(mod); }, @@ -5704,7 +5732,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.owner_decl; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.owner_decl; }, @@ -5748,7 +5776,7 @@ pub const Type = extern union { const error_set = ty.castTag(.error_set).?.data; return error_set.node_offset; }, - .@"union", .union_tagged => { + .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; return union_obj.node_offset; }, @@ -5893,6 +5921,7 @@ pub const Type = extern union { @"opaque", @"struct", @"union", + union_safety_tagged, union_tagged, enum_simple, enum_numbered, @@ -6009,7 +6038,7 @@ pub const Type = extern union { .error_set_single => Payload.Name, .@"opaque" => Payload.Opaque, .@"struct" => Payload.Struct, - .@"union", .union_tagged => Payload.Union, + .@"union", .union_safety_tagged, .union_tagged => Payload.Union, .enum_full, .enum_nonexhaustive => Payload.EnumFull, .enum_simple => Payload.EnumSimple, .enum_numbered => Payload.EnumNumbered, diff --git a/test/behavior/bugs/1381.zig b/test/behavior/bugs/1381.zig index ae80132cc1..002d217434 100644 --- a/test/behavior/bugs/1381.zig +++ b/test/behavior/bugs/1381.zig @@ -12,8 +12,10 @@ const A = union(enum) { }; test "union that needs padding bytes inside an array" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; var as = [_]A{ A{ .B = B{ .D = 1 } }, diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 22d09a066d..8fac5697ec 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -998,6 +998,9 @@ test "tuple element initialized with fn call" { } test "struct with union field" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + const Value = struct { ref: u32 = 2, kind: union(enum) { diff --git a/test/behavior/type.zig b/test/behavior/type.zig index f4a32e5df0..16a79a5122 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -412,7 +412,7 @@ test "Type.Union" { const Untagged = @Type(.{ .Union = .{ - .layout = .Auto, + .layout = .Extern, .tag_type = null, .fields = &.{ .{ .name = "int", .field_type = i32, .alignment = @alignOf(f32) }, diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 1787a9f30e..8e4b262565 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -37,6 +37,7 @@ test "init union with runtime value - floats" { test "basic unions" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO var foo = Foo{ .int = 1 }; try expect(foo.int == 1); @@ -430,9 +431,11 @@ const Foo1 = union(enum) { var glbl: Foo1 = undefined; test "global union with single field is correctly initialized" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO glbl = Foo1{ .f = @typeInfo(Foo1).Union.fields[0].field_type{ .x = 123 }, @@ -473,8 +476,11 @@ test "update the tag value for zero-sized unions" { } test "union initializer generates padding only if needed" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const U = union(enum) { A: u24, @@ -747,9 +753,11 @@ fn Setter(attr: Attribute) type { } test "return union init with void payload" { + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { fn entry() !void { @@ -775,6 +783,7 @@ test "@unionInit stored to a const" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -937,6 +946,7 @@ test "cast from anonymous struct to union" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -969,6 +979,7 @@ test "cast from pointer to anonymous struct to pointer to union" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const U = union(enum) { @@ -1104,6 +1115,8 @@ test "union enum type gets a separate scope" { test "global variable struct contains union initialized to non-most-aligned field" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO const T = struct { const U = union(enum) { diff --git a/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig b/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig index dd69531b01..03f7ad0a8f 100644 --- a/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig +++ b/test/cases/compile_errors/wrong_initializer_for_union_payload_of_type_type.zig @@ -13,5 +13,4 @@ export fn entry() void { // backend=stage2 // target=native // -// :9:14: error: expected type 'type', found 'tmp.U' -// :1:11: note: union declared here +// :9:8: error: use of undefined value here causes undefined behavior diff --git a/test/cases/safety/bad union field access.zig b/test/cases/safety/bad union field access.zig index 7adf48abf2..bdde8d9f3a 100644 --- a/test/cases/safety/bad union field access.zig +++ b/test/cases/safety/bad union field access.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "access of inactive union field")) { + std.process.exit(0); + } + std.process.exit(1); } const Foo = union { @@ -21,5 +23,5 @@ fn bar(f: *Foo) void { f.float = 12.34; } // run -// backend=stage1 -// target=native \ No newline at end of file +// backend=llvm +// target=native