Commit 5cbb35abd0

Jimmi Holst Christensen <jhc@dismail.de>
2022-01-08 13:57:22
Implement bitOffsetOf
This also refactors getting struct field offsets into two iterators. This will be useful when implementing bitCast at comptime on structs.
1 parent d8d5e2d
src/Sema.zig
@@ -10993,12 +10993,16 @@ 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;
     sema.src = .{ .node_offset_bin_op = inst_data.src_node };
     const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
@@ -11028,8 +11032,24 @@ fn zirOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     };
 
     const target = sema.mod.getTarget();
-    const offset = ty.structFieldOffset(index, target);
-    return sema.addIntUnsigned(Type.comptime_int, offset);
+    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.
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,71 +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()) {
-                            if (i == index) return offset;
-                            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);
-                    }
-                    offset = std.mem.alignForwardGeneric(u64, offset, big_align);
-                    return offset;
-                }
-
-                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()) {
-                        if (i == index) return offset;
-                        continue;
+                    var it = ty.iterateStructOffsets(target);
+                    while (it.next()) |field_offset| {
+                        if (index == field_offset.field)
+                            return field_offset.offset;
                     }
 
-                    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);
+                    return std.mem.alignForwardGeneric(u64, it.offset, it.big_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 => {
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"));
+}
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 {
test/behavior.zig
@@ -80,6 +80,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");
@@ -96,6 +97,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");
@@ -135,7 +137,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");
@@ -169,7 +170,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");