From d97928bf52a217f75c359ae9b1513bd9d42c82d3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 22 Oct 2021 17:50:36 -0700 Subject: [PATCH] stage2: implement aligned variables and `@alignCast` * Sema: implement zirAllocExtended * Sema: implement zirAlignCast --- src/Sema.zig | 114 +++++++++++++++++++++++++++------ test/behavior/align.zig | 79 +++++++++++++++++++++++ test/behavior/align_stage1.zig | 82 ------------------------ 3 files changed, 172 insertions(+), 103 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 9bd0e1037d..80c299d1b0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1940,7 +1940,7 @@ fn zirRetPtr( try sema.requireFunctionBlock(block, src); if (block.is_comptime) { - return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty); + return sema.analyzeComptimeAlloc(block, sema.fn_ret_ty, 0); } const ptr_type = try Type.ptr(sema.arena, .{ @@ -2067,9 +2067,7 @@ fn zirAllocExtended( const type_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; break :blk try sema.resolveType(block, ty_src, type_ref); - } else { - return sema.fail(block, src, "TODO implement Sema.zirAllocExtended inferred", .{}); - }; + } else undefined; const alignment: u16 = if (small.has_align) blk: { const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); @@ -2078,22 +2076,47 @@ fn zirAllocExtended( break :blk alignment; } else 0; + const inferred_alloc_ty = if (small.is_const) + Type.initTag(.inferred_alloc_const) + else + Type.initTag(.inferred_alloc_mut); + if (small.is_comptime) { - return sema.fail(block, src, "TODO implement Sema.zirAllocExtended comptime", .{}); + if (small.has_type) { + return sema.analyzeComptimeAlloc(block, var_ty, alignment); + } else { + return sema.addConstant( + inferred_alloc_ty, + try Value.Tag.inferred_alloc_comptime.create(sema.arena, undefined), + ); + } } - if (!small.is_const) { - return sema.fail(block, src, "TODO implement Sema.zirAllocExtended var", .{}); + if (small.has_type) { + if (!small.is_const) { + try sema.validateVarType(block, ty_src, var_ty, false); + } + const ptr_type = try Type.ptr(sema.arena, .{ + .pointee_type = var_ty, + .@"align" = alignment, + .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + }); + try sema.requireRuntimeBlock(block, src); + try sema.resolveTypeLayout(block, src, var_ty); + return block.addTy(.alloc, ptr_type); } - const ptr_type = try Type.ptr(sema.arena, .{ - .pointee_type = var_ty, - .@"align" = alignment, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - }); - try sema.requireRuntimeBlock(block, src); - try sema.resolveTypeLayout(block, src, var_ty); - return block.addTy(.alloc, ptr_type); + // `Sema.addConstant` does not add the instruction to the block because it is + // not needed in the case of constant values. However here, we plan to "downgrade" + // to a normal instruction when we hit `resolve_inferred_alloc`. So we append + // to the block even though it is currently a `.constant`. + const result = try sema.addConstant( + inferred_alloc_ty, + try Value.Tag.inferred_alloc.create(sema.arena, .{}), + ); + try sema.requireFunctionBlock(block, src); + try block.instructions.append(sema.gpa, Air.refToIndex(result).?); + return result; } fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2103,7 +2126,7 @@ fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const inst_data = sema.code.instructions.items(.data)[inst].un_node; const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node }; const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); - return sema.analyzeComptimeAlloc(block, var_ty); + return sema.analyzeComptimeAlloc(block, var_ty, 0); } fn zirAllocInferredComptime(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2125,7 +2148,7 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const var_decl_src = inst_data.src(); const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); if (block.is_comptime) { - return sema.analyzeComptimeAlloc(block, var_ty); + return sema.analyzeComptimeAlloc(block, var_ty, 0); } const ptr_type = try Type.ptr(sema.arena, .{ .pointee_type = var_ty, @@ -2145,7 +2168,7 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node }; const var_ty = try sema.resolveType(block, ty_src, inst_data.operand); if (block.is_comptime) { - return sema.analyzeComptimeAlloc(block, var_ty); + return sema.analyzeComptimeAlloc(block, var_ty, 0); } try sema.validateVarType(block, ty_src, var_ty, false); const ptr_type = try Type.ptr(sema.arena, .{ @@ -9436,8 +9459,10 @@ fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const inst_data = sema.code.instructions.items(.data)[inst].un_node; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ty = try sema.resolveType(block, operand_src, inst_data.operand); + const resolved_ty = try sema.resolveTypeFields(block, operand_src, ty); + try sema.resolveTypeLayout(block, operand_src, resolved_ty); const target = sema.mod.getTarget(); - const abi_align = ty.abiAlignment(target); + const abi_align = resolved_ty.abiAlignment(target); return sema.addIntUnsigned(Type.comptime_int, abi_align); } @@ -9735,8 +9760,33 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirAlignCast", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const align_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const dest_align = try sema.resolveAlign(block, align_src, extra.lhs); + const ptr = sema.resolveInst(extra.rhs); + const ptr_ty = sema.typeOf(ptr); + + // TODO in addition to pointers, this instruction is supposed to work for + // pointer-like optionals and slices. + try sema.checkPtrType(block, ptr_src, ptr_ty); + + // TODO compile error if the result pointer is comptime known and would have an + // alignment that disagrees with the Decl's alignment. + + // TODO insert safety check that the alignment is correct + + const ptr_info = ptr_ty.ptrInfo().data; + const dest_ty = try Type.ptr(sema.arena, .{ + .pointee_type = ptr_info.pointee_type, + .@"align" = dest_align, + .@"addrspace" = ptr_info.@"addrspace", + .mutable = ptr_info.mutable, + .@"allowzero" = ptr_info.@"allowzero", + .@"volatile" = ptr_info.@"volatile", + .size = ptr_info.size, + }); + return sema.coerceCompatiblePtrs(block, dest_ty, ptr, ptr_src); } fn zirClz(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -9838,6 +9888,18 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr } } +fn checkPtrType( + sema: *Sema, + block: *Block, + ty_src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .Pointer => {}, + else => return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty}), + } +} + fn checkFloatType( sema: *Sema, block: *Block, @@ -14630,14 +14692,22 @@ fn analyzeComptimeAlloc( sema: *Sema, block: *Block, var_type: Type, + alignment: u32, ) CompileError!Air.Inst.Ref { const ptr_type = try Type.ptr(sema.arena, .{ .pointee_type = var_type, .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), + .@"align" = alignment, }); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); + + const align_val = if (alignment == 0) + Value.@"null" + else + try Value.Tag.int_u64.create(anon_decl.arena(), alignment); + const decl = try anon_decl.finish( try var_type.copy(anon_decl.arena()), // There will be stores before the first load, but they may be to sub-elements or @@ -14645,6 +14715,8 @@ fn analyzeComptimeAlloc( // into fields/elements and have those overridden with stored values. Value.undef, ); + decl.align_val = align_val; + try sema.mod.declareDeclDependency(sema.owner_decl, decl); return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{ .runtime_index = block.runtime_index, diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 7affd8a068..76a024b88f 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -41,3 +41,82 @@ test "implicitly decreasing slice alignment" { fn addUnalignedSlice(a: []align(1) const u32, b: []align(1) const u32) u32 { return a[0] + b[0]; } + +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 "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 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 "alignment of structs" { + try expect(@alignOf(struct { + a: i32, + b: *i32, + }) == @alignOf(usize)); +} + +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); + } +} diff --git a/test/behavior/align_stage1.zig b/test/behavior/align_stage1.zig index 6f1e081f76..a4af92368e 100644 --- a/test/behavior/align_stage1.zig +++ b/test/behavior/align_stage1.zig @@ -39,43 +39,6 @@ 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; @@ -180,13 +143,6 @@ fn fnWithAlignedStack() i32 { 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); @@ -196,44 +152,6 @@ test "alignment of function with c calling convention" { 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,