Commit 15ff891f04

Jakub Konka <kubkon@jakubkonka.com>
2022-02-02 00:20:41
stage2: pad out (non-packed) struct fields when lowering to bytes
* pad out (non-packed) struct fields when lowering to bytes to be saved in the binary - prior to this change, fields would be saved at non-aligned addresses leading to wrong accesses * add a matching test case to `behavior/struct.zig` tests * fix offset to field calculation in `struct_field_ptr` on `x86_64`
1 parent 521bd2e
Changed files (3)
src
test
behavior
src/arch/x86_64/CodeGen.zig
@@ -1801,13 +1801,12 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
     const struct_field_offset = @intCast(u32, struct_ty.structFieldOffset(index, self.target.*));
     const struct_field_ty = struct_ty.structFieldType(index);
     const struct_field_size = @intCast(u32, struct_field_ty.abiSize(self.target.*));
-    const offset_to_field = struct_size - struct_field_offset - struct_field_size;
 
     const dst_mcv: MCValue = result: {
         switch (mcv) {
             .stack_offset => {
                 const offset_reg = try self.copyToTmpRegister(ptr_ty, .{
-                    .immediate = offset_to_field,
+                    .immediate = struct_field_offset,
                 });
                 self.register_manager.freezeRegs(&.{offset_reg});
                 defer self.register_manager.unfreezeRegs(&.{offset_reg});
@@ -1817,12 +1816,13 @@ fn structFieldPtr(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, inde
                 break :result dst_mcv;
             },
             .ptr_stack_offset => |off| {
+                const offset_to_field = struct_size - struct_field_offset - struct_field_size;
                 const ptr_stack_offset = off + @intCast(i32, offset_to_field);
                 break :result MCValue{ .ptr_stack_offset = ptr_stack_offset };
             },
             .register => |reg| {
                 const offset_reg = try self.copyToTmpRegister(ptr_ty, .{
-                    .immediate = offset_to_field,
+                    .immediate = struct_field_offset,
                 });
                 self.register_manager.freezeRegs(&.{offset_reg});
                 defer self.register_manager.unfreezeRegs(&.{offset_reg});
src/codegen.zig
@@ -373,11 +373,24 @@ pub fn generateSymbol(
         },
         .Struct => {
             // TODO debug info
-            // TODO padding of struct members
+            const struct_obj = typed_value.ty.castTag(.@"struct").?.data;
+            if (struct_obj.layout == .Packed) {
+                return Result{
+                    .fail = try ErrorMsg.create(
+                        bin_file.allocator,
+                        src_loc,
+                        "TODO implement generateSymbol for packed struct",
+                        .{},
+                    ),
+                };
+            }
+
+            const struct_begin = code.items.len;
             const field_vals = typed_value.val.castTag(.@"struct").?.data;
             for (field_vals) |field_val, index| {
                 const field_ty = typed_value.ty.structFieldType(index);
                 if (!field_ty.hasRuntimeBits()) continue;
+
                 switch (try generateSymbol(bin_file, src_loc, .{
                     .ty = field_ty,
                     .val = field_val,
@@ -388,6 +401,16 @@ pub fn generateSymbol(
                     },
                     .fail => |em| return Result{ .fail = em },
                 }
+                const unpadded_field_end = code.items.len - struct_begin;
+
+                // Pad struct members if required
+                const target = bin_file.options.target;
+                const padded_field_end = typed_value.ty.structFieldOffset(index + 1, target);
+                const padding = try math.cast(usize, padded_field_end - unpadded_field_end);
+
+                if (padding > 0) {
+                    try code.writer().writeByteNTimes(0, padding);
+                }
             }
 
             return Result{ .appended = {} };
test/behavior/struct.zig
@@ -18,6 +18,39 @@ test "top level fields" {
     try expect(@as(i32, 1235) == instance.top_level_field);
 }
 
+const StructWithFields = struct {
+    a: u8,
+    b: u32,
+    c: u64,
+    d: u32,
+
+    fn first(self: *const StructWithFields) u8 {
+        return self.a;
+    }
+
+    fn second(self: *const StructWithFields) u32 {
+        return self.b;
+    }
+
+    fn third(self: *const StructWithFields) u64 {
+        return self.c;
+    }
+
+    fn fourth(self: *const StructWithFields) u32 {
+        return self.d;
+    }
+};
+
+test "non-packed struct has fields padded out to the required alignment" {
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+
+    const foo = StructWithFields{ .a = 5, .b = 1, .c = 10, .d = 2 };
+    try expect(foo.first() == 5);
+    try expect(foo.second() == 1);
+    try expect(foo.third() == 10);
+    try expect(foo.fourth() == 2);
+}
+
 const StructWithNoFields = struct {
     fn add(a: i32, b: i32) i32 {
         return a + b;