compiler crash when the source code contains pointer array concatenation
Zig Version
0.11.0-dev.3386+99fe2a23c
Steps to Reproduce and Observed Behavior
const MyStruct = struct {
foobar: u8,
};
var my_struct1 = MyStruct{ .foobar = 1 };
var my_struct2 = MyStruct{ .foobar = 2 };
var my_struct3 = MyStruct{ .foobar = 3 };
var my_struct4 = MyStruct{ .foobar = 4 };
const foo: []const *MyStruct = &[_]*MyStruct{ &my_struct1, &my_struct2, &my_struct3, &my_struct4 };
const bar: []const *MyStruct = foo[0..2] ++ foo[2..]; // this line will make compiler crash
pub fn main() void {
@import("std").debug.print("{any}\n", .{bar});
}
Expected Behavior
Should able to compile the code above.
Interestingly, if I use an integer array instead of a pointer array, it actually works as expected.
const foo: []const u8 = &[_]u8{ 1, 2, 3, 4 };
const bar: []const u8 = foo[0..2] ++ foo[2..]; // this line works
The compiler doesn't crash, but instead outputs an expected error message (below).
This (admittedly slightly unintuitive behavior) is intentional, and happens because foo isn't comptime-known. my_structX are all runtime vars, so pointers to them are runtime-known, so in turn foo is runtime-known, and (since it's a slice) that makes its length unknown, which means it can't participate in concatenation (++). Your working example is okay because foo is comptime-known, so its length also is, so it can safely participate in concatenation.
The note on the error message does explain what's going on here:
thing.zig:11:52: error: unable to resolve comptime value
const bar: []const *MyStruct = foo[0..2] ++ foo[2..]; // this line will make compiler crash
~~~^~~~~
thing.zig:11:52: note: slice value being concatenated must be comptime-known
@mlugg The compiler crashes for me as well with attempt to use null value.
Here is the debug error output from the latest master version(0.11.0-dev.3394+c842deea7):
Expand
$ ~/zig/stage3/bin/zig run test.zig
thread 12963 panic: attempt to use null value
Analyzing test.zig: test.zig:bar
%59 = decl_val("MyStruct") token_offset:12:21 to :12:29
%60 = ptr_type(%59, One) node_offset:12:20 to :12:29
%61 = ptr_type(%60, const, Slice) node_offset:12:12 to :12:29
%62 = as_node(@Zir.Inst.Ref.type_type, %61) node_offset:12:12 to :12:29
%63 = decl_ref("foo") token_offset:12:32 to :12:35
%64 = int(2)
%65 = slice_end(%63, @Zir.Inst.Ref.zero, %64) node_offset:12:32 to :12:41
%66 = decl_ref("foo") token_offset:12:45 to :12:48
%67 = int(2)
%68 = slice_start(%66, %67) node_offset:12:45 to :12:53
> %69 = array_cat(%65, %68) node_offset:12:32 to :12:53
%70 = as_node(%62, %69) node_offset:12:32 to :12:53
%71 = break_inline(%58, %70)
For full context, use the command
zig ast-check -t test.zig
in test.zig: test.zig:main
> %76 = decl_val("bar") token_offset:15:6 to :15:9
in test.zig: test.zig:main
> %73 = block({%74..%80}) node_offset:14:20 to :14:21
/home/mint/zig/src/Sema.zig:12748:73: 0x8c0191d in zirArrayCat (zig)
(try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
^
/home/mint/zig/src/Sema.zig:906:66: 0x892bd06 in analyzeBodyInner (zig)
.array_cat => try sema.zirArrayCat(block, inst),
^
/home/mint/zig/src/Sema.zig:821:45: 0x8737491 in analyzeBodyBreak (zig)
const break_inst = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
^
/home/mint/zig/src/Module.zig:4731:50: 0x8733426 in semaDecl (zig)
const result_ref = (try sema.analyzeBodyBreak(&block_scope, body)).?.operand;
^
/home/mint/zig/src/Module.zig:4250:32: 0x853ee71 in ensureDeclAnalyzed (zig)
break :blk mod.semaDecl(decl_index) catch |err| switch (err) {
^
/home/mint/zig/src/Sema.zig:28989:32: 0x91dc275 in ensureDeclAnalyzed (zig)
sema.mod.ensureDeclAnalyzed(decl_index) catch |err| {
^
/home/mint/zig/src/Sema.zig:29040:32: 0x9264552 in analyzeDeclRefInner (zig)
try sema.ensureDeclAnalyzed(decl_index);
^
/home/mint/zig/src/Sema.zig:28959:50: 0x91adf17 in analyzeDeclVal (zig)
const decl_ref = try sema.analyzeDeclRefInner(decl_index, false);
^
/home/mint/zig/src/Sema.zig:6019:31: 0x8c16842 in zirDeclVal (zig)
return sema.analyzeDeclVal(block, src, decl);
^
/home/mint/zig/src/Sema.zig:934:65: 0x892d5e0 in analyzeBodyInner (zig)
.decl_val => try sema.zirDeclVal(block, inst),
^
/home/mint/zig/src/Sema.zig:5503:34: 0x91d60d9 in resolveBlockBody (zig)
if (sema.analyzeBodyInner(child_block, body)) |_| {
^
/home/mint/zig/src/Sema.zig:5486:33: 0x8ce5665 in zirBlock (zig)
return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges);
^
/home/mint/zig/src/Sema.zig:1461:49: 0x8941184 in analyzeBodyInner (zig)
break :blk try sema.zirBlock(block, inst, tags[inst] == .block_comptime);
^
/home/mint/zig/src/Sema.zig:804:30: 0x8bb3858 in analyzeBody (zig)
_ = sema.analyzeBodyInner(block, body) catch |err| switch (err) {
^
/home/mint/zig/src/Module.zig:5753:21: 0x891255f in analyzeFnBody (zig)
sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) {
^
/home/mint/zig/src/Module.zig:4342:40: 0x87143d8 in ensureFuncBodyAnalyzed (zig)
var air = mod.analyzeFnBody(func, sema_arena) catch |err| switch (err) {
^
/home/mint/zig/src/Compilation.zig:3134:42: 0x871219d in processOneJob (zig)
module.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
^
/home/mint/zig/src/Compilation.zig:3071:30: 0x85a415e in performAllTheWork (zig)
try processOneJob(comp, work_item, main_progress_node);
^
/home/mint/zig/src/Compilation.zig:2026:31: 0x85a0658 in update (zig)
try comp.performAllTheWork(main_progress_node);
^
/home/mint/zig/src/main.zig:3835:24: 0x85d1cd4 in updateModule (zig)
try comp.update(main_progress_node);
^
/home/mint/zig/src/main.zig:3270:17: 0x8453280 in buildOutputType (zig)
updateModule(comp) catch |err| switch (err) {
^
/home/mint/zig/src/main.zig:277:31: 0x8402987 in mainArgs (zig)
return buildOutputType(gpa, arena, args, .run);
^
/home/mint/zig/src/main.zig:213:20: 0x83ff495 in main (zig)
return mainArgs(gpa, arena, args);
^
/home/mint/zig/lib/std/start.zig:609:37: 0x83fee84 in main (zig)
const result = root.main() catch |err| {
^
???:?:?: 0x7f833b95a082 in ??? (???)
Aborted (core dumped)
My apologies, I thought this code was meant to be at function scope. At global scope with bar referenced I can indeed repro the crash.
I'm working on this now
Very odd... I expected the fix would lead to a compile error as mlugg said, but with the following patch the code actually works for me and has the intended behavior:
diff --git a/src/Sema.zig b/src/Sema.zig
index f703c6154..a45c8a311 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -13326,12 +13326,12 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
else => unreachable,
}) |rhs_val| {
const lhs_sub_val = if (lhs_ty.isSinglePointer(mod))
- (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
+ (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)) orelse lhs_val
else
lhs_val;
const rhs_sub_val = if (rhs_ty.isSinglePointer(mod))
- (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).?
+ (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)) orelse rhs_val
else
rhs_val;
Output:
{ t.MyStruct{ .foobar = 1 }, t.MyStruct{ .foobar = 2 }, t.MyStruct{ .foobar = 3 }, t.MyStruct{ .foobar = 4 } }
Am I missing something? All zig build test-cases pass. Should I put this in a PR?