zig icon indicating copy to clipboard operation
zig copied to clipboard

Unreachable code when calling functions on EnumMap to empty struct at comptime

Open dpassens opened this issue 1 year ago • 1 comments

Zig Version

0.11.0-dev.1907+ecc0108ce

Steps to Reproduce and Observed Behavior

Create an EnumMap with an empty struct as value at comptime. Still at comptime, calling certain functions will cause the compiler to reach unreachable code at value.zig:2564:29 (hash).

I have checked the following EnumMap functions:

  • count fails
  • contains fails
  • put works
  • fetchPut works

The issue can be reproduced by zig testing the following program. Uncomment the commented lines, and everything works as expected.

const std = @import("std");

const K = enum {
    one,
};

const V = struct {
//     number: u32,
};

const numbers = map: {
    var map = std.EnumMap(K, V){};
    map.put(.one, .{
//         .number = 1,
    });
    _ = map.count();
    break :map map;
};

test "comptime" {
    _ = numbers;
}

test "runtime" {
    var map = std.EnumMap(K, V){};
    map.put(.one, .{
//         .number = 1,
    });
    _ = map.count();
}
thread 26804 panic: reached unreachable code
Analyzing main.zig: main.zig:numbers
      %12 = alloc_inferred_comptime_mut() node_offset:12:5 to :12:34
      %13 = decl_ref("std") token_offset:12:15 to :12:18
      %14 = field_call_bind(%13, "EnumMap") node_offset:12:15 to :12:26
      %15 = dbg_stmt(2, 26)
      %16 = call(.compile_time, %14, [
        {
          %17 = decl_val("K") token_offset:12:27 to :12:28
          %18 = break_inline(%16, %17)
        },
        {
          %19 = decl_val("V") token_offset:12:30 to :12:31
          %20 = break_inline(%16, %19)
        },
      ]) node_offset:12:15 to :12:32
      %21 = struct_init_empty(%16) node_offset:12:15 to :12:34
      %22 = store_to_inferred_ptr(%12, %21)
      %23 = resolve_inferred_alloc(%12) node_offset:12:5 to :12:34
      %24 = field_call_bind(%12, "put") node_offset:13:5 to :13:12
      %25 = dbg_stmt(3, 12)
      %26 = call(nodiscard .compile_time, %24, [
        {
          %27 = enum_literal("one") token_offset:13:14 to :13:17
          %28 = break_inline(%26, %27)
        },
        {
          %29 = break_inline(%26, @Zir.Inst.Ref.empty_struct)
        },
      ]) node_offset:13:5 to :13:13
      %30 = field_call_bind(%12, "count") node_offset:16:9 to :16:18
      %31 = dbg_stmt(6, 18)
    > %32 = call(.compile_time, %30, []) node_offset:16:9 to :16:20
      %33 = ensure_result_non_error(%32) node_offset:16:9 to :16:20
      %34 = load(%12) node_offset:17:16 to :17:19
      %35 = break_inline(%11, %34)
    For full context, use the command
      zig ast-check -t main.zig

  in main.zig: main.zig:numbers
    > %11 = block_inline({%12..%35}) node_offset:11:17 to :11:23
  in main.zig: main.zig:test.comptime
    > %41 = decl_val("numbers") token_offset:21:9 to :21:16
  in main.zig: main.zig:test.comptime
    > %38 = block({%39..%45}) node_offset:20:17 to :20:18

/home/david/zig/src/value.zig:2564:29: 0x101f68e in hash (zig)
                    else => unreachable,
                            ^
/home/david/zig/src/value.zig:2551:34: 0x10200f4 in hash (zig)
                    elem_val.hash(elem_ty, hasher, mod);
                                 ^
/home/david/zig/src/value.zig:2561:43: 0x101f637 in hash (zig)
                            field_val.hash(field_ty, hasher, mod);
                                          ^
/home/david/zig/src/TypedValue.zig:41:26: 0xd91a97 in hash (zig)
    return tv.val.hash(tv.ty, hasher, mod);
                         ^
/home/david/zig/src/Module.zig:288:34: 0xd9190e in hash (zig)
            arg.hash(&hasher, ctx.module);
                                 ^
/home/david/zig/lib/std/hash_map.zig:1139:34: 0x1cf747d in getAdapted__anon_335127 (zig)
            const hash = ctx.hash(key);
                                 ^
/home/david/zig/lib/std/hash_map.zig:1260:35: 0x19e2042 in getContext (zig)
            return self.getAdapted(key, ctx);
                                  ^
/home/david/zig/src/Sema.zig:6753:50: 0x15ff403 in analyzeCall (zig)
                if (mod.memoized_calls.getContext(memoized_call_key, .{ .module = mod })) |result| {
                                                 ^
/home/david/zig/src/Sema.zig:6314:32: 0x10c3ed8 in zirCall (zig)
        return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args, bound_arg_src);
                               ^
/home/david/zig/src/Sema.zig:913:62: 0xe1501a in analyzeBodyInner (zig)
            .call                         => try sema.zirCall(block, inst),
                                                             ^
/home/david/zig/src/Sema.zig:812:45: 0xbdc895 in analyzeBodyBreak (zig)
    const break_inst = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
                                            ^
/home/david/zig/src/Sema.zig:1509:55: 0xe27108 in analyzeBodyInner (zig)
                    break :b try sema.analyzeBodyBreak(&child_block, inline_body);
                                                      ^
/home/david/zig/src/Sema.zig:812:45: 0xbdc895 in analyzeBodyBreak (zig)
    const break_inst = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
                                            ^
/home/david/zig/src/Module.zig:4597:50: 0xbd8dae in semaDecl (zig)
    const result_ref = (try sema.analyzeBodyBreak(&block_scope, body)).?.operand;
                                                 ^
/home/david/zig/src/Module.zig:4209:38: 0x9cd406 in ensureDeclAnalyzed (zig)
    const type_changed = mod.semaDecl(decl_index) catch |err| switch (err) {
                                     ^
/home/david/zig/src/Sema.zig:28218:32: 0x1641cf5 in ensureDeclAnalyzed (zig)
    sema.mod.ensureDeclAnalyzed(decl_index) catch |err| {
                               ^
/home/david/zig/src/Sema.zig:28260:32: 0x11b6021 in analyzeDeclRef (zig)
    try sema.ensureDeclAnalyzed(decl_index);
                               ^
/home/david/zig/src/Sema.zig:28188:45: 0x160aca4 in analyzeDeclVal (zig)
    const decl_ref = try sema.analyzeDeclRef(decl_index);
                                            ^
/home/david/zig/src/Sema.zig:5900:31: 0x10c8827 in zirDeclVal (zig)
    return sema.analyzeDeclVal(block, src, decl);
                              ^
/home/david/zig/src/Sema.zig:923:65: 0xe15a25 in analyzeBodyInner (zig)
            .decl_val                     => try sema.zirDeclVal(block, inst),
                                                                ^
/home/david/zig/src/Sema.zig:5421:34: 0x163c0a9 in resolveBlockBody (zig)
        if (sema.analyzeBodyInner(child_block, body)) |_| {
                                 ^
/home/david/zig/src/Sema.zig:5404:85: 0x1188d02 in zirBlock (zig)
    return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges);
                                                                                    ^
/home/david/zig/src/Sema.zig:1454:69: 0xe2637a in analyzeBodyInner (zig)
                if (!block.is_comptime) break :blk try sema.zirBlock(block, inst);
                                                                    ^
/home/david/zig/src/Sema.zig:795:30: 0x106c698 in analyzeBody (zig)
    _ = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
                             ^
/home/david/zig/src/Module.zig:5597:43: 0xdf731c in analyzeFnBody (zig)
    sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
                                          ^
/home/david/zig/src/Module.zig:4295:40: 0xbbc421 in ensureFuncBodyAnalyzed (zig)
            var air = mod.analyzeFnBody(func, sema_arena) catch |err| switch (err) {
                                       ^
/home/david/zig/src/Compilation.zig:3296:42: 0xbba4f5 in processOneJob (zig)
            module.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
                                         ^
/home/david/zig/src/Compilation.zig:3234:30: 0xa10e77 in performAllTheWork (zig)
            try processOneJob(comp, work_item);
                             ^
/home/david/zig/src/Compilation.zig:2425:31: 0xa0c9ee in update (zig)
    try comp.performAllTheWork(main_progress_node);
                              ^
/home/david/zig/src/main.zig:3641:20: 0xa3494d in updateModule (zig)
    try comp.update();
                   ^
/home/david/zig/src/main.zig:3278:17: 0x91a780 in buildOutputType (zig)
    updateModule(gpa, comp, hook) catch |err| switch (err) {
                ^
/home/david/zig/src/main.zig:269:31: 0x8ee334 in mainArgs (zig)
        return buildOutputType(gpa, arena, args, .zig_test);
                              ^
/home/david/zig/src/main.zig:207:20: 0x8ed7e5 in main (zig)
    return mainArgs(gpa, arena, args);
                   ^
/home/david/zig/lib/std/start.zig:617:37: 0x8efecc in main (zig)
            const result = root.main() catch |err| {
                                    ^

Expected Behavior

The compiler should not reach unreachable code.

dpassens avatar Mar 08 '23 17:03 dpassens

Fixed by:

--- a/src/value.zig
+++ b/src/value.zig
@@ -2553,7 +2553,7 @@ pub const Value = extern union {
             },
             .Struct => {
                 switch (val.tag()) {
-                    .empty_struct_value => {},
+                    .empty_struct_value, .the_only_possible_value => {},
                     .aggregate => {
                         const field_values = val.castTag(.aggregate).?.data;
                         for (field_values, 0..) |field_val, i| {

jacobly0 avatar Mar 08 '23 19:03 jacobly0

No longer repros because InternPool.

jacobly0 avatar Jun 22 '23 02:06 jacobly0