const std = @import("std"); const expect = std.testing.expect; const builtin = @import("builtin"); const native_arch = builtin.target.cpu.arch; const assert = std.debug.assert; var foo: u8 align(4) = 100; test "global variable alignment" { comptime assert(@typeInfo(@TypeOf(&foo)).pointer.alignment == 4); comptime assert(@TypeOf(&foo) == *align(4) u8); { const slice = @as(*align(4) [1]u8, &foo)[0..]; comptime assert(@TypeOf(slice) == *align(4) [1]u8); } } test "large alignment of local constant" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // flaky const x: f32 align(128) = 12.34; try std.testing.expect(@intFromPtr(&x) % 128 == 0); } test "slicing array of length 1 can not assume runtime index is always zero" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // flaky var runtime_index: usize = 1; _ = &runtime_index; const slice = @as(*align(4) [1]u8, &foo)[runtime_index..]; try expect(@TypeOf(slice) == []u8); try expect(slice.len == 0); try expect(@as(u2, @truncate(@intFromPtr(slice.ptr) - 1)) == 0); } test "default alignment allows unspecified in type syntax" { try expect(*u32 == *align(@alignOf(u32)) u32); } test "implicitly decreasing pointer alignment" { const a: u32 align(4) = 3; const b: u32 align(8) = 4; try expect(addUnaligned(&a, &b) == 7); } fn addUnaligned(a: *align(1) const u32, b: *align(1) const u32) u32 { return a.* + b.*; } test "@alignCast pointers" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var x: u32 align(4) = 1; expectsOnly1(&x); try expect(x == 2); } fn expectsOnly1(x: *align(1) u32) void { expects4(@alignCast(x)); } fn expects4(x: *align(4) u32) void { x.* += 1; } test "alignment of struct with pointer has same alignment as usize" { try expect(@alignOf(struct { a: i32, b: *i32, }) == @alignOf(usize)); } test "alignment and size of structs with 128-bit fields" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO const A = struct { x: u128, }; const B = extern struct { x: u128, y: u8, }; const expected = switch (builtin.cpu.arch) { .amdgcn, .arm, .armeb, .thumb, .thumbeb, .hexagon, .lanai, .mips, .mipsel, .powerpc, .powerpcle, .riscv32, .s390x, => .{ .a_align = 8, .a_size = 16, .b_align = 16, .b_size = 32, .u128_align = 8, .u128_size = 16, .u129_align = 8, .u129_size = 24, }, .aarch64, .aarch64_be, .bpfel, .bpfeb, .mips64, .mips64el, .nvptx, .nvptx64, .powerpc64, .powerpc64le, .sparc, .sparc64, .riscv64, .wasm32, .wasm64, .x86, .x86_64, => .{ .a_align = 16, .a_size = 16, .b_align = 16, .b_size = 32, .u128_align = 16, .u128_size = 16, .u129_align = 16, .u129_size = 32, }, else => return error.SkipZigTest, }; const min_struct_align = if (builtin.zig_backend == .stage2_c) 16 else 0; comptime { assert(@alignOf(A) == @max(expected.a_align, min_struct_align)); assert(@sizeOf(A) == expected.a_size); assert(@alignOf(B) == @max(expected.b_align, min_struct_align)); assert(@sizeOf(B) == expected.b_size); assert(@alignOf(u128) == expected.u128_align); assert(@sizeOf(u128) == expected.u128_size); assert(@alignOf(u129) == expected.u129_align); assert(@sizeOf(u129) == expected.u129_size); } } test "implicitly decreasing slice alignment" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const a: u32 align(4) = 3; const b: u32 align(8) = 4; try expect(addUnalignedSlice(@as(*const [1]u32, &a)[0..], @as(*const [1]u32, &b)[0..]) == 7); } fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { return a[0] + b[0]; } test "specifying alignment allows pointer cast" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try testBytesAlign(0x33); } fn testBytesAlign(b: u8) !void { var bytes align(4) = [_]u8{ b, b, b, b }; const ptr = @as(*u32, @ptrCast(&bytes[0])); try expect(ptr.* == 0x33333333); } test "@alignCast slices" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var array align(4) = [_]u32{ 1, 1 }; const slice = array[0..]; sliceExpectsOnly1(slice); try expect(slice[0] == 2); } fn sliceExpectsOnly1(slice: []align(1) u32) void { sliceExpects4(@alignCast(slice)); } fn sliceExpects4(slice: []align(4) u32) void { slice[0] += 1; } test "return error union with 128-bit integer" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try expect(3 == try give()); } fn give() anyerror!u128 { return 3; } test "page aligned array on stack" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // Large alignment value to make it hard to accidentally pass. var array align(0x1000) = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; var number1: u8 align(16) = 42; var number2: u8 align(16) = 43; try expect(@intFromPtr(&array[0]) & 0xFFF == 0); try expect(array[3] == 4); try expect(@as(u4, @truncate(@intFromPtr(&number1))) == 0); try expect(@as(u4, @truncate(@intFromPtr(&number2))) == 0); try expect(number1 == 42); try expect(number2 == 43); } test "function alignment" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // function alignment is a compile error on wasm if (native_arch.isWasm()) return error.SkipZigTest; const S = struct { fn alignExpr() align(@sizeOf(usize) * 2) i32 { return 1234; } fn align1() align(1) void {} fn align4() align(4) void {} }; try expect(S.alignExpr() == 1234); try expect(@TypeOf(S.alignExpr) == fn () i32); try expect(@TypeOf(&S.alignExpr) == *align(@sizeOf(usize) * 2) const fn () i32); S.align1(); try expect(@TypeOf(S.align1) == fn () void); try expect(@TypeOf(&S.align1) == *align(1) const fn () void); S.align4(); try expect(@TypeOf(S.align4) == fn () void); try expect(@TypeOf(&S.align4) == *align(4) const fn () void); } test "implicitly decreasing fn alignment" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // function alignment is a compile error on wasm if (native_arch.isWasm()) return error.SkipZigTest; try testImplicitlyDecreaseFnAlign(alignedSmall, 1234); try testImplicitlyDecreaseFnAlign(alignedBig, 5678); } fn testImplicitlyDecreaseFnAlign(ptr: *align(1) const fn () i32, answer: i32) !void { try expect(ptr() == answer); } fn alignedSmall() align(8) i32 { return 1234; } fn alignedBig() align(16) i32 { return 5678; } test "@alignCast functions" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // function alignment is a compile error on wasm if (native_arch.isWasm()) return error.SkipZigTest; if (native_arch.isThumb()) return error.SkipZigTest; try expect(fnExpectsOnly1(simple4) == 0x19); } fn fnExpectsOnly1(ptr: *align(1) const fn () i32) i32 { return fnExpects4(@alignCast(ptr)); } fn fnExpects4(ptr: *align(4) const fn () i32) i32 { return ptr(); } fn simple4() align(4) i32 { return 0x19; } test "runtime-known array index has best alignment possible" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // take full advantage of over-alignment var array align(4) = [_]u8{ 1, 2, 3, 4 }; comptime assert(@TypeOf(&array[0]) == *align(4) u8); comptime assert(@TypeOf(&array[1]) == *u8); comptime assert(@TypeOf(&array[2]) == *align(2) u8); comptime assert(@TypeOf(&array[3]) == *u8); // because align is too small but we still figure out to use 2 var bigger align(2) = [_]u64{ 1, 2, 3, 4 }; comptime assert(@TypeOf(&bigger[0]) == *align(2) u64); comptime assert(@TypeOf(&bigger[1]) == *align(2) u64); comptime assert(@TypeOf(&bigger[2]) == *align(2) u64); comptime assert(@TypeOf(&bigger[3]) == *align(2) u64); // because pointer is align 2 and u32 align % 2 == 0 we can assume align 2 var smaller align(2) = [_]u32{ 1, 2, 3, 4 }; var runtime_zero: usize = 0; _ = &runtime_zero; comptime assert(@TypeOf(smaller[runtime_zero..]) == []align(2) u32); comptime assert(@TypeOf(smaller[runtime_zero..].ptr) == [*]align(2) u32); try testIndex(smaller[runtime_zero..].ptr, 0, *align(2) u32); try testIndex(smaller[runtime_zero..].ptr, 1, *align(2) u32); try testIndex(smaller[runtime_zero..].ptr, 2, *align(2) u32); try testIndex(smaller[runtime_zero..].ptr, 3, *align(2) u32); // has to use ABI alignment because index known at runtime only try testIndex2(&array, 0, *u8); try testIndex2(&array, 1, *u8); try testIndex2(&array, 2, *u8); try testIndex2(&array, 3, *u8); } fn testIndex(smaller: [*]align(2) u32, index: usize, comptime T: type) !void { comptime assert(@TypeOf(&smaller[index]) == T); } fn testIndex2(ptr: [*]align(4) u8, index: usize, comptime T: type) !void { comptime assert(@TypeOf(&ptr[index]) == T); } test "alignment of function with c calling convention" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const a = @alignOf(@TypeOf(nothing)); var runtime_nothing = ¬hing; _ = &runtime_nothing; const casted1: *align(a) const u8 = @ptrCast(runtime_nothing); const casted2: *const fn () callconv(.c) void = @ptrCast(casted1); casted2(); } fn nothing() callconv(.c) void {} const DefaultAligned = struct { nevermind: u32, badguy: i128, }; test "read 128-bit field from default aligned struct in stack memory" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var default_aligned = DefaultAligned{ .nevermind = 1, .badguy = 12, }; _ = &default_aligned; try expect(12 == default_aligned.badguy); } var default_aligned_global = DefaultAligned{ .nevermind = 1, .badguy = 12, }; test "read 128-bit field from default aligned struct in global memory" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; try expect(12 == default_aligned_global.badguy); } test "struct field explicit alignment" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // flaky if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO const S = struct { const Node = struct { next: *Node, massive_byte: u8 align(64), }; }; var node: S.Node = undefined; node.massive_byte = 100; try expect(node.massive_byte == 100); comptime assert(@TypeOf(&node.massive_byte) == *align(64) u8); try expect(@intFromPtr(&node.massive_byte) % 64 == 0); } test "align(N) on functions" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) { // https://github.com/ziglang/zig/issues/16845 return error.SkipZigTest; } if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) { // This is not supported on MSVC. return error.SkipZigTest; } // function alignment is a compile error on wasm if (native_arch.isWasm()) return error.SkipZigTest; if (native_arch.isThumb()) return error.SkipZigTest; try expect((@intFromPtr(&overaligned_fn) & (0x1000 - 1)) == 0); } fn overaligned_fn() align(0x1000) i32 { return 42; } test "comptime alloc alignment" { // TODO: it's impossible to test this in Zig today, since comptime vars do not have runtime addresses. if (true) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // flaky comptime var bytes1 = [_]u8{0}; _ = &bytes1; comptime var bytes2 align(256) = [_]u8{0}; const bytes2_addr = @intFromPtr(&bytes2); try expect(bytes2_addr & 0xff == 0); } test "@alignCast null" { if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; var ptr: ?*anyopaque = null; _ = &ptr; const aligned: ?*anyopaque = @alignCast(ptr); try expect(aligned == null); } test "alignment of slice element" { const a: []align(1024) const u8 = undefined; try expect(@TypeOf(&a[0]) == *align(1024) const u8); } test "sub-aligned pointer field access" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // Originally reported at https://github.com/ziglang/zig/issues/14904 const Header = extern struct { tag: u32, bytes_len: u32, }; var buf: [9]u8 align(4) = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; const ptr: *align(1) Header = @ptrCast(buf[1..][0..8]); const x = ptr.bytes_len; switch (builtin.cpu.arch.endian()) { .big => try expect(x == 0x06070809), .little => try expect(x == 0x09080706), } } test "alignment of zero-bit types is respected" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO const S = struct { arr: [0]usize = .{} }; comptime assert(@alignOf(void) == 1); comptime assert(@alignOf(u0) == 1); comptime assert(@alignOf([0]usize) == @alignOf(usize)); comptime assert(@alignOf(S) == @alignOf(usize)); var s: S = .{}; var v32: void align(32) = {}; var x32: u0 align(32) = 0; var s32: S align(32) = .{}; var zero: usize = 0; _ = &zero; try expect(@intFromPtr(&s) % @alignOf(usize) == 0); try expect(@intFromPtr(&s.arr) % @alignOf(usize) == 0); try expect(@intFromPtr(s.arr[zero..zero].ptr) % @alignOf(usize) == 0); try expect(@intFromPtr(&v32) % 32 == 0); try expect(@intFromPtr(&x32) % 32 == 0); try expect(@intFromPtr(&s32) % 32 == 0); try expect(@intFromPtr(&s32.arr) % 32 == 0); try expect(@intFromPtr(s32.arr[zero..zero].ptr) % 32 == 0); } test "zero-bit fields in extern struct pad fields appropriately" { if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const S = extern struct { x: u8, a: [0]u16 = .{}, y: u8, }; // `a` should give `S` alignment 2, and pad the `arr` field. comptime assert(@alignOf(S) == 2); comptime assert(@sizeOf(S) == 4); comptime assert(@offsetOf(S, "x") == 0); comptime assert(@offsetOf(S, "a") == 2); comptime assert(@offsetOf(S, "y") == 2); var s: S = .{ .x = 100, .y = 200 }; try expect(@intFromPtr(&s) % 2 == 0); try expect(@intFromPtr(&s.y) - @intFromPtr(&s.x) == 2); try expect(@intFromPtr(&s.y) == @intFromPtr(&s.a)); try expect(@as(*S, @fieldParentPtr("a", &s.a)) == &s); } test "function pointer @intFromPtr/@ptrFromInt roundtrip" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // This only succeeds on Thumb if we handle the Thumb bit correctly; if not, the `@ptrFromInt` // will incorrectly trip an alignment safety check. const nothing_ptr: *const fn () callconv(.c) void = ¬hing; const nothing_int: usize = @intFromPtr(nothing_ptr); const nothing_ptr2: *const fn () callconv(.c) void = @ptrFromInt(nothing_int); try std.testing.expectEqual(nothing_ptr, nothing_ptr2); } test "function pointer align mask" { if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; const int = if (builtin.cpu.arch.isArm() or builtin.cpu.arch.isMIPS()) 0x20202021 else 0x20202020; const unaligned: *const fn () callconv(.c) void = @ptrFromInt(int); const aligned: *align(16) const fn () callconv(.c) void = @alignCast(unaligned); try expect(@intFromPtr(aligned) == int); }