Skip to content

formalize the panic interface #21520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Sep 28, 2024
Merged

formalize the panic interface #21520

merged 21 commits into from
Sep 28, 2024

Conversation

andrewrk
Copy link
Member

@andrewrk andrewrk commented Sep 26, 2024

The primary goals of this patchset are to eliminate the -fformatted-panics and -fno-formatted-panics command line options, eliminate the mandatory dependency on formatted printing of safety panics generated by the compiler, and eliminate the internal compiler features corresponding to these things.

A secondary goal is to make overriding the panic handler be a smoother use case.

Note that panic messages are unchanged; the default panic handler produces identical messages but without a dependency on std.fmt.

At first, this patchset removed all the panic helpers, making the compiler directly emit calls to std.builtin.panic only. Unfortunately this caused a performance regression in ReleaseSafe builds. For example, here is ReleaseSafe builds of the compiler, master branch vs this, compiling hello world, and this is even with the new code cheating a little bit in the llvm backend with respect to integer overflow panics:

Benchmark 1 (59 runs): master/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           861ms ± 10.6ms     830ms …  915ms          2 ( 3%)        0%
  peak_rss            213MB ±  756KB     212MB …  215MB          0 ( 0%)        0%
  cpu_cycles         4.26G  ± 10.5M     4.24G  … 4.29G           0 ( 0%)        0%
  instructions       8.54G  ± 2.75M     8.54G  … 8.55G           1 ( 2%)        0%
  cache_references    337M  ±  929K      334M  …  339M           2 ( 3%)        0%
  cache_misses       64.9M  ±  193K     64.4M  … 65.3M           0 ( 0%)        0%
  branch_misses      38.6M  ± 95.3K     38.3M  … 38.7M           5 ( 8%)        0%
Benchmark 2 (56 runs): this/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           895ms ± 13.7ms     862ms …  917ms          9 (16%)        💩+  3.9% ±  0.5%
  peak_rss            216MB ±  916KB     215MB …  218MB          0 ( 0%)        💩+  1.2% ±  0.1%
  cpu_cycles         4.47G  ± 13.4M     4.44G  … 4.50G           2 ( 4%)        💩+  4.8% ±  0.1%
  instructions       9.17G  ± 2.16M     9.16G  … 9.17G           0 ( 0%)        💩+  7.3% ±  0.0%
  cache_references    357M  ±  676K      356M  …  358M           0 ( 0%)        💩+  6.0% ±  0.1%
  cache_misses       67.0M  ±  206K     66.6M  … 67.5M           0 ( 0%)        💩+  3.2% ±  0.1%
  branch_misses      39.7M  ± 72.7K     39.5M  … 39.8M           1 ( 2%)        💩+  2.9% ±  0.1%

So I reintroduced the std.builtin panic helpers (but without calling into formatted printing), such as std.builtin.panicUnwrapError, and gained the perf back. However the new code is still cheating with LLVM backend integer overflow safety checks.

ReleaseSafe builds of the compiler, after re-introducing std.builtin helpers:

Benchmark 1 (59 runs): master/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           856ms ± 15.2ms     822ms …  924ms         11 (19%)        0%
  peak_rss            213MB ±  846KB     212MB …  215MB          0 ( 0%)        0%
  cpu_cycles         4.27G  ± 11.4M     4.24G  … 4.29G           0 ( 0%)        0%
  instructions       8.54G  ± 3.09M     8.54G  … 8.55G           1 ( 2%)        0%
  cache_references    336M  ±  921K      335M  …  339M           0 ( 0%)        0%
  cache_misses       64.9M  ±  209K     64.3M  … 65.5M           1 ( 2%)        0%
  branch_misses      38.6M  ± 77.5K     38.3M  … 38.7M           2 ( 3%)        0%
Benchmark 2 (61 runs): this/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           823ms ± 14.5ms     794ms …  844ms          0 ( 0%)        ⚡-  3.9% ±  0.6%
  peak_rss            213MB ±  721KB     212MB …  215MB          0 ( 0%)          -  0.1% ±  0.1%
  cpu_cycles         4.12G  ± 14.2M     4.09G  … 4.17G           2 ( 3%)        ⚡-  3.4% ±  0.1%
  instructions       8.30G  ± 1.72M     8.30G  … 8.30G           0 ( 0%)        ⚡-  2.9% ±  0.0%
  cache_references    321M  ±  670K      320M  …  323M           0 ( 0%)        ⚡-  4.5% ±  0.1%
  cache_misses       62.9M  ±  190K     62.5M  … 63.3M           0 ( 0%)        ⚡-  3.0% ±  0.1%
  branch_misses      37.1M  ± 79.6K     36.8M  … 37.2M           5 ( 8%)        ⚡-  3.9% ±  0.1%

ReleaseSafe builds of the compiler, after re-instating the LLVM integer overflow safety calls:

Benchmark 1 (58 runs): master/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           869ms ± 14.2ms     836ms …  922ms          2 ( 3%)        0%
  peak_rss            213MB ±  727KB     212MB …  215MB          0 ( 0%)        0%
  cpu_cycles         4.32G  ± 32.6M     4.25G  … 4.38G           0 ( 0%)        0%
  instructions       8.54G  ± 3.12M     8.54G  … 8.56G           2 ( 3%)        0%
  cache_references    337M  ±  963K      335M  …  340M           1 ( 2%)        0%
  cache_misses       65.0M  ±  228K     64.5M  … 65.6M           0 ( 0%)        0%
  branch_misses      38.6M  ± 82.3K     38.4M  … 38.7M           0 ( 0%)        0%
Benchmark 2 (59 runs): this/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           850ms ± 11.9ms     808ms …  877ms          2 ( 3%)        ⚡-  2.2% ±  0.6%
  peak_rss            213MB ±  837KB     212MB …  216MB          1 ( 2%)          -  0.0% ±  0.1%
  cpu_cycles         4.24G  ± 35.8M     4.17G  … 4.35G           3 ( 5%)        ⚡-  1.8% ±  0.3%
  instructions       8.41G  ± 2.99M     8.40G  … 8.42G           2 ( 3%)        ⚡-  1.6% ±  0.0%
  cache_references    325M  ± 1.07M      323M  …  328M           1 ( 2%)        ⚡-  3.6% ±  0.1%
  cache_misses       63.0M  ±  369K     62.5M  … 64.6M           4 ( 7%)        ⚡-  3.1% ±  0.2%
  branch_misses      37.3M  ±  129K     37.1M  … 37.9M           3 ( 5%)        ⚡-  3.2% ±  0.1%

That's the final state of the perf in this branch for ReleaseSafe. ReleaseFast is not affected by these changes. Unfortunately the binary size is bigger:

ReleaseSafe builds of the compiler (+3M):

-rwxr-xr-x 1 andy users 317M Sep 25 13:58 master/zig
-rwxr-xr-x 1 andy users 320M Sep 25 20:28 this/zig

Closes #17969

I am putting this up for discussion but I am not sure I want to merge it.

An alternative would be to do these things on top of the patchset before merging it:

  • Embrace panic helpers but put them into a common std.builtin.Panic namespace so that they can all be overridden together, making it still easy to swap out panicking with one symbol. I think it's important for any function that the compiler automatically generates calls to to be user-overridable.
  • Embrace std.fmt for panics by default because it apparently is performing fairly well compared to the alternative contained in this patchset.
  • Provide a slimmer panic alternative that takes advantage of the new Panic namespace that bundles a set of panic helpers that don't call into formatted printing. This fills a similar role to -fno-formatted-panics but is done via overriding std.builtin symbols rather than compiler flags.
  • I suspect the codegen for union inits is inefficient as well as the parameter passing convention for unions. I think if those were improved then this patchset might become more compelling, and the bloat cost here would be largely if not entirely offset.

I am leaning towards doing all of these bullet points before merging, based on the key observation that the std.builtin panic helpers are particularly effective at generating efficient code. I did not take that into account when accepting #17969.

@andrewrk andrewrk requested a review from kprotty as a code owner September 26, 2024 03:59
@andrewrk andrewrk added breaking Implementing this issue could cause existing code to no longer compile or have different behavior. release notes This PR should be mentioned in the release notes. labels Sep 26, 2024
@andrewrk andrewrk removed the request for review from kprotty September 26, 2024 04:03
@andrewrk andrewrk changed the title eliminate formatted panics eliminate mandatory dependency on formatted printing of generated panic function calls Sep 26, 2024
I had to bring back some of the old API so that I could compile the new
compiler with an old compiler.
now that we have a zig1.wasm update it's not needed
Introduces `std.builtin.Panic` which is a complete interface for
panicking. Provide `std.debug.FormattedPanic` and
`std.debug.SimplePanic` and let the user choose, or make their own.
although they would also pass simply reverted to master branch because
I made the deprecated API still work for now (to be removed after 0.14.0
is tagged)
try ip.getOrPutString(gpa, pt.tid, inner_name, .no_embedded_nulls),
) orelse return sema.fail(block, src, "std.builtin missing {s}", .{inner_name});
try sema.ensureNavResolved(src, nav);
return Value.fromInterned(ip.getNav(nav).status.resolved.val);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlugg should this be calling analyzeNavVal instead?

Copy link
Member

@mlugg mlugg Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but also, see Sema.namespaceLookupVal which just wraps these two calls.

Don't worry too much about incremental dependencies for std.builtin stuff at the minute -- incremental updates to std.builtin (and also, more importantly, updates that change the user-provided panic handler) are very broken as-is, this is on my todo list for incremental.

@andrewrk andrewrk changed the title eliminate mandatory dependency on formatted printing of generated panic function calls formalize the panic interface Sep 27, 2024
@andrewrk
Copy link
Member Author

Follow-up: I changed the approach as outlined above, and now the difference vs master branch looks like this:

Debug builds of the compiler binary size (same):

-rwxr-xr-x 1 andy users 345020360 Sep 26 15:23 master/bin/zig
-rwxr-xr-x 1 andy users 345015648 Sep 26 14:25 branch/bin/zig

ReleaseSafe builds of the compiler compiling hello world (insignificant difference):

Benchmark 1 (701 runs): master/zig build-exe ../test/standalone/simple/hello_world/hello.zig          
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           857ms ± 14.1ms     819ms …  924ms         57 ( 8%)        0%
  peak_rss            213MB ±  764KB     211MB …  216MB         10 ( 1%)        0%
  cpu_cycles         4.27G  ± 13.4M     4.24G  … 4.42G           6 ( 1%)        0%
  instructions       8.53G  ± 2.57M     8.53G  … 8.55G          11 ( 2%)        0%
  cache_references    334M  ±  977K      332M  …  343M          13 ( 2%)        0%
  cache_misses       65.0M  ±  196K     64.5M  … 65.7M           5 ( 1%)        0%
  branch_misses      38.4M  ± 84.2K     38.0M  … 38.6M          38 ( 5%)        0%
Benchmark 2 (702 runs): branch/zig build-exe ../test/standalone/simple/hello_world/hello.zig
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           855ms ± 13.1ms     818ms …  881ms         48 ( 7%)          -  0.2% ±  0.2%
  peak_rss            213MB ±  766KB     210MB …  215MB          6 ( 1%)          -  0.2% ±  0.0%
  cpu_cycles         4.26G  ± 9.88M     4.24G  … 4.33G          18 ( 3%)          -  0.2% ±  0.0%
  instructions       8.54G  ± 2.12M     8.53G  … 8.55G          13 ( 2%)          +  0.0% ±  0.0%
  cache_references    337M  ±  895K      334M  …  341M          13 ( 2%)          +  0.7% ±  0.0%
  cache_misses       64.7M  ±  210K     64.0M  … 65.3M           5 ( 1%)          -  0.5% ±  0.0%
  branch_misses      38.6M  ±  106K     38.2M  … 39.0M          59 ( 8%)          +  0.5% ±  0.0%

Now there's a formal interface defined for all the panic helpers, and a std.debug.FormattedPanic and std.debug.SimplePanic implementation of each is provided. The formatted-panics CLI flags are not needed, and the backend CPU feature enum tags are not needed.

One thing that's neat about this approach is that you can omit any given panic function and receive a compile error if a call to that function would have been generated. For compiler-rt we pass empty struct (when not in test mode)!

@andrewrk
Copy link
Member Author

I haven't figured out this crash yet:

thread 2554371 panic: attempt to use null value
/home/andy/src/zig/src/link/Plan9.zig:792:55: 0x6114357 in flushModule (zig)
            const code = data_atom.code.getOwnedCode().?; // lazy symbols must own their code
                                                      ^
/home/andy/src/zig/src/link/Plan9.zig:544:28: 0x5e58e15 in flush (zig)
    return self.flushModule(arena, tid, prog_node);
                           ^
/home/andy/src/zig/src/link.zig:631:77: 0x5c84c5a in flush (zig)
                return @as(*tag.Type(), @fieldParentPtr("base", base)).flush(arena, tid, prog_node);
                                                                            ^
/home/andy/src/zig/src/Compilation.zig:2435:17: 0x5c84326 in flush (zig)
        lf.flush(arena, tid, prog_node) catch |err| switch (err) {
                ^
/home/andy/src/zig/src/Compilation.zig:2395:22: 0x5c87f6d in update (zig)
            try flush(comp, arena, .{
                     ^
/home/andy/src/zig/src/main.zig:4499:20: 0x5c8e98d in updateModule (zig)
    try comp.update(prog_node);
                   ^
/home/andy/src/zig/src/main.zig:3546:21: 0x5cf883b in buildOutputType (zig)
        updateModule(comp, color, root_prog_node) catch |err| switch (err) {
                    ^
/home/andy/src/zig/src/main.zig:258:31: 0x5b57d96 in mainArgs (zig)
        return buildOutputType(gpa, arena, args, .{ .build = .Exe });
                              ^
/home/andy/src/zig/src/main.zig:199:20: 0x5b54df5 in main (zig)
    return mainArgs(gpa, arena, args);
                   ^
/home/andy/src/zig/lib/std/start.zig:619:37: 0x5b5491e in main (zig)
            const result = root.main() catch |err| {
                                    ^

try ip.getOrPutString(gpa, pt.tid, inner_name, .no_embedded_nulls),
) orelse return sema.fail(block, src, "std.builtin missing {s}", .{inner_name});
try sema.ensureNavResolved(src, nav);
return Value.fromInterned(ip.getNav(nav).status.resolved.val);
Copy link
Member

@mlugg mlugg Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but also, see Sema.namespaceLookupVal which just wraps these two calls.

Don't worry too much about incremental dependencies for std.builtin stuff at the minute -- incremental updates to std.builtin (and also, more importantly, updates that change the user-provided panic handler) are very broken as-is, this is on my todo list for incremental.

@mlugg
Copy link
Member

mlugg commented Sep 27, 2024

#20240 is loosely related to this. I'd quite like to see a decision made on that proposal (I'm far from wedded to my specific ideas there, I just want to decide how the panic handler works across TUs).

@andrewrk
Copy link
Member Author

I want to say that this closes #20240 because it makes that problem fully solvable outside the language specification and compiler implementation. It could be quite valuable to have different panic implementations in different compilation units - consider for example fuzz testing where you have the fuzzer compilation unit (-fno-fuzz) and the unit test being fuzzed (-ffuzz). When the former panics, it is a problem in the test harness, whereas the latter could panic due to a test failure and trigger an interesting input being found.

For other use cases where you want the same implementation, any given compilation unit can be marked as the main one that gets the debug info handling code and exports a symbol for other compilations to call into when they panic - just like you would do when mixing Zig and C code.

Unless there can be a more compelling problem statement, I think that proposal can be rejected because the use cases it addresses are already perfectly solvable without it.

better names, return error instead of panicking, better diagnostics, use
the standard APIs for resolving values
@andrewrk
Copy link
Member Author

error: fatal linker error: cannot expand segment seg(0)(__TEXT_ZIG) in virtual memory
    note: TODO: emit relocations to memory locations in self-hosted backends
    note: as a workaround, try increasing pre-allocated virtual memory of each segment

what does this mean? did I regress the machine code size or something?

This experimental target has no recent active maintainer. It's the only
linker backend complaining about this branch and I can't make sense of
the stack trace.

This can be fixed asynchronously by anyone who wants to maintain plan9
support. It does not need to block this branch.
@andrewrk andrewrk removed the breaking Implementing this issue could cause existing code to no longer compile or have different behavior. label Sep 27, 2024
/// This namespace is used by the Zig compiler to emit various kinds of safety
/// panics. These can be overridden by making a public `Panic` namespace in the
/// root source file.
pub const Panic: type = if (@hasDecl(root, "Panic"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a namespace instead of a comptime struct like std.Options to avoid generating unused decls? Shouldn't only accessing the fields at comptime already avoid unused default values being codegened?

@castholm
Copy link
Contributor

I am putting this up for discussion

Overall I like this change (especially the point about the importance of being able to override compiler-generated functions), and it solves the problem with formatted panics in environments with tiny stack spaces since you can now override the default calls to std.debug.PanicExtra. I think the originally proposed PanicCause union felt slightly more elegant than Panic.messages, but both provide the same amount of freedom to the overrider so if the latter simplifies things and generates better code it's a reasonable choice.

But why is the panic override is a namespace and not a struct? With it being a namespace, it would seem that it will run into the same problems with discoverability and locality of error messages as std.options had before it was reworked into a struct (#14432). With the namespace strategy it's not immediately obvious to a user which functions with which signatures and which messages will need to be exported. The expected interface is not clear from std.builtin alone either; the user needs to look at the example implementations std.debug.FormattedPanic/SimplePanic to discover the interface in full.

If std.builtin.Panic was defined as a struct, like std.Options, the interface immediately becomes more self-documenting and clear (and tooling like ZLS will have a much easier time providing completions):

// std.builtin
pub const panic: Panic = if (@hasDecl(root, "panic")) root.panic else std.debug.formatted_panic;

pub const Panic = struct {
    call: fn (msg: []const u8, error_return_trace: ?*const StackTrace, first_trace_addr: ?usize) noreturn,
    sentinelMismatch: fn (expected: anytype, found: @TypeOf(expected)) noreturn,
    unwrapError: fn (error_return_trace: ?*StackTrace, err: anyerror) noreturn,
    outOfBounds: fn (index: usize, len: usize) noreturn,
    startGreaterThanEnd: fn (start: usize, end: usize) noreturn,
    inactiveUnionField: fn (active: anytype, accessed: @TypeOf(active)) noreturn,
    messages: Messages = .{},

    pub const Messages = struct {
        reached_unreachable: []const u8 = "reached unreachable code",
        unwrap_null: []const u8 = "attempt to use null value",
        // ...
        noreturn_returned: []const u8 = "'noreturn' function returned",
    };
};

// root
pub const panic: std.builtin.Panic = .{
    .call = customCallImpl,
    .sentinelMismatch = customSentinelMismatchImpl,
    // ...
    .inactiveUnionField = customInactiveUnionFieldImpl,
};

The only immediate problem I see with this struct strategy is that @TypeOf isn't allowed to reference parameters in function body types like fn (expected: anytype, found: @TypeOf(expected)) noreturn (related: #15409), so those functions would need to be defined as fn (anytype, anytype) noreturn.

But maybe there are other problems with the struct strategy (codegen?) that makes the namespace strategy the better choice in the end. (Nit: If it ends up remaining a namespace, it should be panic and not Panic.)

@andrewrk
Copy link
Member Author

But why is the panic override is a namespace and not a struct?

The same reason that I ended up ditching PanicCause - since many safety checks are generated by the compiler, the codegen is sensitive to minor changes in how the calls are invoked. Even the cost of initializing a tagged union at each panic site was harming the icache.

Maybe it would be OK since the value would be comptime-known, however it also loses the nice property that undesired kinds of panics can be suppressed by omitting the corresponding function. For example this patch does this in compiler-rt:

// Avoid dragging in the runtime safety mechanisms into this .o file, unless
// we're trying to test compiler-rt.
pub const Panic = if (builtin.is_test) std.debug.FormattedPanic else struct {};

This makes it into a compile error if a compiler_rt function would have generated any safety checks.

So I think this idea would require making all the function pointers and messages optional. Or maybe it can be made to work with @compileError in the function bodies for this use case.

Anyway I think it would be worth a try, if it was definitely better ergonomics to do so. Is it for sure though? I might want to hold out for proper interfaces instead of trying to hack one. That change also sounds like a pain in the butt to make so I would want to do that as a follow-up, i.e. merge this and move on to other priorities for now.

kubkon and others added 2 commits September 28, 2024 11:59
in case someone wants to pursue the idea of making the panic interface a
struct, this will reduce churn.
@andrewrk
Copy link
Member Author

andrewrk commented Sep 28, 2024

@mlugg in our voice call I indicated that I wanted to explore the tagged union approach again by trying to make the codegen better for it. However, I think it's still needed to use the helper functions for better safety codegen, particularly for "access of inactive union field".

pub fn inactiveUnionField(active: anytype, accessed: @TypeOf(active)) noreturn {
    @branchHint(.cold);
    std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{
        @tagName(accessed), // triggers an additional safety check
        @tagName(active), // triggers an additional safety check
    });
}

The problem with the PanicCause approach is that the icache cost of those two additional safety checks will be paid at every union field access. Meanwhile the helper function accomplishes moving those two additional safety checks to the body of a cold function. It also means that @tagName does not need to be implemented in the backend in order to use the simple panic handler.

Also in the call you strongly suggested to take the struct approach as outlined by @castholm above, however after sleeping on it, I've come to the same conclusion that I came to before chatting with you: this is a step in the right direction, that should be merged now, and the idea of using the struct is a next step that will come with its own challenges. I will leave that to someone else to implement. I have barely enough motivation remaining on this branch to finish it as-is.

I will push one final commit to reduce churn, reverting the test cases to rely on the deprecated old definitions, with the recommendation that people don't update their code until 0.14.0 is tagged, in case a follow-up improvement happens.

@andrewrk andrewrk enabled auto-merge September 28, 2024 19:19
@mlugg
Copy link
Member

mlugg commented Sep 28, 2024

Hm, okay, I see the issue with the tagged union approach there. I'm still not sure if I'm entirely satisfied with this solution, however, merging as-is for now on the basis that this represents an improvement on status quo seems reasonable to me. Likewise for the struct-vs-namespace issue; I stand by my opinion that an actual struct would be better, but I'm happy to get this in as-is for now.

@andrewrk andrewrk merged commit 0cdec97 into master Sep 28, 2024
10 checks passed
@andrewrk andrewrk deleted the no-formatted-panics branch September 28, 2024 22:58
@xdBronch
Copy link
Contributor

was a pattern where the tag of the PanicCause is a comptime parameter and the payload is sent separately explored? i imagine this would end up similar to what this pr did in terms of codegen since theres no union initialization but a simpler implementation imo, could look something like this for example

pub fn panic(
    comptime tag: std.meta.Tag(std.builtin.PanicCause),
    payload: std.meta.TagPayload(std.builtin.PanicCause, tag),
    _: ?*std.builtin.StackTrace,
    _: ?usize,
) noreturn {
    @branchHint(.cold);

    switch (tag) {
        .unreach => std.debug.panicExtra(null, @returnAddress(), "unreachable", .{}),
        .inactive_union_field => std.debug.panicExtra(null, @returnAddress(), "access of union field '{s}' while field '{s}' is active", .{
            @tagName(payload.accessed),
            @tagName(payload.active),
        }),
        else => @compileError("panic not handled"),
    }
}

@andrewrk
Copy link
Member Author

Can you additionally share the source for PanicCause in this snippet?

@mlugg
Copy link
Member

mlugg commented Sep 29, 2024

My specific question (which I think may be Andrew's too) is: what is the payload of inactive_union_field? It seems like in your implementation it's trying to be "an arbitrary union value", which of course is not representable.

@xdBronch
Copy link
Contributor

Can you additionally share the source for PanicCause in this snippet?

the same as you outlined in #17969/the original implementation of this pr
earlier you had

pub const PanicCause = union(enum) {
    ...
    inactive_union_field: InactiveUnionField,
    ...

    pub const InactiveUnionField = struct {
        active: []const u8,
        accessed: []const u8,
    };
};

so in a call to panic with inactive_union_field as the tag, the payload would be std.builtin.PanicCause.InactiveUnionField

i did write that snippet without realizing you had made this struct hold the names themselves which explains your earlier comment that i got that part of the code from, i suppose that part may complicate it if thats what your question is meaning

@BratishkaErik
Copy link
Contributor

was a pattern where the tag of the PanicCause is a comptime parameter and the payload is sent separately explored?

@amp-59 tried it here
#19764, and IIRC he said it was the most effective variant of interface by measurements (in PR, Discord and Telegram)

@amp-59
Copy link
Contributor

amp-59 commented Sep 29, 2024

it was the most effective variant of interface by measurements (in PR, Discord and Telegram)

It is very complex. The many-functions interface was the most competitive with the comptime panic cause single-function interface, but the poor ergonomics at scale (of the former) and the fact that the design contradicted the stated goal of the issue (#17969) meant that I never tested it really thoroughly like the other three.

I thought to mention it in the summary of #19764; that a many-functions interface could achieve the smallest fail block because no aggregate instantiation was necessary, but I also thought that might cause confusion, and that this would undersell the advantages of the single-function interface (which are really significant at scale).

In other words, yes it is the best overall, but it is not clear-cut. Now that it seems that maintainers are more open to the idea of a many-functions interface I might attempt a more thorough investigation.

scheibo added a commit to pkmn/engine that referenced this pull request Sep 30, 2024
surely im doing this wrong because this is ass
linusg added a commit to linusg/zig that referenced this pull request Oct 6, 2024
lib/std/debug.zig:491:38: error: slice of non-array type 'u16'
            utf16_buffer[len_minus_3][0..3].* = .{ '\r', '\n', 0 };
            ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

lib/std/debug.zig:510:70: error: expected type '?*const anyopaque', found '[]u16'
                _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data);
                                                                     ^~~~~~~~~

Regressed in ziglang#21520.
linusg added a commit to linusg/zig that referenced this pull request Oct 6, 2024
lib/std/debug.zig:491:38: error: slice of non-array type 'u16'
            utf16_buffer[len_minus_3][0..3].* = .{ '\r', '\n', 0 };
            ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

lib/std/debug.zig:510:70: error: expected type '?*const anyopaque', found '[]u16'
                _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data);
                                                                     ^~~~~~~~~

Regressed in ziglang#21520.
bernardassan pushed a commit to bernardassan/zig that referenced this pull request Oct 8, 2024
lib/std/debug.zig:491:38: error: slice of non-array type 'u16'
            utf16_buffer[len_minus_3][0..3].* = .{ '\r', '\n', 0 };
            ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

lib/std/debug.zig:510:70: error: expected type '?*const anyopaque', found '[]u16'
                _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data);
                                                                     ^~~~~~~~~

Regressed in ziglang#21520.
richerfu pushed a commit to richerfu/zig that referenced this pull request Oct 28, 2024
lib/std/debug.zig:491:38: error: slice of non-array type 'u16'
            utf16_buffer[len_minus_3][0..3].* = .{ '\r', '\n', 0 };
            ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~

lib/std/debug.zig:510:70: error: expected type '?*const anyopaque', found '[]u16'
                _ = bs.exit(uefi.handle, .Aborted, exit_msg.len + 1, exit_data);
                                                                     ^~~~~~~~~

Regressed in ziglang#21520.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release notes This PR should be mentioned in the release notes.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

simplify the panic handler function to be only one function; remove -fformatted-panics
8 participants