stage2: implement aligned variables and @alignCast

* Sema: implement zirAllocExtended
 * Sema: implement zirAlignCast
This commit is contained in:
Andrew Kelley 2021-10-22 17:50:36 -07:00
parent 01c1f41520
commit d97928bf52
3 changed files with 172 additions and 103 deletions

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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,