Skip to content

Audit LLVM ABI size #12127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 95 additions & 25 deletions src/codegen/llvm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2424,6 +2424,24 @@ pub const DeclGen = struct {
}

fn lowerType(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type {
const llvm_ty = try lowerTypeInner(dg, t);
if (std.debug.runtime_safety and false) check: {
if (t.zigTypeTag() == .Opaque) break :check;
if (!t.hasRuntimeBits()) break :check;
if (!llvm_ty.isSized().toBool()) break :check;

const zig_size = t.abiSize(dg.module.getTarget());
const llvm_size = dg.object.target_data.abiSizeOfType(llvm_ty);
if (llvm_size != zig_size) {
log.err("when lowering {}, Zig ABI size = {d} but LLVM ABI size = {d}", .{
t.fmt(dg.module), zig_size, llvm_size,
});
}
}
return llvm_ty;
}

fn lowerTypeInner(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type {
const gpa = dg.gpa;
const target = dg.module.getTarget();
switch (t.zigTypeTag()) {
Expand Down Expand Up @@ -2519,10 +2537,18 @@ pub const DeclGen = struct {
return payload_llvm_ty;
}

const fields: [2]*const llvm.Type = .{
payload_llvm_ty, dg.context.intType(1),
comptime assert(optional_layout_version == 2);
var fields_buf: [3]*const llvm.Type = .{
payload_llvm_ty, dg.context.intType(1), undefined,
};
return dg.context.structType(&fields, fields.len, .False);
const offset = child_ty.abiSize(target) + 1;
const abi_size = t.abiSize(target);
const padding = @intCast(c_uint, abi_size - offset);
if (padding == 0) {
return dg.context.structType(&fields_buf, 2, .False);
}
fields_buf[2] = dg.context.intType(8).arrayType(padding);
return dg.context.structType(&fields_buf, 3, .False);
},
.ErrorUnion => {
const payload_ty = t.errorUnionPayload();
Expand All @@ -2534,12 +2560,37 @@ pub const DeclGen = struct {

const payload_align = payload_ty.abiAlignment(target);
const error_align = Type.anyerror.abiAlignment(target);

const payload_size = payload_ty.abiSize(target);
const error_size = Type.anyerror.abiSize(target);

var fields_buf: [3]*const llvm.Type = undefined;
if (error_align > payload_align) {
const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type };
return dg.context.structType(&fields, fields.len, .False);
fields_buf[0] = llvm_error_type;
fields_buf[1] = llvm_payload_type;
const payload_end =
std.mem.alignForwardGeneric(u64, error_size, payload_align) +
payload_size;
const abi_size = std.mem.alignForwardGeneric(u64, payload_end, error_align);
const padding = @intCast(c_uint, abi_size - payload_end);
if (padding == 0) {
return dg.context.structType(&fields_buf, 2, .False);
}
fields_buf[2] = dg.context.intType(8).arrayType(padding);
return dg.context.structType(&fields_buf, 3, .False);
} else {
const fields: [2]*const llvm.Type = .{ llvm_payload_type, llvm_error_type };
return dg.context.structType(&fields, fields.len, .False);
fields_buf[0] = llvm_payload_type;
fields_buf[1] = llvm_error_type;
const error_end =
std.mem.alignForwardGeneric(u64, payload_size, error_align) +
error_size;
const abi_size = std.mem.alignForwardGeneric(u64, error_end, payload_align);
const padding = @intCast(c_uint, abi_size - error_end);
if (padding == 0) {
return dg.context.structType(&fields_buf, 2, .False);
}
fields_buf[2] = dg.context.intType(8).arrayType(padding);
return dg.context.structType(&fields_buf, 3, .False);
}
},
.ErrorSet => return dg.context.intType(16),
Expand Down Expand Up @@ -2704,7 +2755,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) {
Expand Down Expand Up @@ -3020,7 +3071,7 @@ pub const DeclGen = struct {
return dg.context.constStruct(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
.False,
.True,
);
} else {
const llvm_elem_ty = try dg.lowerType(elem_ty);
Expand Down Expand Up @@ -3057,7 +3108,7 @@ pub const DeclGen = struct {
return dg.context.constStruct(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
.False,
.True,
);
} else {
const llvm_elem_ty = try dg.lowerType(elem_ty);
Expand All @@ -3074,7 +3125,7 @@ pub const DeclGen = struct {
const llvm_elems: [1]*const llvm.Value = .{sentinel};
const need_unnamed = dg.isUnnamedType(elem_ty, llvm_elems[0]);
if (need_unnamed) {
return dg.context.constStruct(&llvm_elems, llvm_elems.len, .False);
return dg.context.constStruct(&llvm_elems, llvm_elems.len, .True);
} else {
const llvm_elem_ty = try dg.lowerType(elem_ty);
return llvm_elem_ty.constArray(&llvm_elems, llvm_elems.len);
Expand All @@ -3083,6 +3134,7 @@ pub const DeclGen = struct {
else => unreachable,
},
.Optional => {
comptime assert(optional_layout_version == 2);
var buf: Type.Payload.ElemType = undefined;
const payload_ty = tv.ty.optionalChild(&buf);
const llvm_i1 = dg.context.intType(1);
Expand All @@ -3091,25 +3143,30 @@ pub const DeclGen = struct {
if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {
return non_null_bit;
}
const llvm_ty = try dg.lowerType(tv.ty);
if (tv.ty.optionalReprIsPayload()) {
if (tv.val.castTag(.opt_payload)) |payload| {
return dg.lowerValue(.{ .ty = payload_ty, .val = payload.data });
} else if (is_pl) {
return dg.lowerValue(.{ .ty = payload_ty, .val = tv.val });
} else {
const llvm_ty = try dg.lowerType(tv.ty);
return llvm_ty.constNull();
}
}
assert(payload_ty.zigTypeTag() != .Fn);
const fields: [2]*const llvm.Value = .{
try dg.lowerValue(.{
.ty = payload_ty,
.val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
}),
non_null_bit,
};
return dg.context.constStruct(&fields, fields.len, .False);

const llvm_field_count = llvm_ty.countStructElementTypes();
var fields_buf: [3]*const llvm.Value = undefined;
fields_buf[0] = try dg.lowerValue(.{
.ty = payload_ty,
.val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
});
fields_buf[1] = non_null_bit;
if (llvm_field_count > 2) {
assert(llvm_field_count == 3);
fields_buf[2] = llvm_ty.structGetTypeAtIndex(2).getUndef();
}
return dg.context.constStruct(&fields_buf, llvm_field_count, .False);
},
.Fn => {
const fn_decl_index = switch (tv.val.tag()) {
Expand Down Expand Up @@ -3155,12 +3212,23 @@ pub const DeclGen = struct {
.ty = payload_type,
.val = if (tv.val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef),
});
var fields_buf: [3]*const llvm.Value = undefined;

const llvm_ty = try dg.lowerType(tv.ty);
const llvm_field_count = llvm_ty.countStructElementTypes();
if (llvm_field_count > 2) {
assert(llvm_field_count == 3);
fields_buf[2] = llvm_ty.structGetTypeAtIndex(2).getUndef();
}

if (error_align > payload_align) {
const fields: [2]*const llvm.Value = .{ llvm_error_value, llvm_payload_value };
return dg.context.constStruct(&fields, fields.len, .False);
fields_buf[0] = llvm_error_value;
fields_buf[1] = llvm_payload_value;
return dg.context.constStruct(&fields_buf, llvm_field_count, .False);
} else {
const fields: [2]*const llvm.Value = .{ llvm_payload_value, llvm_error_value };
return dg.context.constStruct(&fields, fields.len, .False);
fields_buf[0] = llvm_payload_value;
fields_buf[1] = llvm_error_value;
return dg.context.constStruct(&fields_buf, llvm_field_count, .False);
}
},
.Struct => {
Expand Down Expand Up @@ -3361,7 +3429,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);
};

if (layout.tag_size == 0) {
Expand Down Expand Up @@ -5847,6 +5915,7 @@ pub const FuncGen = struct {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const payload_ty = self.air.typeOf(ty_op.operand);
const non_null_bit = self.context.intType(1).constAllOnes();
comptime assert(optional_layout_version == 2);
if (!payload_ty.hasRuntimeBitsIgnoreComptime()) return non_null_bit;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOfIndex(inst);
Expand Down Expand Up @@ -9300,6 +9369,7 @@ fn intrinsicsAllowed(scalar_ty: Type, target: std.Target) bool {
/// We can do this because for all types, Zig ABI alignment >= LLVM ABI
/// alignment.
const struct_layout_version = 2;
const optional_layout_version = 2;

/// We use the least significant bit of the pointer address to tell us
/// whether the type is fully resolved. Types that are only fwd declared
Expand Down
9 changes: 9 additions & 0 deletions src/codegen/llvm/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ pub const Type = opaque {

pub const countStructElementTypes = LLVMCountStructElementTypes;
extern fn LLVMCountStructElementTypes(StructTy: *const Type) c_uint;

pub const isOpaqueStruct = LLVMIsOpaqueStruct;
extern fn LLVMIsOpaqueStruct(StructTy: *const Type) Bool;

pub const isSized = LLVMTypeIsSized;
extern fn LLVMTypeIsSized(Ty: *const Type) Bool;
};

pub const Module = opaque {
Expand Down Expand Up @@ -1032,6 +1038,9 @@ pub const TargetData = opaque {

pub const abiAlignmentOfType = LLVMABIAlignmentOfType;
extern fn LLVMABIAlignmentOfType(TD: *const TargetData, Ty: *const Type) c_uint;

pub const abiSizeOfType = LLVMABISizeOfType;
extern fn LLVMABISizeOfType(TD: *const TargetData, Ty: *const Type) c_ulonglong;
};

pub const CodeModel = enum(c_int) {
Expand Down
3 changes: 3 additions & 0 deletions src/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2305,6 +2305,9 @@ pub const Type = extern union {
/// true if and only if the type takes up space in memory at runtime.
/// There are two reasons a type will return false:
/// * the type is a comptime-only type. For example, the type `type` itself.
/// - note, however, that a struct can have mixed fields and only the non-comptime-only
/// fields will count towards the ABI size. For example, `struct {T: type, x: i32}`
/// hasRuntimeBits()=true and abiSize()=4
/// * the type has only one possible value, making its ABI size 0.
/// When `ignore_comptime_only` is true, then types that are comptime only
/// may return false positives.
Expand Down