mirror of
https://github.com/ziglang/zig.git
synced 2026-01-06 05:25:10 +00:00
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:
parent
08eedc962d
commit
3cebaaad1c
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 };
|
||||
},
|
||||
|
||||
170
src/Sema.zig
170
src/Sema.zig
@ -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 });
|
||||
|
||||
@ -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", .{});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user