Commit 01c1f41520

Andrew Kelley <andrew@ziglang.org>
2021-10-23 02:12:12
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.
1 parent b24e9b6
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 });
 }
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);
 
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);
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}),
             };
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 });
         },
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.
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;
 
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));
     }
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;
-}
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;
+}
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) {