astgen: improved handling of coercion

GenZir struct now has rl_ty_inst field which tracks the result location
type (if any) a block expects all of its results to be coerced to.

Remove a redundant coercion on const local initialization with a
specified type.

Switch expressions, during elision of store_to_block_ptr instructions,
now re-purpose them to be type coercion when the block has a type in the
result location.
This commit is contained in:
Andrew Kelley 2021-03-31 18:05:37 -07:00
parent 08eedc962d
commit 3cebaaad1c
4 changed files with 227 additions and 105 deletions

View File

@ -1415,6 +1415,7 @@ fn varDecl(
const type_inst = try typeExpr(gz, &init_scope.base, var_decl.ast.type_node);
opt_type_inst = type_inst;
init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node);
init_scope.rl_ty_inst = type_inst;
} else {
const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node);
resolve_inferred_alloc = alloc;
@ -1441,20 +1442,13 @@ fn varDecl(
parent_zir.appendAssumeCapacity(src_inst);
}
assert(parent_zir.items.len == expected_len);
const casted_init = if (opt_type_inst != .none)
try gz.addPlNode(.as_node, var_decl.ast.type_node, zir.Inst.As{
.dest_type = opt_type_inst,
.operand = init_inst,
})
else
init_inst;
const sub_scope = try block_arena.create(Scope.LocalVal);
sub_scope.* = .{
.parent = scope,
.gen_zir = gz,
.name = ident_name,
.inst = casted_init,
.inst = init_inst,
.src = name_src,
};
return &sub_scope.base;
@ -3029,25 +3023,48 @@ fn switchExpr(
// all prongs, except for prongs that ended with a noreturn instruction.
// Elide all the `store_to_block_ptr` instructions.
// The break instructions need to have their operands coerced if the
// switch's result location is a `ty`. In this case we overwrite the
// `store_to_block_ptr` instruction with an `as` instruction and repurpose
// it as the break operand.
var extra_index: usize = 0;
extra_index += 2;
extra_index += @boolToInt(multi_cases_len != 0);
if (special_prong != .none) {
if (special_prong != .none) special_prong: {
const body_len_index = extra_index;
const body_len = scalar_cases_payload.items[extra_index];
extra_index += 1;
if (body_len < 2) {
extra_index += body_len;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
break :special_prong;
}
extra_index += body_len - 2;
const store_inst = scalar_cases_payload.items[extra_index];
if (zir_tags[store_inst] == .store_to_block_ptr) {
assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
if (zir_tags[store_inst] != .store_to_block_ptr) {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
break :special_prong;
}
assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
if (block_scope.rl_ty_inst != .none) {
extra_index += 1;
const break_inst = scalar_cases_payload.items[extra_index];
extra_index += 1;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
zir_tags[store_inst] = .as;
zir_datas[store_inst].bin = .{
.lhs = block_scope.rl_ty_inst,
.rhs = zir_datas[break_inst].@"break".operand,
};
zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
} else {
scalar_cases_payload.items[body_len_index] -= 1;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
extra_index += 1;
astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
extra_index += 1;
} else {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
}
} else {
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
@ -3066,16 +3083,29 @@ fn switchExpr(
}
extra_index += body_len - 2;
const store_inst = scalar_cases_payload.items[extra_index];
if (zir_tags[store_inst] == .store_to_block_ptr) {
if (zir_tags[store_inst] != .store_to_block_ptr) {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
continue;
}
if (block_scope.rl_ty_inst != .none) {
extra_index += 1;
const break_inst = scalar_cases_payload.items[extra_index];
extra_index += 1;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
zir_tags[store_inst] = .as;
zir_datas[store_inst].bin = .{
.lhs = block_scope.rl_ty_inst,
.rhs = zir_datas[break_inst].@"break".operand,
};
zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
} else {
assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
scalar_cases_payload.items[body_len_index] -= 1;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
extra_index += 1;
astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
extra_index += 1;
} else {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
}
}
extra_index = 0;
@ -3098,16 +3128,29 @@ fn switchExpr(
}
extra_index += body_len - 2;
const store_inst = multi_cases_payload.items[extra_index];
if (zir_tags[store_inst] == .store_to_block_ptr) {
if (zir_tags[store_inst] != .store_to_block_ptr) {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
continue;
}
if (block_scope.rl_ty_inst != .none) {
extra_index += 1;
const break_inst = multi_cases_payload.items[extra_index];
extra_index += 1;
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
zir_tags[store_inst] = .as;
zir_datas[store_inst].bin = .{
.lhs = block_scope.rl_ty_inst,
.rhs = zir_datas[break_inst].@"break".operand,
};
zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst);
} else {
assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
multi_cases_payload.items[body_len_index] -= 1;
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
extra_index += 1;
astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]);
extra_index += 1;
} else {
extra_index += 2;
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]);
}
}

View File

@ -942,6 +942,8 @@ pub const Scope = struct {
break_result_loc: AstGen.ResultLoc = undefined,
/// When a block has a pointer result location, here it is.
rl_ptr: zir.Inst.Ref = .none,
/// When a block has a type result location, here it is.
rl_ty_inst: zir.Inst.Ref = .none,
/// Keeps track of how many branches of a block did not actually
/// consume the result location. astgen uses this to figure out
/// whether to rely on break instructions or writing to the result
@ -1001,7 +1003,11 @@ pub const Scope = struct {
// we emit ZIR for the block break instructions to have the result values,
// and then rvalue() on that to pass the value to the result location.
switch (parent_rl) {
.discard, .none, .ty, .ptr, .ref => {
.ty => |ty_inst| {
gz.rl_ty_inst = ty_inst;
gz.break_result_loc = parent_rl;
},
.discard, .none, .ptr, .ref => {
gz.break_result_loc = parent_rl;
},
@ -1016,6 +1022,7 @@ pub const Scope = struct {
},
.block_ptr => |parent_block_scope| {
gz.rl_ty_inst = parent_block_scope.rl_ty_inst;
gz.rl_ptr = parent_block_scope.rl_ptr;
gz.break_result_loc = .{ .block_ptr = gz };
},

View File

@ -4104,102 +4104,108 @@ fn coerce(
}
assert(inst.ty.zigTypeTag() != .Undefined);
// null to ?T
if (dest_type.zigTypeTag() == .Optional and inst.ty.zigTypeTag() == .Null) {
return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
}
// T to ?T
if (dest_type.zigTypeTag() == .Optional) {
var buf: Type.Payload.ElemType = undefined;
const child_type = dest_type.optionalChild(&buf);
if (child_type.eql(inst.ty)) {
return sema.wrapOptional(block, dest_type, inst);
} else if (try sema.coerceNum(block, child_type, inst)) |some| {
return sema.wrapOptional(block, dest_type, some);
}
}
// T to E!T or E to E!T
if (dest_type.tag() == .error_union) {
return try sema.wrapErrorUnion(block, dest_type, inst);
}
// 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();
if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
const dst_elem_type = dest_type.elemType();
switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
.ok => {},
.no_match => break :src_array_ptr,
}
switch (dest_type.ptrSize()) {
.Slice => {
// *[N]T to []T
return sema.coerceArrayPtrToSlice(block, dest_type, inst);
},
.C => {
// *[N]T to [*c]T
return sema.coerceArrayPtrToMany(block, dest_type, inst);
},
.Many => {
// *[N]T to [*]T
// *[N:s]T to [*:s]T
const src_sentinel = array_type.sentinel();
const dst_sentinel = dest_type.sentinel();
if (src_sentinel == null and dst_sentinel == null)
return sema.coerceArrayPtrToMany(block, dest_type, inst);
if (src_sentinel) |src_s| {
if (dst_sentinel) |dst_s| {
if (src_s.eql(dst_s)) {
return sema.coerceArrayPtrToMany(block, dest_type, inst);
}
}
}
},
.One => {},
}
}
// comptime known number to other number
if (try sema.coerceNum(block, dest_type, inst)) |some|
return some;
const target = sema.mod.getTarget();
// integer widening
if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
assert(inst.value() == null); // handled above
switch (dest_type.zigTypeTag()) {
.Optional => {
// null to ?T
if (inst.ty.zigTypeTag() == .Null) {
return sema.mod.constInst(sema.arena, inst_src, .{ .ty = dest_type, .val = Value.initTag(.null_value) });
}
const src_info = inst.ty.intInfo(target);
const dst_info = dest_type.intInfo(target);
if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))
{
try sema.requireRuntimeBlock(block, inst_src);
return block.addUnOp(inst_src, dest_type, .intcast, inst);
}
}
// T to ?T
var buf: Type.Payload.ElemType = undefined;
const child_type = dest_type.optionalChild(&buf);
if (child_type.eql(inst.ty)) {
return sema.wrapOptional(block, dest_type, inst);
} else if (try sema.coerceNum(block, child_type, inst)) |some| {
return sema.wrapOptional(block, dest_type, some);
}
},
.Pointer => {
// 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();
if (inst.ty.isConstPtr() and !dest_type.isConstPtr()) break :src_array_ptr;
if (inst.ty.isVolatilePtr() and !dest_type.isVolatilePtr()) break :src_array_ptr;
// float widening
if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) {
assert(inst.value() == null); // handled above
const dst_elem_type = dest_type.elemType();
switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
.ok => {},
.no_match => break :src_array_ptr,
}
const src_bits = inst.ty.floatBits(target);
const dst_bits = dest_type.floatBits(target);
if (dst_bits >= src_bits) {
try sema.requireRuntimeBlock(block, inst_src);
return block.addUnOp(inst_src, dest_type, .floatcast, inst);
}
switch (dest_type.ptrSize()) {
.Slice => {
// *[N]T to []T
return sema.coerceArrayPtrToSlice(block, dest_type, inst);
},
.C => {
// *[N]T to [*c]T
return sema.coerceArrayPtrToMany(block, dest_type, inst);
},
.Many => {
// *[N]T to [*]T
// *[N:s]T to [*:s]T
const src_sentinel = array_type.sentinel();
const dst_sentinel = dest_type.sentinel();
if (src_sentinel == null and dst_sentinel == null)
return sema.coerceArrayPtrToMany(block, dest_type, inst);
if (src_sentinel) |src_s| {
if (dst_sentinel) |dst_s| {
if (src_s.eql(dst_s)) {
return sema.coerceArrayPtrToMany(block, dest_type, inst);
}
}
}
},
.One => {},
}
}
},
.Int => {
// integer widening
if (inst.ty.zigTypeTag() == .Int) {
assert(inst.value() == null); // handled above
const dst_info = dest_type.intInfo(target);
const src_info = inst.ty.intInfo(target);
if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(src_info.signedness == .signed and dst_info.signedness == .unsigned and dst_info.bits > src_info.bits))
{
try sema.requireRuntimeBlock(block, inst_src);
return block.addUnOp(inst_src, dest_type, .intcast, inst);
}
}
},
.Float => {
// float widening
if (inst.ty.zigTypeTag() == .Float) {
assert(inst.value() == null); // handled above
const src_bits = inst.ty.floatBits(target);
const dst_bits = dest_type.floatBits(target);
if (dst_bits >= src_bits) {
try sema.requireRuntimeBlock(block, inst_src);
return block.addUnOp(inst_src, dest_type, .floatcast, inst);
}
}
},
else => {},
}
return sema.mod.fail(&block.base, inst_src, "expected {}, found {}", .{ dest_type, inst.ty });

View File

@ -279,6 +279,72 @@ pub fn addCases(ctx: *TestContext) !void {
\\ return a - 4;
\\}
, "");
// Switch expression missing else case.
case.addError(
\\export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 3 => 3,
\\ 4 => 4,
\\ };
\\ return a - 4;
\\}
, &.{":3:22: error: switch must handle all possibilities"});
// Switch expression, has an unreachable prong.
case.addCompareOutput(
\\export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression, has an unreachable prong and prongs write
// to result locations.
case.addCompareOutput(
\\export fn main() c_int {
\\ var cond: c_int = 0;
\\ var a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 99...300, 12 => 3,
\\ 0 => 4,
\\ 13 => unreachable,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, "");
// Switch expression has duplicate case value.
case.addError(
\\export fn main() c_int {
\\ var cond: c_int = 0;
\\ const a: c_int = switch (cond) {
\\ 1 => 1,
\\ 2 => 2,
\\ 96, 11...13, 97 => 3,
\\ 0 => 4,
\\ 90, 12 => 100,
\\ else => 5,
\\ };
\\ return a - 4;
\\}
, &.{
":8:13: error: duplicate switch value",
":6:15: note: previous value here",
});
}
//{
// var case = ctx.exeFromCompiledC("optionals", .{});