Merge pull request #10542 from Hejsil/stage2-bit-offset-of

Stage2 bitOffsetOf and offsetOf builtin functions
This commit is contained in:
Andrew Kelley 2022-01-08 15:48:14 -05:00 committed by GitHub
commit 7651913fd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 305 additions and 168 deletions

View File

@ -11046,15 +11046,63 @@ fn zirShrExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
}
fn zirBitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
return sema.fail(block, src, "TODO: Sema.zirBitOffsetOf", .{});
const offset = try bitOffsetOf(sema, block, inst);
return sema.addIntUnsigned(Type.comptime_int, offset);
}
fn zirOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const offset = try bitOffsetOf(sema, block, inst);
return sema.addIntUnsigned(Type.comptime_int, offset / 8);
}
fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u64 {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();
return sema.fail(block, src, "TODO: Sema.zirOffsetOf", .{});
sema.src = .{ .node_offset_bin_op = inst_data.src_node };
const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const ty = try sema.resolveType(block, lhs_src, extra.lhs);
const field_name = try sema.resolveConstString(block, rhs_src, extra.rhs);
try sema.resolveTypeLayout(block, lhs_src, ty);
if (ty.tag() != .@"struct") {
return sema.fail(
block,
lhs_src,
"expected struct type, found '{}'",
.{ty},
);
}
const index = ty.structFields().getIndex(field_name) orelse {
return sema.fail(
block,
rhs_src,
"struct '{}' has no field '{s}'",
.{ ty, field_name },
);
};
const target = sema.mod.getTarget();
const layout = ty.containerLayout();
if (layout == .Packed) {
var it = ty.iteratePackedStructOffsets(target);
while (it.next()) |field_offset| {
if (field_offset.field == index) {
return (field_offset.offset * 8) + field_offset.running_bits;
}
}
} else {
var it = ty.iterateStructOffsets(target);
while (it.next()) |field_offset| {
if (field_offset.field == index) {
return field_offset.offset * 8;
}
}
}
unreachable;
}
/// Returns `true` if the type was a comptime_int.

View File

@ -2922,6 +2922,15 @@ pub const Type = extern union {
}
}
pub fn containerLayout(ty: Type) std.builtin.TypeInfo.ContainerLayout {
return switch (ty.tag()) {
.@"struct" => ty.castTag(.@"struct").?.data.layout,
.@"union" => ty.castTag(.@"union").?.data.layout,
.union_tagged => ty.castTag(.union_tagged).?.data.layout,
else => unreachable,
};
}
/// Asserts that the type is an error union.
pub fn errorUnionPayload(self: Type) Type {
return switch (self.tag()) {
@ -3765,6 +3774,116 @@ pub const Type = extern union {
}
}
pub const PackedFieldOffset = struct {
field: usize,
offset: u64,
running_bits: u16,
};
pub const PackedStructOffsetIterator = struct {
field: usize = 0,
offset: u64 = 0,
big_align: u32 = 0,
running_bits: u16 = 0,
struct_obj: *Module.Struct,
target: Target,
pub fn next(it: *PackedStructOffsetIterator) ?PackedFieldOffset {
comptime assert(Type.packed_struct_layout_version == 1);
if (it.struct_obj.fields.count() <= it.field)
return null;
const field = it.struct_obj.fields.values()[it.field];
defer it.field += 1;
if (!field.ty.hasCodeGenBits()) {
return PackedFieldOffset{
.field = it.field,
.offset = it.offset,
.running_bits = it.running_bits,
};
}
const field_align = field.packedAlignment();
if (field_align == 0) {
defer it.running_bits += @intCast(u16, field.ty.bitSize(it.target));
return PackedFieldOffset{
.field = it.field,
.offset = it.offset,
.running_bits = it.running_bits,
};
} else {
it.big_align = @maximum(it.big_align, field_align);
if (it.running_bits != 0) {
var int_payload: Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = it.running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(it.target);
it.big_align = @maximum(it.big_align, int_align);
it.offset = std.mem.alignForwardGeneric(u64, it.offset, int_align);
it.offset += int_ty.abiSize(it.target);
it.running_bits = 0;
}
it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align);
defer it.offset += field.ty.abiSize(it.target);
return PackedFieldOffset{
.field = it.field,
.offset = it.offset,
.running_bits = it.running_bits,
};
}
}
};
/// 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.
pub fn iteratePackedStructOffsets(ty: Type, target: Target) PackedStructOffsetIterator {
const struct_obj = ty.castTag(.@"struct").?.data;
assert(struct_obj.haveLayout());
assert(struct_obj.layout == .Packed);
return .{ .struct_obj = struct_obj, .target = target };
}
pub const FieldOffset = struct {
field: usize,
offset: u64,
};
pub const StructOffsetIterator = struct {
field: usize = 0,
offset: u64 = 0,
big_align: u32 = 0,
struct_obj: *Module.Struct,
target: Target,
pub fn next(it: *StructOffsetIterator) ?FieldOffset {
if (it.struct_obj.fields.count() <= it.field)
return null;
const field = it.struct_obj.fields.values()[it.field];
defer it.field += 1;
if (!field.ty.hasCodeGenBits())
return FieldOffset{ .field = it.field, .offset = it.offset };
const field_align = field.normalAlignment(it.target);
it.big_align = @maximum(it.big_align, field_align);
it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align);
defer it.offset += field.ty.abiSize(it.target);
return FieldOffset{ .field = it.field, .offset = it.offset };
}
};
/// 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.
pub fn iterateStructOffsets(ty: Type, target: Target) StructOffsetIterator {
const struct_obj = ty.castTag(.@"struct").?.data;
assert(struct_obj.haveLayout());
assert(struct_obj.layout != .Packed);
return .{ .struct_obj = struct_obj, .target = target };
}
/// Supports structs and unions.
/// For packed structs, it returns the byte offset of the containing integer.
pub fn structFieldOffset(ty: Type, index: usize, target: Target) u64 {
@ -3774,65 +3893,34 @@ pub const Type = extern union {
assert(struct_obj.haveLayout());
const is_packed = struct_obj.layout == .Packed;
if (!is_packed) {
var offset: u64 = 0;
var big_align: u32 = 0;
for (struct_obj.fields.values()) |field, i| {
if (!field.ty.hasCodeGenBits()) continue;
const field_align = field.normalAlignment(target);
big_align = @maximum(big_align, field_align);
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
if (i == index) return offset;
offset += field.ty.abiSize(target);
var it = ty.iterateStructOffsets(target);
while (it.next()) |field_offset| {
if (index == field_offset.field)
return field_offset.offset;
}
offset = std.mem.alignForwardGeneric(u64, offset, big_align);
return offset;
return std.mem.alignForwardGeneric(u64, it.offset, it.big_align);
}
comptime assert(Type.packed_struct_layout_version == 1);
var offset: u64 = 0;
var big_align: u32 = 0;
var running_bits: u16 = 0;
for (struct_obj.fields.values()) |field, i| {
if (!field.ty.hasCodeGenBits()) continue;
const field_align = field.packedAlignment();
if (field_align == 0) {
if (i == index) return offset;
running_bits += @intCast(u16, field.ty.bitSize(target));
} else {
big_align = @maximum(big_align, field_align);
if (running_bits != 0) {
var int_payload: Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
offset += int_ty.abiSize(target);
running_bits = 0;
}
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
if (i == index) return offset;
offset += field.ty.abiSize(target);
}
var it = ty.iteratePackedStructOffsets(target);
while (it.next()) |field_offset| {
if (index == field_offset.field)
return field_offset.offset;
}
if (running_bits != 0) {
if (it.running_bits != 0) {
var int_payload: Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
.data = it.running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
offset += int_ty.abiSize(target);
it.big_align = @maximum(it.big_align, int_align);
it.offset = std.mem.alignForwardGeneric(u64, it.offset, int_align);
it.offset += int_ty.abiSize(target);
}
offset = std.mem.alignForwardGeneric(u64, offset, big_align);
return offset;
it.offset = std.mem.alignForwardGeneric(u64, it.offset, it.big_align);
return it.offset;
},
.@"union" => return 0,
.union_tagged => {

View File

@ -81,6 +81,7 @@ test {
_ = @import("behavior/bugs/1310.zig");
_ = @import("behavior/bugs/1381.zig");
_ = @import("behavior/bugs/1500.zig");
_ = @import("behavior/bugs/1735.zig");
_ = @import("behavior/bugs/1741.zig");
_ = @import("behavior/bugs/2006.zig");
_ = @import("behavior/bugs/2578.zig");
@ -98,6 +99,7 @@ test {
_ = @import("behavior/generics_llvm.zig");
_ = @import("behavior/math.zig");
_ = @import("behavior/maximum_minimum.zig");
_ = @import("behavior/merge_error_sets.zig");
_ = @import("behavior/namespace_depends_on_compile_var.zig");
_ = @import("behavior/null_llvm.zig");
_ = @import("behavior/optional_llvm.zig");
@ -137,7 +139,6 @@ test {
_ = @import("behavior/bugs/1421.zig");
_ = @import("behavior/bugs/1442.zig");
_ = @import("behavior/bugs/1607.zig");
_ = @import("behavior/bugs/1735.zig");
_ = @import("behavior/bugs/1851.zig");
_ = @import("behavior/bugs/1914.zig");
_ = @import("behavior/bugs/2114.zig");
@ -171,7 +172,6 @@ test {
_ = @import("behavior/if_stage1.zig");
_ = @import("behavior/ir_block_deps.zig");
_ = @import("behavior/math_stage1.zig");
_ = @import("behavior/merge_error_sets.zig");
_ = @import("behavior/misc.zig");
_ = @import("behavior/muladd.zig");
_ = @import("behavior/null_stage1.zig");

View File

@ -47,3 +47,116 @@ fn fn1(alpha: bool) void {
test "lazy @sizeOf result is checked for definedness" {
_ = fn1;
}
const A = struct {
a: u8,
b: u32,
c: u8,
d: u3,
e: u5,
f: u16,
g: u16,
h: u9,
i: u7,
};
const P = packed struct {
a: u8,
b: u32,
c: u8,
d: u3,
e: u5,
f: u16,
g: u16,
h: u9,
i: u7,
};
test "@offsetOf" {
// Packed structs have fixed memory layout
try expect(@offsetOf(P, "a") == 0);
try expect(@offsetOf(P, "b") == 1);
try expect(@offsetOf(P, "c") == 5);
try expect(@offsetOf(P, "d") == 6);
try expect(@offsetOf(P, "e") == 6);
try expect(@offsetOf(P, "f") == 7);
try expect(@offsetOf(P, "g") == 9);
try expect(@offsetOf(P, "h") == 11);
try expect(@offsetOf(P, "i") == 12);
// // Normal struct fields can be moved/padded
var a: A = undefined;
try expect(@ptrToInt(&a.a) - @ptrToInt(&a) == @offsetOf(A, "a"));
try expect(@ptrToInt(&a.b) - @ptrToInt(&a) == @offsetOf(A, "b"));
try expect(@ptrToInt(&a.c) - @ptrToInt(&a) == @offsetOf(A, "c"));
try expect(@ptrToInt(&a.d) - @ptrToInt(&a) == @offsetOf(A, "d"));
try expect(@ptrToInt(&a.e) - @ptrToInt(&a) == @offsetOf(A, "e"));
try expect(@ptrToInt(&a.f) - @ptrToInt(&a) == @offsetOf(A, "f"));
try expect(@ptrToInt(&a.g) - @ptrToInt(&a) == @offsetOf(A, "g"));
try expect(@ptrToInt(&a.h) - @ptrToInt(&a) == @offsetOf(A, "h"));
try expect(@ptrToInt(&a.i) - @ptrToInt(&a) == @offsetOf(A, "i"));
}
test "@offsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" {
const p3a_len = 3;
const P3 = packed struct {
a: [p3a_len]u8,
b: usize,
};
try std.testing.expect(0 == @offsetOf(P3, "a"));
try std.testing.expect(p3a_len == @offsetOf(P3, "b"));
const p5a_len = 5;
const P5 = packed struct {
a: [p5a_len]u8,
b: usize,
};
try std.testing.expect(0 == @offsetOf(P5, "a"));
try std.testing.expect(p5a_len == @offsetOf(P5, "b"));
const p6a_len = 6;
const P6 = packed struct {
a: [p6a_len]u8,
b: usize,
};
try std.testing.expect(0 == @offsetOf(P6, "a"));
try std.testing.expect(p6a_len == @offsetOf(P6, "b"));
const p7a_len = 7;
const P7 = packed struct {
a: [p7a_len]u8,
b: usize,
};
try std.testing.expect(0 == @offsetOf(P7, "a"));
try std.testing.expect(p7a_len == @offsetOf(P7, "b"));
const p9a_len = 9;
const P9 = packed struct {
a: [p9a_len]u8,
b: usize,
};
try std.testing.expect(0 == @offsetOf(P9, "a"));
try std.testing.expect(p9a_len == @offsetOf(P9, "b"));
// 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25 etc. are further cases
}
test "@bitOffsetOf" {
// Packed structs have fixed memory layout
try expect(@bitOffsetOf(P, "a") == 0);
try expect(@bitOffsetOf(P, "b") == 8);
try expect(@bitOffsetOf(P, "c") == 40);
try expect(@bitOffsetOf(P, "d") == 48);
try expect(@bitOffsetOf(P, "e") == 51);
try expect(@bitOffsetOf(P, "f") == 56);
try expect(@bitOffsetOf(P, "g") == 72);
try expect(@offsetOf(A, "a") * 8 == @bitOffsetOf(A, "a"));
try expect(@offsetOf(A, "b") * 8 == @bitOffsetOf(A, "b"));
try expect(@offsetOf(A, "c") * 8 == @bitOffsetOf(A, "c"));
try expect(@offsetOf(A, "d") * 8 == @bitOffsetOf(A, "d"));
try expect(@offsetOf(A, "e") * 8 == @bitOffsetOf(A, "e"));
try expect(@offsetOf(A, "f") * 8 == @bitOffsetOf(A, "f"));
try expect(@offsetOf(A, "g") * 8 == @bitOffsetOf(A, "g"));
}

View File

@ -2,118 +2,6 @@ const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const A = struct {
a: u8,
b: u32,
c: u8,
d: u3,
e: u5,
f: u16,
g: u16,
h: u9,
i: u7,
};
const P = packed struct {
a: u8,
b: u32,
c: u8,
d: u3,
e: u5,
f: u16,
g: u16,
h: u9,
i: u7,
};
test "@offsetOf" {
// Packed structs have fixed memory layout
try expect(@offsetOf(P, "a") == 0);
try expect(@offsetOf(P, "b") == 1);
try expect(@offsetOf(P, "c") == 5);
try expect(@offsetOf(P, "d") == 6);
try expect(@offsetOf(P, "e") == 6);
try expect(@offsetOf(P, "f") == 7);
try expect(@offsetOf(P, "g") == 9);
try expect(@offsetOf(P, "h") == 11);
try expect(@offsetOf(P, "i") == 12);
// Normal struct fields can be moved/padded
var a: A = undefined;
try expect(@ptrToInt(&a.a) - @ptrToInt(&a) == @offsetOf(A, "a"));
try expect(@ptrToInt(&a.b) - @ptrToInt(&a) == @offsetOf(A, "b"));
try expect(@ptrToInt(&a.c) - @ptrToInt(&a) == @offsetOf(A, "c"));
try expect(@ptrToInt(&a.d) - @ptrToInt(&a) == @offsetOf(A, "d"));
try expect(@ptrToInt(&a.e) - @ptrToInt(&a) == @offsetOf(A, "e"));
try expect(@ptrToInt(&a.f) - @ptrToInt(&a) == @offsetOf(A, "f"));
try expect(@ptrToInt(&a.g) - @ptrToInt(&a) == @offsetOf(A, "g"));
try expect(@ptrToInt(&a.h) - @ptrToInt(&a) == @offsetOf(A, "h"));
try expect(@ptrToInt(&a.i) - @ptrToInt(&a) == @offsetOf(A, "i"));
}
test "@offsetOf packed struct, array length not power of 2 or multiple of native pointer width in bytes" {
const p3a_len = 3;
const P3 = packed struct {
a: [p3a_len]u8,
b: usize,
};
try std.testing.expectEqual(0, @offsetOf(P3, "a"));
try std.testing.expectEqual(p3a_len, @offsetOf(P3, "b"));
const p5a_len = 5;
const P5 = packed struct {
a: [p5a_len]u8,
b: usize,
};
try std.testing.expectEqual(0, @offsetOf(P5, "a"));
try std.testing.expectEqual(p5a_len, @offsetOf(P5, "b"));
const p6a_len = 6;
const P6 = packed struct {
a: [p6a_len]u8,
b: usize,
};
try std.testing.expectEqual(0, @offsetOf(P6, "a"));
try std.testing.expectEqual(p6a_len, @offsetOf(P6, "b"));
const p7a_len = 7;
const P7 = packed struct {
a: [p7a_len]u8,
b: usize,
};
try std.testing.expectEqual(0, @offsetOf(P7, "a"));
try std.testing.expectEqual(p7a_len, @offsetOf(P7, "b"));
const p9a_len = 9;
const P9 = packed struct {
a: [p9a_len]u8,
b: usize,
};
try std.testing.expectEqual(0, @offsetOf(P9, "a"));
try std.testing.expectEqual(p9a_len, @offsetOf(P9, "b"));
// 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 25 etc. are further cases
}
test "@bitOffsetOf" {
// Packed structs have fixed memory layout
try expect(@bitOffsetOf(P, "a") == 0);
try expect(@bitOffsetOf(P, "b") == 8);
try expect(@bitOffsetOf(P, "c") == 40);
try expect(@bitOffsetOf(P, "d") == 48);
try expect(@bitOffsetOf(P, "e") == 51);
try expect(@bitOffsetOf(P, "f") == 56);
try expect(@bitOffsetOf(P, "g") == 72);
try expect(@offsetOf(A, "a") * 8 == @bitOffsetOf(A, "a"));
try expect(@offsetOf(A, "b") * 8 == @bitOffsetOf(A, "b"));
try expect(@offsetOf(A, "c") * 8 == @bitOffsetOf(A, "c"));
try expect(@offsetOf(A, "d") * 8 == @bitOffsetOf(A, "d"));
try expect(@offsetOf(A, "e") * 8 == @bitOffsetOf(A, "e"));
try expect(@offsetOf(A, "f") * 8 == @bitOffsetOf(A, "f"));
try expect(@offsetOf(A, "g") * 8 == @bitOffsetOf(A, "g"));
}
test "@sizeOf(T) == 0 doesn't force resolving struct size" {
const S = struct {
const Foo = struct {