Commit 3cebaaad1c

Andrew Kelley <andrew@ziglang.org>
2021-04-01 03:05:37
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.
1 parent 08eedc9
Changed files (4)
src/AstGen.zig
@@ -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);
-                    scalar_cases_payload.items[body_len_index] -= 1;
+                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;
-                    astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]);
+                    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 {
-                    extra_index += 2;
+                    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 {
                 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]);
                 }
             }
 
src/Module.zig
@@ -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 };
                 },
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;
+
+                const dst_elem_type = dest_type.elemType();
+                switch (coerceInMemoryAllowed(dst_elem_type, array_elem_type)) {
+                    .ok => {},
+                    .no_match => break :src_array_ptr,
+                }
 
-    // float widening
-    if (inst.ty.zigTypeTag() == .Float and dest_type.zigTypeTag() == .Float) {
-        assert(inst.value() == null); // handled above
+                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);
 
-        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);
-        }
+                        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 });
test/stage2/cbe.zig
@@ -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", .{});