diff options
| -rw-r--r-- | src/Liveness.zig | 3 | ||||
| -rw-r--r-- | src/Module.zig | 11 | ||||
| -rw-r--r-- | src/Sema.zig | 42 | ||||
| -rw-r--r-- | src/arch/aarch64/CodeGen.zig | 3 | ||||
| -rw-r--r-- | src/codegen.zig | 3 | ||||
| -rw-r--r-- | src/codegen/c.zig | 3 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 5 | ||||
| -rw-r--r-- | src/print_air.zig | 19 | ||||
| -rw-r--r-- | test/behavior.zig | 3 | ||||
| -rw-r--r-- | test/behavior/align.zig | 308 | ||||
| -rw-r--r-- | test/behavior/align_stage1.zig | 307 |
11 files changed, 370 insertions, 337 deletions
diff --git a/src/Liveness.zig b/src/Liveness.zig index 5f0919a6cb..499500fddb 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -267,7 +267,6 @@ fn analyzeInst( .set_union_tag, .min, .max, - .slice, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); @@ -363,7 +362,7 @@ fn analyzeInst( const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_operand, .none, .none }); }, - .ptr_elem_ptr, .slice_elem_ptr => { + .ptr_elem_ptr, .slice_elem_ptr, .slice => { const extra = a.air.extraData(Air.Bin, inst_datas[inst].ty_pl.payload).data; return trackOperands(a, new_set, inst, main_tomb, .{ extra.lhs, extra.rhs, .none }); }, diff --git a/src/Module.zig b/src/Module.zig index 3ac523bdc5..de6770d3d7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -772,6 +772,17 @@ pub const Decl = struct { else => false, }; } + + pub fn getAlignment(decl: Decl, target: Target) u32 { + assert(decl.has_tv); + if (decl.align_val.tag() != .null_value) { + // Explicit alignment. + return @intCast(u32, decl.align_val.toUnsignedInt()); + } else { + // Natural alignment. + return decl.ty.abiAlignment(target); + } + } }; /// This state is attached to every Decl when Module emit_h is non-null. diff --git a/src/Sema.zig b/src/Sema.zig index 92a3a6a50f..9bd0e1037d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -11925,18 +11925,37 @@ fn coerce( return sema.coerce(block, dest_ty, inst_as_ptr, inst_src); } + // *T to *[1]T + single_item: { + if (!dest_ty.isSinglePointer()) break :single_item; + if (!inst_ty.isSinglePointer()) break :single_item; + const ptr_elem_ty = inst_ty.childType(); + const array_ty = dest_ty.childType(); + if (array_ty.zigTypeTag() != .Array) break :single_item; + const array_elem_ty = array_ty.childType(); + const dest_is_mut = !dest_ty.isConstPtr(); + if (inst_ty.isConstPtr() and dest_is_mut) break :single_item; + if (inst_ty.isVolatilePtr() and !dest_ty.isVolatilePtr()) break :single_item; + if (inst_ty.ptrAddressSpace() != dest_ty.ptrAddressSpace()) break :single_item; + switch (coerceInMemoryAllowed(array_elem_ty, ptr_elem_ty, dest_is_mut, target)) { + .ok => {}, + .no_match => break :single_item, + } + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); + } + // Coercions where the source is a single pointer to an array. src_array_ptr: { if (!inst_ty.isSinglePointer()) break :src_array_ptr; - const array_type = inst_ty.elemType(); - if (array_type.zigTypeTag() != .Array) break :src_array_ptr; - const array_elem_type = array_type.elemType(); + const array_ty = inst_ty.childType(); + if (array_ty.zigTypeTag() != .Array) break :src_array_ptr; + const array_elem_type = array_ty.childType(); const dest_is_mut = !dest_ty.isConstPtr(); if (inst_ty.isConstPtr() and dest_is_mut) break :src_array_ptr; if (inst_ty.isVolatilePtr() and !dest_ty.isVolatilePtr()) break :src_array_ptr; if (inst_ty.ptrAddressSpace() != dest_ty.ptrAddressSpace()) break :src_array_ptr; - const dst_elem_type = dest_ty.elemType(); + const dst_elem_type = dest_ty.childType(); switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type, dest_is_mut, target)) { .ok => {}, .no_match => break :src_array_ptr, @@ -11949,20 +11968,20 @@ fn coerce( }, .C => { // *[N]T to [*c]T - return sema.coerceArrayPtrToMany(block, dest_ty, inst, inst_src); + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); }, .Many => { // *[N]T to [*]T // *[N:s]T to [*:s]T // *[N:s]T to [*]T if (dest_ty.sentinel()) |dst_sentinel| { - if (array_type.sentinel()) |src_sentinel| { + if (array_ty.sentinel()) |src_sentinel| { if (src_sentinel.eql(dst_sentinel, dst_elem_type)) { - return sema.coerceArrayPtrToMany(block, dest_ty, inst, inst_src); + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } } } else { - return sema.coerceArrayPtrToMany(block, dest_ty, inst, inst_src); + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } }, .One => {}, @@ -12680,7 +12699,7 @@ fn coerceArrayPtrToSlice( return block.addTyOp(.array_to_slice, dest_ty, inst); } -fn coerceArrayPtrToMany( +fn coerceCompatiblePtrs( sema: *Sema, block: *Block, dest_ty: Type, @@ -12888,10 +12907,15 @@ fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { const variable = payload.data; + const alignment: u32 = if (decl.align_val.tag() == .null_value) + 0 + else + @intCast(u32, decl.align_val.toUnsignedInt()); const ty = try Type.ptr(sema.arena, .{ .pointee_type = decl_tv.ty, .mutable = variable.is_mutable, .@"addrspace" = decl.@"addrspace", + .@"align" = alignment, }); return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 155ac6de68..2c6feec70c 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -876,7 +876,8 @@ fn airMax(self: *Self, inst: Air.Inst.Index) !void { } fn airSlice(self: *Self, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement slice for {}", .{self.target.cpu.arch}); return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } diff --git a/src/codegen.zig b/src/codegen.zig index ac9eaeda2a..5f5ee1b549 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1246,7 +1246,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn airSlice(self: *Self, inst: Air.Inst.Index) !void { - const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { else => return self.fail("TODO implement slice for {}", .{self.target.cpu.arch}), }; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index aa1ece7ba3..dd71590566 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1663,7 +1663,8 @@ fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValu fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; - const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; + const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const ptr = try f.resolveInst(bin_op.lhs); const len = try f.resolveInst(bin_op.rhs); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d6b41208f2..3f24bb535d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -581,7 +581,9 @@ pub const DeclGen = struct { } else if (decl.val.castTag(.extern_fn)) |extern_fn| { _ = try self.resolveLlvmFunction(extern_fn.data); } else { + const target = self.module.getTarget(); const global = try self.resolveGlobalDecl(decl); + global.setAlignment(decl.getAlignment(target)); assert(decl.has_tv); const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { const variable = payload.data; @@ -2713,7 +2715,8 @@ pub const FuncGen = struct { fn airSlice(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; - const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data; const ptr = try self.resolveInst(bin_op.lhs); const len = try self.resolveInst(bin_op.rhs); const inst_ty = self.air.typeOfIndex(inst); diff --git a/src/print_air.zig b/src/print_air.zig index 5aec562241..17efa8297d 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -141,7 +141,6 @@ const Writer = struct { .set_union_tag, .min, .max, - .slice, => try w.writeBinOp(s, inst), .is_null, @@ -203,8 +202,11 @@ const Writer = struct { .loop, => try w.writeBlock(s, inst), - .slice_elem_ptr => try w.writeSliceElemPtr(s, inst), - .ptr_elem_ptr => try w.writePtrElemPtr(s, inst), + .slice, + .slice_elem_ptr, + .ptr_elem_ptr, + => try w.writeTyPlBin(s, inst), + .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), .constant => try w.writeConstant(s, inst), @@ -285,16 +287,7 @@ const Writer = struct { try s.print(", {d}", .{extra.field_index}); } - fn writeSliceElemPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { - const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; - const extra = w.air.extraData(Air.Bin, ty_pl.payload).data; - - try w.writeOperand(s, inst, 0, extra.lhs); - try s.writeAll(", "); - try w.writeOperand(s, inst, 1, extra.rhs); - } - - fn writePtrElemPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { + fn writeTyPlBin(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const extra = w.air.extraData(Air.Bin, ty_pl.payload).data; diff --git a/test/behavior.zig b/test/behavior.zig index a44d95df9f..05e05d51fc 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); test { // Tests that pass for both. + _ = @import("behavior/align.zig"); _ = @import("behavior/array.zig"); _ = @import("behavior/atomics.zig"); _ = @import("behavior/basic.zig"); @@ -65,7 +66,7 @@ test { // When all comptime_memory.zig tests pass, #9646 can be closed. // _ = @import("behavior/comptime_memory.zig"); } else { - _ = @import("behavior/align.zig"); + _ = @import("behavior/align_stage1.zig"); _ = @import("behavior/alignof.zig"); _ = @import("behavior/array_stage1.zig"); if (builtin.os.tag != .wasi) { diff --git a/test/behavior/align.zig b/test/behavior/align.zig index e99cf1e0e2..7affd8a068 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -19,42 +19,6 @@ test "global variable alignment" { } } -fn derp() align(@sizeOf(usize) * 2) i32 { - return 1234; -} -fn noop1() align(1) void {} -fn noop4() align(4) void {} - -test "function alignment" { - // function alignment is a compile error on wasm32/wasm64 - if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; - - try expect(derp() == 1234); - try expect(@TypeOf(noop1) == fn () align(1) void); - try expect(@TypeOf(noop4) == fn () align(4) void); - noop1(); - noop4(); -} - -var baz: packed struct { - a: u32, - b: u32, -} = undefined; - -test "packed struct alignment" { - try expect(@TypeOf(&baz.b) == *align(1) u32); -} - -const blah: packed struct { - a: u3, - b: u3, - c: u2, -} = undefined; - -test "bit field alignment" { - try expect(@TypeOf(&blah.b) == *align(1:3:1) const u3); -} - test "default alignment allows unspecified in type syntax" { try expect(*u32 == *align(@alignOf(u32)) u32); } @@ -77,275 +41,3 @@ test "implicitly decreasing slice alignment" { fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { return a[0] + b[0]; } - -test "specifying alignment allows pointer cast" { - try testBytesAlign(0x33); -} -fn testBytesAlign(b: u8) !void { - var bytes align(4) = [_]u8{ - b, - b, - b, - b, - }; - const ptr = @ptrCast(*u32, &bytes[0]); - try expect(ptr.* == 0x33333333); -} - -test "@alignCast pointers" { - var x: u32 align(4) = 1; - expectsOnly1(&x); - try expect(x == 2); -} -fn expectsOnly1(x: *align(1) u32) void { - expects4(@alignCast(4, x)); -} -fn expects4(x: *align(4) u32) void { - x.* += 1; -} - -test "@alignCast slices" { - 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(4, slice)); -} -fn sliceExpects4(slice: []align(4) u32) void { - slice[0] += 1; -} - -test "implicitly decreasing fn alignment" { - // function alignment is a compile error on wasm32/wasm64 - if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; - - try testImplicitlyDecreaseFnAlign(alignedSmall, 1234); - try testImplicitlyDecreaseFnAlign(alignedBig, 5678); -} - -fn testImplicitlyDecreaseFnAlign(ptr: fn () align(1) 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" { - // function alignment is a compile error on wasm32/wasm64 - if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; - if (native_arch == .thumb) return error.SkipZigTest; - - try expect(fnExpectsOnly1(simple4) == 0x19); -} -fn fnExpectsOnly1(ptr: fn () align(1) i32) i32 { - return fnExpects4(@alignCast(4, ptr)); -} -fn fnExpects4(ptr: fn () align(4) i32) i32 { - return ptr(); -} -fn simple4() align(4) i32 { - return 0x19; -} - -test "generic function with align param" { - // function alignment is a compile error on wasm32/wasm64 - if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; - if (native_arch == .thumb) return error.SkipZigTest; - - try expect(whyWouldYouEverDoThis(1) == 0x1); - try expect(whyWouldYouEverDoThis(4) == 0x1); - try expect(whyWouldYouEverDoThis(8) == 0x1); -} - -fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) u8 { - _ = align_bytes; - return 0x1; -} - -test "@ptrCast preserves alignment of bigger source" { - var x: u32 align(16) = 1234; - const ptr = @ptrCast(*u8, &x); - try expect(@TypeOf(ptr) == *align(16) u8); -} - -test "runtime known array index has best alignment possible" { - // take full advantage of over-alignment - var array align(4) = [_]u8{ 1, 2, 3, 4 }; - try expect(@TypeOf(&array[0]) == *align(4) u8); - try expect(@TypeOf(&array[1]) == *u8); - try expect(@TypeOf(&array[2]) == *align(2) u8); - try expect(@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 }; - try expect(@TypeOf(&bigger[0]) == *align(2) u64); - try expect(@TypeOf(&bigger[1]) == *align(2) u64); - try expect(@TypeOf(&bigger[2]) == *align(2) u64); - try expect(@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; - comptime try expect(@TypeOf(smaller[runtime_zero..]) == []align(2) u32); - comptime try expect(@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[runtime_zero..].ptr, 0, *u8); - try testIndex2(array[runtime_zero..].ptr, 1, *u8); - try testIndex2(array[runtime_zero..].ptr, 2, *u8); - try testIndex2(array[runtime_zero..].ptr, 3, *u8); -} -fn testIndex(smaller: [*]align(2) u32, index: usize, comptime T: type) !void { - comptime try expect(@TypeOf(&smaller[index]) == T); -} -fn testIndex2(ptr: [*]align(4) u8, index: usize, comptime T: type) !void { - comptime try expect(@TypeOf(&ptr[index]) == T); -} - -test "alignstack" { - try expect(fnWithAlignedStack() == 1234); -} - -fn fnWithAlignedStack() i32 { - @setAlignStack(256); - return 1234; -} - -test "alignment of structs" { - try expect(@alignOf(struct { - a: i32, - b: *i32, - }) == @alignOf(usize)); -} - -test "alignment of function with c calling convention" { - var runtime_nothing = nothing; - const casted1 = @ptrCast(*const u8, runtime_nothing); - const casted2 = @ptrCast(fn () callconv(.C) void, casted1); - casted2(); -} - -fn nothing() callconv(.C) void {} - -test "return error union with 128-bit integer" { - try expect(3 == try give()); -} -fn give() anyerror!u128 { - return 3; -} - -test "alignment of >= 128-bit integer type" { - try expect(@alignOf(u128) == 16); - try expect(@alignOf(u129) == 16); -} - -test "alignment of struct with 128-bit field" { - try expect(@alignOf(struct { - x: u128, - }) == 16); - - comptime { - try expect(@alignOf(struct { - x: u128, - }) == 16); - } -} - -test "size of extern struct with 128-bit field" { - try expect(@sizeOf(extern struct { - x: u128, - y: u8, - }) == 32); - - comptime { - try expect(@sizeOf(extern struct { - x: u128, - y: u8, - }) == 32); - } -} - -const DefaultAligned = struct { - nevermind: u32, - badguy: i128, -}; - -test "read 128-bit field from default aligned struct in stack memory" { - var default_aligned = DefaultAligned{ - .nevermind = 1, - .badguy = 12, - }; - try expect((@ptrToInt(&default_aligned.badguy) % 16) == 0); - 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" { - try expect((@ptrToInt(&default_aligned_global.badguy) % 16) == 0); - try expect(12 == default_aligned_global.badguy); -} - -test "struct field explicit alignment" { - 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 try expect(@TypeOf(&node.massive_byte) == *align(64) u8); - try expect(@ptrToInt(&node.massive_byte) % 64 == 0); -} - -test "align(@alignOf(T)) T does not force resolution of T" { - const S = struct { - const A = struct { - a: *align(@alignOf(A)) A, - }; - fn doTheTest() void { - suspend { - resume @frame(); - } - _ = bar(@Frame(doTheTest)); - } - fn bar(comptime T: type) *align(@alignOf(T)) T { - ok = true; - return undefined; - } - - var ok = false; - }; - _ = async S.doTheTest(); - try expect(S.ok); -} - -test "align(N) on functions" { - // function alignment is a compile error on wasm32/wasm64 - if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; - if (native_arch == .thumb) return error.SkipZigTest; - - try expect((@ptrToInt(overaligned_fn) & (0x1000 - 1)) == 0); -} -fn overaligned_fn() align(0x1000) i32 { - return 42; -} diff --git a/test/behavior/align_stage1.zig b/test/behavior/align_stage1.zig new file mode 100644 index 0000000000..6f1e081f76 --- /dev/null +++ b/test/behavior/align_stage1.zig @@ -0,0 +1,307 @@ +const std = @import("std"); +const expect = std.testing.expect; +const builtin = @import("builtin"); +const native_arch = builtin.target.cpu.arch; + +fn derp() align(@sizeOf(usize) * 2) i32 { + return 1234; +} +fn noop1() align(1) void {} +fn noop4() align(4) void {} + +test "function alignment" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + try expect(derp() == 1234); + try expect(@TypeOf(noop1) == fn () align(1) void); + try expect(@TypeOf(noop4) == fn () align(4) void); + noop1(); + noop4(); +} + +var baz: packed struct { + a: u32, + b: u32, +} = undefined; + +test "packed struct alignment" { + try expect(@TypeOf(&baz.b) == *align(1) u32); +} + +const blah: packed struct { + a: u3, + b: u3, + c: u2, +} = undefined; + +test "bit field alignment" { + try expect(@TypeOf(&blah.b) == *align(1:3:1) const u3); +} + +test "specifying alignment allows pointer cast" { + try testBytesAlign(0x33); +} +fn testBytesAlign(b: u8) !void { + var bytes align(4) = [_]u8{ b, b, b, b }; + const ptr = @ptrCast(*u32, &bytes[0]); + try expect(ptr.* == 0x33333333); +} + +test "@alignCast pointers" { + var x: u32 align(4) = 1; + expectsOnly1(&x); + try expect(x == 2); +} +fn expectsOnly1(x: *align(1) u32) void { + expects4(@alignCast(4, x)); +} +fn expects4(x: *align(4) u32) void { + x.* += 1; +} + +test "@alignCast slices" { + 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(4, slice)); +} +fn sliceExpects4(slice: []align(4) u32) void { + slice[0] += 1; +} + +test "implicitly decreasing fn alignment" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + + try testImplicitlyDecreaseFnAlign(alignedSmall, 1234); + try testImplicitlyDecreaseFnAlign(alignedBig, 5678); +} + +fn testImplicitlyDecreaseFnAlign(ptr: fn () align(1) 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" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + if (native_arch == .thumb) return error.SkipZigTest; + + try expect(fnExpectsOnly1(simple4) == 0x19); +} +fn fnExpectsOnly1(ptr: fn () align(1) i32) i32 { + return fnExpects4(@alignCast(4, ptr)); +} +fn fnExpects4(ptr: fn () align(4) i32) i32 { + return ptr(); +} +fn simple4() align(4) i32 { + return 0x19; +} + +test "generic function with align param" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + if (native_arch == .thumb) return error.SkipZigTest; + + try expect(whyWouldYouEverDoThis(1) == 0x1); + try expect(whyWouldYouEverDoThis(4) == 0x1); + try expect(whyWouldYouEverDoThis(8) == 0x1); +} + +fn whyWouldYouEverDoThis(comptime align_bytes: u8) align(align_bytes) u8 { + _ = align_bytes; + return 0x1; +} + +test "@ptrCast preserves alignment of bigger source" { + var x: u32 align(16) = 1234; + const ptr = @ptrCast(*u8, &x); + try expect(@TypeOf(ptr) == *align(16) u8); +} + +test "runtime known array index has best alignment possible" { + // take full advantage of over-alignment + var array align(4) = [_]u8{ 1, 2, 3, 4 }; + try expect(@TypeOf(&array[0]) == *align(4) u8); + try expect(@TypeOf(&array[1]) == *u8); + try expect(@TypeOf(&array[2]) == *align(2) u8); + try expect(@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 }; + try expect(@TypeOf(&bigger[0]) == *align(2) u64); + try expect(@TypeOf(&bigger[1]) == *align(2) u64); + try expect(@TypeOf(&bigger[2]) == *align(2) u64); + try expect(@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; + comptime try expect(@TypeOf(smaller[runtime_zero..]) == []align(2) u32); + comptime try expect(@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[runtime_zero..].ptr, 0, *u8); + try testIndex2(array[runtime_zero..].ptr, 1, *u8); + try testIndex2(array[runtime_zero..].ptr, 2, *u8); + try testIndex2(array[runtime_zero..].ptr, 3, *u8); +} +fn testIndex(smaller: [*]align(2) u32, index: usize, comptime T: type) !void { + comptime try expect(@TypeOf(&smaller[index]) == T); +} +fn testIndex2(ptr: [*]align(4) u8, index: usize, comptime T: type) !void { + comptime try expect(@TypeOf(&ptr[index]) == T); +} + +test "alignstack" { + try expect(fnWithAlignedStack() == 1234); +} + +fn fnWithAlignedStack() i32 { + @setAlignStack(256); + return 1234; +} + +test "alignment of structs" { + try expect(@alignOf(struct { + a: i32, + b: *i32, + }) == @alignOf(usize)); +} + +test "alignment of function with c calling convention" { + var runtime_nothing = nothing; + const casted1 = @ptrCast(*const u8, runtime_nothing); + const casted2 = @ptrCast(fn () callconv(.C) void, casted1); + casted2(); +} + +fn nothing() callconv(.C) void {} + +test "return error union with 128-bit integer" { + try expect(3 == try give()); +} +fn give() anyerror!u128 { + return 3; +} + +test "alignment of >= 128-bit integer type" { + try expect(@alignOf(u128) == 16); + try expect(@alignOf(u129) == 16); +} + +test "alignment of struct with 128-bit field" { + try expect(@alignOf(struct { + x: u128, + }) == 16); + + comptime { + try expect(@alignOf(struct { + x: u128, + }) == 16); + } +} + +test "size of extern struct with 128-bit field" { + try expect(@sizeOf(extern struct { + x: u128, + y: u8, + }) == 32); + + comptime { + try expect(@sizeOf(extern struct { + x: u128, + y: u8, + }) == 32); + } +} + +const DefaultAligned = struct { + nevermind: u32, + badguy: i128, +}; + +test "read 128-bit field from default aligned struct in stack memory" { + var default_aligned = DefaultAligned{ + .nevermind = 1, + .badguy = 12, + }; + try expect((@ptrToInt(&default_aligned.badguy) % 16) == 0); + 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" { + try expect((@ptrToInt(&default_aligned_global.badguy) % 16) == 0); + try expect(12 == default_aligned_global.badguy); +} + +test "struct field explicit alignment" { + 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 try expect(@TypeOf(&node.massive_byte) == *align(64) u8); + try expect(@ptrToInt(&node.massive_byte) % 64 == 0); +} + +test "align(@alignOf(T)) T does not force resolution of T" { + const S = struct { + const A = struct { + a: *align(@alignOf(A)) A, + }; + fn doTheTest() void { + suspend { + resume @frame(); + } + _ = bar(@Frame(doTheTest)); + } + fn bar(comptime T: type) *align(@alignOf(T)) T { + ok = true; + return undefined; + } + + var ok = false; + }; + _ = async S.doTheTest(); + try expect(S.ok); +} + +test "align(N) on functions" { + // function alignment is a compile error on wasm32/wasm64 + if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest; + if (native_arch == .thumb) return error.SkipZigTest; + + try expect((@ptrToInt(overaligned_fn) & (0x1000 - 1)) == 0); +} +fn overaligned_fn() align(0x1000) i32 { + return 42; +} |
