ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
33.75k stars 2.48k forks source link

A bug in the Zig compiler: stage1/analyze.cpp:621 in get_pointer_to_type_extra2 #9590

Open mountain opened 3 years ago

mountain commented 3 years ago

We are learning Zig, and wrote a special cache following the generic patterns in HashMap, but failed to compile, and it seemed to be compiler bug, but I am not very sure about it.

Semantic Analysis [961/1796] Assertion failed at /Users/runner/work/1/s/src/stage1/analyze.cpp:621 in get_pointer_to_type_extra2. This is a bug in the Zig compiler.thread 3180717 panic: 
Unable to dump stack trace: debug info stripped

The related code is as below:

const std = @import("std");

const Allocator = std.mem.Allocator;

pub fn ArrayCache(
    comptime Context: type,
    comptime E: type,
    comptime limit: usize
) type {

    return struct {
        allocator: *Allocator,
        registry: std.AutoHashMap(Context, std.AutoHashMap([]u8, []E)),

        const Self = @This();

        pub fn init(allocator: *Allocator) Self {
            return .{
                .allocator = allocator,
                .registry = std.AutoHashMap(Context, std.AutoHashMap([]u8, []E)).init(allocator),
            };
        }

        pub fn declare(self: Self, ctx: Context, name: []u8, size: usize) error{LimitExceeded}![]E {
            if (size > limit) return error.LimitExceeded;

            if (!self.registry.contains(ctx)) {
                self.registry.put(std.AutoHashMap([]u8, []E).init(self.allocator));
            }
            var cache = self.registry.get(ctx);
            const key = try std.fmt.allocPrint(self.allocator, "{s}-{d}", .{name, size},);

            if (!cache.registry.contains(key)) {
                cache.put(self.allocator.alloc(E, size));
            }

            return cache.get(key);
        }
    };

}

test "ArrayCache declare" {
    var cache = ArrayCache(i32, i32, 15).init(testing.allocator);
    defer cache.deinit();

    array = try cache.declare(0, "array", 10);
    try testing.expectEqual(array.len, 10);
}

test "ArrayCache limit exceeded" {
    var cache = ArrayCache(i32, i32, 15).init(testing.allocator);
    defer cache.deinit();

    array = try cache.declare(0, "array", 16);
    try testing.expectEqual(array, error.LimitExceeded);
}

test "compile everything" {
    std.testing.refAllDecls(ArrayCache(i32, i32, 15));
}

The OS is Mac, and Zig version is 0.9.0-dev.804+4c9d41730.

Could you check this?

mountain commented 3 years ago

I made some typo in the above code to remove pointers * in generic declarations, but the compiler still failed.

jmc-88 commented 2 years ago

I couldn't get the original code to compile (since there were a few issues with it, and some interfaces changed for the final 0.9.0 release, e.g. allocgate), but I "modernized it" to the following:

const std = @import("std");
const testing = std.testing;
const trait = std.meta.trait;
const Allocator = std.mem.Allocator;

fn autoFree(allocator: Allocator, value: anytype) void {
    const T = @TypeOf(value);
    if (comptime trait.isManyItemPtr(T) or trait.isSlice(T)) {
        allocator.free(value);
    } else if (comptime trait.isSingleItemPtr(T)) {
        allocator.destroy(value);
    }
}

pub fn ArrayCache(comptime Context: type, comptime E: type, comptime limit: usize) type {
    const Cache = std.StringHashMap([]E);
    const Registry = std.AutoHashMap(Context, Cache);

    return struct {
        allocator: Allocator,
        registry: Registry,

        const Self = @This();

        pub fn init(allocator: Allocator) Self {
            return .{
                .allocator = allocator,
                .registry = Registry.init(allocator),
            };
        }

        pub fn deinit(self: *Self) void {
            var re_it = self.registry.iterator();
            while (re_it.next()) |registry_entry| {
                const cache = registry_entry.value_ptr;
                var ce_it = cache.iterator();
                while (ce_it.next()) |cache_entry| {
                    autoFree(self.allocator, cache_entry.key_ptr.*);
                    self.allocator.free(cache_entry.value_ptr.*);
                }
                autoFree(self.allocator, registry_entry.key_ptr.*);
                cache.deinit();
            }
            self.registry.deinit();
        }

        pub fn declare(self: *Self, ctx: Context, name: []const u8, size: usize) error{ OutOfMemory, LimitExceeded }![]E {
            if (size > limit) return error.LimitExceeded;

            var cache = cblk: {
                const gop = try self.registry.getOrPut(ctx);
                if (!gop.found_existing) gop.value_ptr.* = Cache.init(self.allocator);
                break :cblk gop.value_ptr;
            };

            const key = try std.fmt.allocPrint(self.allocator, "{s}-{d}", .{ name, size });
            errdefer self.allocator.free(key);

            return rblk: {
                const cache_gop = try cache.getOrPut(key);
                if (!cache_gop.found_existing) cache_gop.value_ptr.* = try self.allocator.alloc(E, size);
                if (cache_gop.found_existing) self.allocator.free(key);
                break :rblk cache_gop.value_ptr.*;
            };
        }
    };
}

test "ArrayCache declare" {
    var cache = ArrayCache(i32, i32, 15).init(testing.allocator);
    defer cache.deinit();

    const array = try cache.declare(0, "array", 10);
    try testing.expectEqual(@as(usize, 10), array.len);
}

test "ArrayCache limit exceeded" {
    var cache = ArrayCache(i32, i32, 15).init(testing.allocator);
    defer cache.deinit();

    const array = cache.declare(0, "array", 16);
    try testing.expectError(error.LimitExceeded, array);
}

test "compile everything" {
    std.testing.refAllDecls(ArrayCache(i32, i32, 15));
}

and it seems to work fine here:

$ zig version
0.9.0

$ zig test 9590.zig
All 3 tests passed.

Can you confirm whether this is still an issue for you? Otherwise I'd be inclined to close this as an obsolete report.