From 6f303c01f3e06fe8203563065ea32537f6eff456 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 1 Mar 2022 18:24:00 -0700 Subject: [PATCH] LLVM: add extra padding to structs and tuples sometimes * Sema: resolve type fully when emitting an alloc AIR instruction to avoid tripping assertion for checking struct field alignment. * LLVM backend: keep a reference to the LLVM target data alive during lowering so that we can ask LLVM what it thinks the ABI alignment and size of LLVM types are. We need this in order to lower tuples and structs so that we can put in extra padding bytes when Zig disagrees with LLVM about the size or alignment of something. * LLVM backend: make the LLVM struct type packed that contains the most aligned union field and the padding. This prevents the struct from being too big according to LLVM. In the future, we may want to consider instead emitting unions in a "flat" manner; putting the tag, most aligned union field, and padding all in the same struct field space. * LLVM backend: make structs with 2 or fewer fields return isByRef=false. This results in more efficient codegen. This required lowering of bitcast to sometimes store the struct into an alloca, ptrcast, and then load because LLVM does not allow bitcasting structs. * enable more passing behavior tests. --- src/Sema.zig | 4 +- src/codegen/llvm.zig | 377 ++++++++++++++++++++-------- src/codegen/llvm/bindings.zig | 6 + src/type.zig | 2 +- test/behavior/basic.zig | 2 - test/behavior/bugs/394.zig | 2 +- test/behavior/for.zig | 9 +- test/behavior/if.zig | 2 - test/behavior/optional.zig | 20 +- test/behavior/ptrcast.zig | 6 +- test/behavior/reflection.zig | 2 - test/behavior/sizeof_and_typeof.zig | 4 - test/behavior/struct.zig | 8 +- test/behavior/type.zig | 4 - test/behavior/var_args.zig | 12 +- 15 files changed, 327 insertions(+), 133 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index e58fdef4a78831568996e44286b268741b1e7884..c0b49fa4731876e0e4dd82e4f02ea5a1947cd308 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2439,7 +2439,7 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); try sema.requireRuntimeBlock(block, var_decl_src); - try sema.resolveTypeLayout(block, ty_src, var_ty); + try sema.resolveTypeFully(block, ty_src, var_ty); return block.addTy(.alloc, ptr_type); } @@ -2461,7 +2461,7 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); try sema.requireRuntimeBlock(block, var_decl_src); - try sema.resolveTypeLayout(block, ty_src, var_ty); + try sema.resolveTypeFully(block, ty_src, var_ty); return block.addTy(.alloc, ptr_type); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index bb82a80bbb736db806ece6d44acf2480f1178a5e..28654b905a287bfe7216a0d59912a14c8d744b20 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -160,6 +160,7 @@ pub const Object = struct { llvm_module: *const llvm.Module, context: *const llvm.Context, target_machine: *const llvm.TargetMachine, + target_data: *const llvm.TargetData, /// Ideally we would use `llvm_module.getNamedFunction` to go from *Decl to LLVM function, /// but that has some downsides: /// * we have to compute the fully qualified name every time we want to do the lookup @@ -258,7 +259,7 @@ pub const Object = struct { errdefer target_machine.dispose(); const target_data = target_machine.createTargetDataLayout(); - defer target_data.dispose(); + errdefer target_data.dispose(); llvm_module.setModuleDataLayout(target_data); @@ -266,6 +267,7 @@ pub const Object = struct { .llvm_module = llvm_module, .context = context, .target_machine = target_machine, + .target_data = target_data, .decl_map = .{}, .type_map = .{}, .type_map_arena = std.heap.ArenaAllocator.init(gpa), @@ -274,6 +276,7 @@ pub const Object = struct { } pub fn deinit(self: *Object, gpa: Allocator) void { + self.target_data.dispose(); self.target_machine.dispose(); self.llvm_module.dispose(); self.context.dispose(); @@ -955,20 +958,55 @@ pub const DeclGen = struct { // reference, we need to copy it here. gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator()); - if (t.castTag(.tuple)) |tuple| { + if (t.isTuple()) { + const tuple = t.tupleFields(); const llvm_struct_ty = dg.context.structCreateNamed(""); gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls - const types = tuple.data.types; - const values = tuple.data.values; - var llvm_field_types = try std.ArrayListUnmanaged(*const llvm.Type).initCapacity(gpa, types.len); + var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; defer llvm_field_types.deinit(gpa); - for (types) |field_ty, i| { - const field_val = values[i]; + try llvm_field_types.ensureUnusedCapacity(gpa, tuple.types.len); + + // We need to insert extra padding if LLVM's isn't enough. + var zig_offset: u64 = 0; + var llvm_offset: u64 = 0; + var zig_big_align: u32 = 0; + var llvm_big_align: u32 = 0; + + for (tuple.types) |field_ty, i| { + const field_val = tuple.values[i]; if (field_val.tag() != .unreachable_value) continue; - llvm_field_types.appendAssumeCapacity(try dg.llvmType(field_ty)); + const field_align = field_ty.abiAlignment(target); + zig_big_align = @maximum(zig_big_align, field_align); + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, field_align); + + const field_llvm_ty = try dg.llvmType(field_ty); + const field_llvm_align = dg.object.target_data.ABIAlignmentOfType(field_llvm_ty); + llvm_big_align = @maximum(llvm_big_align, field_llvm_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, field_llvm_align); + + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + try llvm_field_types.append(gpa, llvm_array_ty); + llvm_offset = zig_offset; + } + try llvm_field_types.append(gpa, field_llvm_ty); + + llvm_offset += dg.object.target_data.ABISizeOfType(field_llvm_ty); + zig_offset += field_ty.abiSize(target); + } + { + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, zig_big_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, llvm_big_align); + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + try llvm_field_types.append(gpa, llvm_array_ty); + llvm_offset = zig_offset; + } } llvm_struct_ty.structSetBody( @@ -998,12 +1036,49 @@ pub const DeclGen = struct { assert(struct_obj.haveFieldTypes()); - var llvm_field_types = try std.ArrayListUnmanaged(*const llvm.Type).initCapacity(gpa, struct_obj.fields.count()); + var llvm_field_types: std.ArrayListUnmanaged(*const llvm.Type) = .{}; defer llvm_field_types.deinit(gpa); + try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count()); + + // We need to insert extra padding if LLVM's isn't enough. + var zig_offset: u64 = 0; + var llvm_offset: u64 = 0; + var zig_big_align: u32 = 0; + var llvm_big_align: u32 = 0; + for (struct_obj.fields.values()) |field| { - if (!field.ty.hasRuntimeBits()) continue; - llvm_field_types.appendAssumeCapacity(try dg.llvmType(field.ty)); + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + const field_align = field.normalAlignment(target); + zig_big_align = @maximum(zig_big_align, field_align); + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, field_align); + + const field_llvm_ty = try dg.llvmType(field.ty); + const field_llvm_align = dg.object.target_data.ABIAlignmentOfType(field_llvm_ty); + llvm_big_align = @maximum(llvm_big_align, field_llvm_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, field_llvm_align); + + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + try llvm_field_types.append(gpa, llvm_array_ty); + llvm_offset = zig_offset; + } + try llvm_field_types.append(gpa, field_llvm_ty); + + llvm_offset += dg.object.target_data.ABISizeOfType(field_llvm_ty); + zig_offset += field.ty.abiSize(target); + } + { + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, zig_big_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, llvm_big_align); + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + try llvm_field_types.append(gpa, llvm_array_ty); + llvm_offset = zig_offset; + } } llvm_struct_ty.structSetBody( @@ -1050,7 +1125,7 @@ pub const DeclGen = struct { llvm_aligned_field_ty, dg.context.intType(8).arrayType(padding_len), }; - break :t dg.context.structType(&fields, fields.len, .False); + break :t dg.context.structType(&fields, fields.len, .True); }; if (layout.tag_size == 0) { @@ -1461,9 +1536,9 @@ pub const DeclGen = struct { const field_vals = tv.val.castTag(.@"struct").?.data; const gpa = dg.gpa; const struct_obj = tv.ty.castTag(.@"struct").?.data; + const target = dg.module.getTarget(); if (struct_obj.layout == .Packed) { - const target = dg.module.getTarget(); const big_bits = struct_obj.packedIntegerBits(target); const int_llvm_ty = dg.context.intType(big_bits); const fields = struct_obj.fields.values(); @@ -1497,19 +1572,56 @@ pub const DeclGen = struct { var llvm_fields = try std.ArrayListUnmanaged(*const llvm.Value).initCapacity(gpa, llvm_field_count); defer llvm_fields.deinit(gpa); + // These are used to detect where the extra padding fields are so that we + // can initialize them with undefined. + var zig_offset: u64 = 0; + var llvm_offset: u64 = 0; + var zig_big_align: u32 = 0; + var llvm_big_align: u32 = 0; + var need_unnamed = false; - for (field_vals) |field_val, i| { - const field_ty = tv.ty.structFieldType(i); - if (!field_ty.hasRuntimeBits()) continue; + for (struct_obj.fields.values()) |field, i| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + const field_align = field.normalAlignment(target); + zig_big_align = @maximum(zig_big_align, field_align); + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, field_align); + + const field_llvm_ty = try dg.llvmType(field.ty); + const field_llvm_align = dg.object.target_data.ABIAlignmentOfType(field_llvm_ty); + llvm_big_align = @maximum(llvm_big_align, field_llvm_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, field_llvm_align); + + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + // TODO make this and all other padding elsewhere in debug + // builds be 0xaa not undef. + llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef()); + llvm_offset = zig_offset; + } const field_llvm_val = try dg.genTypedValue(.{ - .ty = field_ty, - .val = field_val, + .ty = field.ty, + .val = field_vals[i], }); - need_unnamed = need_unnamed or dg.isUnnamedType(field_ty, field_llvm_val); + need_unnamed = need_unnamed or dg.isUnnamedType(field.ty, field_llvm_val); llvm_fields.appendAssumeCapacity(field_llvm_val); + + llvm_offset += dg.object.target_data.ABISizeOfType(field_llvm_ty); + zig_offset += field.ty.abiSize(target); + } + { + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, zig_big_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, llvm_big_align); + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + const llvm_array_ty = dg.context.intType(8).arrayType(padding_len); + llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef()); + llvm_offset = zig_offset; + } } if (need_unnamed) { @@ -1556,7 +1668,7 @@ pub const DeclGen = struct { const fields: [2]*const llvm.Value = .{ field, dg.context.intType(8).arrayType(padding_len).getUndef(), }; - break :p dg.context.constStruct(&fields, fields.len, .False); + break :p dg.context.constStruct(&fields, fields.len, .True); }; // In this case we must make an unnamed struct because LLVM does @@ -1741,7 +1853,7 @@ pub const DeclGen = struct { }, .Struct => { var ty_buf: Type.Payload.Pointer = undefined; - const llvm_field_index = llvmFieldIndex(parent.ty, field_index, target, &ty_buf).?; + const llvm_field_index = dg.llvmFieldIndex(parent.ty, field_index, &ty_buf).?; const indices: [2]*const llvm.Value = .{ llvm_u32.constInt(0, .False), llvm_u32.constInt(llvm_field_index, .False), @@ -1972,6 +2084,107 @@ pub const DeclGen = struct { return null; } } + + /// Take into account 0 bit fields and padding. Returns null if an llvm + /// field could not be found. + /// This only happens if you want the field index of a zero sized field at + /// the end of the struct. + fn llvmFieldIndex( + dg: *DeclGen, + ty: Type, + field_index: u32, + ptr_pl_buf: *Type.Payload.Pointer, + ) ?c_uint { + const target = dg.module.getTarget(); + + // Detects where we inserted extra padding fields so that we can skip + // over them in this function. + var zig_offset: u64 = 0; + var llvm_offset: u64 = 0; + var zig_big_align: u32 = 0; + var llvm_big_align: u32 = 0; + + if (ty.isTuple()) { + const tuple = ty.tupleFields(); + var llvm_field_index: c_uint = 0; + for (tuple.types) |field_ty, i| { + if (tuple.values[i].tag() != .unreachable_value) continue; + + const field_align = field_ty.abiAlignment(target); + zig_big_align = @maximum(zig_big_align, field_align); + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, field_align); + + // assert no error because we have already seen a successful + // llvmType on this field. + const field_llvm_ty = dg.llvmType(field_ty) catch unreachable; + const field_llvm_align = dg.object.target_data.ABIAlignmentOfType(field_llvm_ty); + llvm_big_align = @maximum(llvm_big_align, field_llvm_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, field_llvm_align); + + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + llvm_field_index += 1; + llvm_offset = zig_offset; + } + + if (field_index == i) { + ptr_pl_buf.* = .{ + .data = .{ + .pointee_type = field_ty, + .@"align" = field_align, + .@"addrspace" = .generic, + }, + }; + return llvm_field_index; + } + + llvm_field_index += 1; + llvm_offset += dg.object.target_data.ABISizeOfType(field_llvm_ty); + zig_offset += field_ty.abiSize(target); + } + return null; + } + assert(ty.containerLayout() != .Packed); + + var llvm_field_index: c_uint = 0; + for (ty.structFields().values()) |field, i| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + const field_align = field.normalAlignment(target); + zig_big_align = @maximum(zig_big_align, field_align); + zig_offset = std.mem.alignForwardGeneric(u64, zig_offset, field_align); + + // assert no error because we have already seen a successful llvmType on this field. + const field_llvm_ty = dg.llvmType(field.ty) catch unreachable; + const field_llvm_align = dg.object.target_data.ABIAlignmentOfType(field_llvm_ty); + llvm_big_align = @maximum(llvm_big_align, field_llvm_align); + llvm_offset = std.mem.alignForwardGeneric(u64, llvm_offset, field_llvm_align); + + const padding_len = @intCast(c_uint, zig_offset - llvm_offset); + if (padding_len > 0) { + llvm_field_index += 1; + llvm_offset = zig_offset; + } + + if (field_index == i) { + ptr_pl_buf.* = .{ + .data = .{ + .pointee_type = field.ty, + .@"align" = field_align, + .@"addrspace" = .generic, + }, + }; + return llvm_field_index; + } + + llvm_field_index += 1; + llvm_offset += dg.object.target_data.ABISizeOfType(field_llvm_ty); + zig_offset += field.ty.abiSize(target); + } else { + // We did not find an llvm field that corresponds to this zig field. + return null; + } + } }; pub const FuncGen = struct { @@ -2850,7 +3063,7 @@ pub const FuncGen = struct { }, else => { var ptr_ty_buf: Type.Payload.Pointer = undefined; - const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?; + const llvm_field_index = self.dg.llvmFieldIndex(struct_ty, field_index, &ptr_ty_buf).?; return self.builder.buildExtractValue(struct_llvm_val, llvm_field_index, ""); }, }, @@ -2865,7 +3078,7 @@ pub const FuncGen = struct { .Struct => { assert(struct_ty.containerLayout() != .Packed); var ptr_ty_buf: Type.Payload.Pointer = undefined; - const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?; + const llvm_field_index = self.dg.llvmFieldIndex(struct_ty, field_index, &ptr_ty_buf).?; const field_ptr = self.builder.buildStructGEP(struct_llvm_val, llvm_field_index, ""); const field_ptr_ty = Type.initPayload(&ptr_ty_buf.base); return self.load(field_ptr, field_ptr_ty); @@ -3916,6 +4129,7 @@ pub const FuncGen = struct { const operand_is_ref = isByRef(operand_ty); const result_is_ref = isByRef(inst_ty); const llvm_dest_ty = try self.dg.llvmType(inst_ty); + const target = self.dg.module.getTarget(); if (operand_is_ref and result_is_ref) { // They are both pointers; just do a bitcast on the pointers :) @@ -3927,7 +4141,6 @@ pub const FuncGen = struct { } if (operand_ty.zigTypeTag() == .Vector and inst_ty.zigTypeTag() == .Array) { - const target = self.dg.module.getTarget(); const elem_ty = operand_ty.childType(); if (!result_is_ref) { return self.dg.todo("implement bitcast vector to non-ref array", .{}); @@ -3957,7 +4170,6 @@ pub const FuncGen = struct { } return array_ptr; } else if (operand_ty.zigTypeTag() == .Array and inst_ty.zigTypeTag() == .Vector) { - const target = self.dg.module.getTarget(); const elem_ty = operand_ty.childType(); const llvm_vector_ty = try self.dg.llvmType(inst_ty); if (!operand_is_ref) { @@ -3998,18 +4210,39 @@ pub const FuncGen = struct { if (operand_is_ref) { // Bitcast the operand pointer, then load. const casted_ptr = self.builder.buildBitCast(operand, llvm_dest_ty.pointerType(0), ""); - return self.builder.buildLoad(casted_ptr, ""); + const load_inst = self.builder.buildLoad(casted_ptr, ""); + load_inst.setAlignment(operand_ty.abiAlignment(target)); + return load_inst; } if (result_is_ref) { // Bitcast the result pointer, then store. + const alignment = @maximum(operand_ty.abiAlignment(target), inst_ty.abiAlignment(target)); const result_ptr = self.buildAlloca(llvm_dest_ty); + result_ptr.setAlignment(alignment); const operand_llvm_ty = try self.dg.llvmType(operand_ty); const casted_ptr = self.builder.buildBitCast(result_ptr, operand_llvm_ty.pointerType(0), ""); - _ = self.builder.buildStore(operand, casted_ptr); + const store_inst = self.builder.buildStore(operand, casted_ptr); + store_inst.setAlignment(alignment); return result_ptr; } + if (llvm_dest_ty.getTypeKind() == .Struct) { + // Both our operand and our result are values, not pointers, + // but LLVM won't let us bitcast struct values. + // Therefore, we store operand to bitcasted alloca, then load for result. + const alignment = @maximum(operand_ty.abiAlignment(target), inst_ty.abiAlignment(target)); + const result_ptr = self.buildAlloca(llvm_dest_ty); + result_ptr.setAlignment(alignment); + const operand_llvm_ty = try self.dg.llvmType(operand_ty); + const casted_ptr = self.builder.buildBitCast(result_ptr, operand_llvm_ty.pointerType(0), ""); + const store_inst = self.builder.buildStore(operand, casted_ptr); + store_inst.setAlignment(alignment); + const load_inst = self.builder.buildLoad(result_ptr, ""); + load_inst.setAlignment(alignment); + return load_inst; + } + return self.builder.buildBitCast(operand, llvm_dest_ty, ""); } @@ -5009,9 +5242,8 @@ pub const FuncGen = struct { return self.builder.buildBitCast(struct_ptr, result_llvm_ty, ""); }, else => { - const target = self.dg.module.getTarget(); var ty_buf: Type.Payload.Pointer = undefined; - if (llvmFieldIndex(struct_ty, field_index, target, &ty_buf)) |llvm_field_index| { + if (self.dg.llvmFieldIndex(struct_ty, field_index, &ty_buf)) |llvm_field_index| { return self.builder.buildStructGEP(struct_ptr, llvm_field_index, ""); } else { // If we found no index then this means this is a zero sized field at the @@ -5422,63 +5654,6 @@ fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.Ca }; } -/// Take into account 0 bit fields. Returns null if an llvm field could not be found. This only -/// happens if you want the field index of a zero sized field at the end of the struct. -fn llvmFieldIndex( - ty: Type, - field_index: u32, - target: std.Target, - ptr_pl_buf: *Type.Payload.Pointer, -) ?c_uint { - if (ty.castTag(.tuple)) |payload| { - const values = payload.data.values; - var llvm_field_index: c_uint = 0; - for (values) |val, i| { - if (val.tag() != .unreachable_value) { - continue; - } - if (field_index > i) { - llvm_field_index += 1; - continue; - } - const field_ty = payload.data.types[i]; - ptr_pl_buf.* = .{ - .data = .{ - .pointee_type = field_ty, - .@"align" = field_ty.abiAlignment(target), - .@"addrspace" = .generic, - }, - }; - return llvm_field_index; - } - return null; - } - const struct_obj = ty.castTag(.@"struct").?.data; - assert(struct_obj.layout != .Packed); - - var llvm_field_index: c_uint = 0; - for (struct_obj.fields.values()) |field, i| { - if (!field.ty.hasRuntimeBits()) - continue; - if (field_index > i) { - llvm_field_index += 1; - continue; - } - - ptr_pl_buf.* = .{ - .data = .{ - .pointee_type = field.ty, - .@"align" = field.normalAlignment(target), - .@"addrspace" = .generic, - }, - }; - return llvm_field_index; - } else { - // We did not find an llvm field that corresponds to this zig field. - return null; - } -} - fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool { switch (fn_info.cc) { .Unspecified, .Inline => return isByRef(fn_info.return_type), @@ -5497,7 +5672,7 @@ fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool } fn isByRef(ty: Type) bool { - // For tuples (and TODO structs), if there are more than this many non-void + // For tuples and structs, if there are more than this many non-void // fields, then we make it byref, otherwise byval. const max_fields_byval = 2; @@ -5529,24 +5704,28 @@ fn isByRef(ty: Type) bool { .Struct => { // Packed structs are represented to LLVM as integers. if (ty.containerLayout() == .Packed) return false; - - if (!ty.hasRuntimeBits()) return false; - if (ty.castTag(.tuple)) |tuple| { + if (ty.isTuple()) { + const tuple = ty.tupleFields(); var count: usize = 0; - for (tuple.data.values) |field_val, i| { + for (tuple.values) |field_val, i| { if (field_val.tag() != .unreachable_value) continue; + count += 1; - if (count > max_fields_byval) { - return true; - } - const field_ty = tuple.data.types[i]; - if (isByRef(field_ty)) { - return true; - } + if (count > max_fields_byval) return true; + if (isByRef(tuple.types[i])) return true; } return false; } - return true; + var count: usize = 0; + const fields = ty.structFields(); + for (fields.values()) |field| { + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; + + count += 1; + if (count > max_fields_byval) return true; + if (isByRef(field.ty)) return true; + } + return false; }, .Union => return ty.hasRuntimeBits(), .ErrorUnion => return isByRef(ty.errorUnionPayload()), diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index a9eea39c670e9cfacb89f264e44f817dbe072597..c505a7d04562cac127aab85fd61554639bad2397 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -916,6 +916,12 @@ pub const TargetMachine = opaque { pub const TargetData = opaque { pub const dispose = LLVMDisposeTargetData; extern fn LLVMDisposeTargetData(*const TargetData) void; + + pub const ABISizeOfType = LLVMABISizeOfType; + extern fn LLVMABISizeOfType(TD: *const TargetData, Ty: *const Type) c_ulonglong; + + pub const ABIAlignmentOfType = LLVMABIAlignmentOfType; + extern fn LLVMABIAlignmentOfType(TD: *const TargetData, Ty: *const Type) c_uint; }; pub const CodeModel = enum(c_int) { diff --git a/src/type.zig b/src/type.zig index 90e79a8eed48543c42c8540ad34ee6cdc1588ab5..8084da34de050b9f15d2bc069145af3a09eec1dd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4400,7 +4400,7 @@ pub const Type = extern union { }; /// Get an iterator that iterates over all the struct field, returning the field and - /// offset of that field. Asserts that the type is a none packed struct. + /// offset of that field. Asserts that the type is a non-packed struct. pub fn iterateStructOffsets(ty: Type, target: Target) StructOffsetIterator { const struct_obj = ty.castTag(.@"struct").?.data; assert(struct_obj.haveLayout()); diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 7b2fa0948c081ee9282b97d7c1180eeee39cffbc..b122314720c5921a5b84f2c264d3490d396c5976 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -778,8 +778,6 @@ extern var opaque_extern_var: opaque {}; var var_to_export: u32 = 42; test "lazy typeInfo value as generic parameter" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - const S = struct { fn foo(args: anytype) void { _ = args; diff --git a/test/behavior/bugs/394.zig b/test/behavior/bugs/394.zig index 28934c8dd0c0b6d6ea026bcf905e24a21b650cbe..e7d6e80936d92c4482837eb34a9ca6490f7c564c 100644 --- a/test/behavior/bugs/394.zig +++ b/test/behavior/bugs/394.zig @@ -10,7 +10,7 @@ const S = struct { const expect = @import("std").testing.expect; const builtin = @import("builtin"); -test "bug 394 fixed" { +test "fixed" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 8f465eb3dec1fc7c433843ad113c747f921acc37..023a2d3c3019cb4d6fe9f15751db91bd5d8e822a 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -157,7 +157,9 @@ test "2 break statements and an else" { } test "for loop with pointer elem var" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + 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 source = "abcdefg"; var target: [source.len]u8 = undefined; @@ -199,7 +201,10 @@ test "for copies its payload" { } test "for on slice with allowzero ptr" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + 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_c) return error.SkipZigTest; // TODO const S = struct { fn doTheTest(slice: []const u8) !void { diff --git a/test/behavior/if.zig b/test/behavior/if.zig index d8beba0222e756bb43141249e1929a959977f6d4..349d7d233d4979ef6126a049e3a8f7c5cd635391 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -106,8 +106,6 @@ test "if copies its payload" { } test "if prongs cast to expected type instead of peer type resolution" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - const S = struct { fn doTheTest(f: bool) !void { var x: i32 = 0; diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index cc8174dc13fef4ffd5c29f8f3aa9a994c7c68946..d6963a6143389dad936e10bbfb5725722c54153a 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -251,7 +251,9 @@ test "coerce an anon struct literal to optional struct" { } test "0-bit child type coerced to optional return ptr result location" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + 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 S = struct { fn doTheTest() !void { @@ -300,7 +302,9 @@ test "0-bit child type coerced to optional" { } test "array of optional unaligned types" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + 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 Enum = enum { one, two, three }; @@ -320,15 +324,15 @@ test "array of optional unaligned types" { // The index must be a runtime value var i: usize = 0; - try expectEqual(Enum.one, values[i].?.Num); + try expect(Enum.one == values[i].?.Num); i += 1; - try expectEqual(Enum.two, values[i].?.Num); + try expect(Enum.two == values[i].?.Num); i += 1; - try expectEqual(Enum.three, values[i].?.Num); + try expect(Enum.three == values[i].?.Num); i += 1; - try expectEqual(Enum.one, values[i].?.Num); + try expect(Enum.one == values[i].?.Num); i += 1; - try expectEqual(Enum.two, values[i].?.Num); + try expect(Enum.two == values[i].?.Num); i += 1; - try expectEqual(Enum.three, values[i].?.Num); + try expect(Enum.three == values[i].?.Num); } diff --git a/test/behavior/ptrcast.zig b/test/behavior/ptrcast.zig index 2bc35e4286c6177c072e9b007a41e99514783a8b..025335e9096ffc228be7a4885a6d45eb9e366b52 100644 --- a/test/behavior/ptrcast.zig +++ b/test/behavior/ptrcast.zig @@ -41,7 +41,11 @@ fn testReinterpretBytesAsExternStruct() !void { } test "reinterpret struct field at comptime" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const numNative = comptime Bytes.init(0x12345678); if (native_endian != .Little) { diff --git a/test/behavior/reflection.zig b/test/behavior/reflection.zig index a181e95b8673e28fd5bf6c2594757e27b4d0049d..865813a663f17476a84a894a38b75d87b612dd0e 100644 --- a/test/behavior/reflection.zig +++ b/test/behavior/reflection.zig @@ -5,8 +5,6 @@ const mem = std.mem; const reflection = @This(); test "reflection: function return type, var args, and param types" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - comptime { const info = @typeInfo(@TypeOf(dummy)).Fn; try expect(info.return_type.? == i32); diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index a405309244f987f397bea6ccd80b59de715d82a3..852e1e74e4ec641077e290eef0b2ad9d33f405ed 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -180,8 +180,6 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { } test "@TypeOf() has no runtime side effects" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - const S = struct { fn foo(comptime T: type, ptr: *T) T { ptr.* += 1; @@ -195,8 +193,6 @@ test "@TypeOf() has no runtime side effects" { } test "branching logic inside @TypeOf" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - const S = struct { var data: i32 = 0; fn foo() anyerror!i32 { diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 13fe0820ce289a2fa903f62e959ae272d10fea58..20123e8014626ce973ade5e522efbb32ed1fce53 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -857,7 +857,9 @@ test "fn with C calling convention returns struct by value" { } test "non-packed struct with u128 entry in union" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + 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 U = union(enum) { Num: u128, @@ -952,7 +954,9 @@ test "fully anonymous struct" { } test "fully anonymous list literal" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO const S = struct { fn doTheTest() !void { diff --git a/test/behavior/type.zig b/test/behavior/type.zig index b1ecccbb1a8802d51465d9db55d65b56154cedb5..eea4d7d203880ddab1377940e7a9675e36a587f8 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -138,8 +138,6 @@ test "Type.Array" { } test "@Type create slice with null sentinel" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - const Slice = @Type(TypeInfo{ .Pointer = .{ .size = .Slice, @@ -156,8 +154,6 @@ test "@Type create slice with null sentinel" { } test "@Type picks up the sentinel value from TypeInfo" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO - try testTypes(&[_]type{ [11:0]u8, [4:10]u8, [*:0]u8, [*:0]const u8, diff --git a/test/behavior/var_args.zig b/test/behavior/var_args.zig index 0e37c845b6502b7ca9fe6f8dc83a96ecf2dc800d..6639d8664ecbf2bc22de5133bc1c3fb90712f931 100644 --- a/test/behavior/var_args.zig +++ b/test/behavior/var_args.zig @@ -13,7 +13,9 @@ fn add(args: anytype) i32 { } test "add arbitrary args" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try expect(add(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(add(.{@as(i32, 1234)}) == 1234); @@ -34,7 +36,9 @@ test "send void arg to var args" { } test "pass args directly" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try expect(addSomeStuff(.{ @as(i32, 1), @as(i32, 2), @as(i32, 3), @as(i32, 4) }) == 10); try expect(addSomeStuff(.{@as(i32, 1234)}) == 1234); @@ -46,7 +50,9 @@ fn addSomeStuff(args: anytype) i32 { } test "runtime parameter before var args" { - if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO try expect((try extraFn(10, .{})) == 0); try expect((try extraFn(10, .{false})) == 1); -- 2.54.0