diff --git a/src/Sema.zig b/src/Sema.zig index aba992d50e..7dd9b3497d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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. diff --git a/src/type.zig b/src/type.zig index f4561769e2..167248a179 100644 --- a/src/type.zig +++ b/src/type.zig @@ -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 => { diff --git a/test/behavior.zig b/test/behavior.zig index 64c076e015..96e5326f2a 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -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"); diff --git a/test/behavior/sizeof_and_typeof.zig b/test/behavior/sizeof_and_typeof.zig index a0fe4855cb..2dbd4b3495 100644 --- a/test/behavior/sizeof_and_typeof.zig +++ b/test/behavior/sizeof_and_typeof.zig @@ -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")); +} diff --git a/test/behavior/sizeof_and_typeof_stage1.zig b/test/behavior/sizeof_and_typeof_stage1.zig index 429b530a93..20cefef0e7 100644 --- a/test/behavior/sizeof_and_typeof_stage1.zig +++ b/test/behavior/sizeof_and_typeof_stage1.zig @@ -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 {