mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
stage2: slice and alignment fixes
* Fix backend using wrong union field of the slice instruction. * LLVM backend properly sets alignment on global variables. * Sema: add coercion for *T to *[1]T * Sema: pointers to Decls with explicit alignment now have alignment metadata in them.
This commit is contained in:
parent
b24e9b6347
commit
01c1f41520
@ -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 });
|
||||
},
|
||||
|
||||
@ -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.
|
||||
|
||||
42
src/Sema.zig
42
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));
|
||||
}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
@ -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}),
|
||||
};
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
307
test/behavior/align_stage1.zig
Normal file
307
test/behavior/align_stage1.zig
Normal file
@ -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;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user