Skip to content

Formatted panics cause larger binary sizes #26049

@fatemonkey

Description

@fatemonkey

I was playing around with using Zig for embedded systems...

const target = b.resolveTargetQuery(.{ .abi = .eabi, .cpu_arch = .thumb, .os_tag = .freestanding, .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m0plus } });

...when I noticed the binary size of the following program, even though I was trying to use std.debug.no_panic to make it as small as possible in a debug build while still having the runtime safety checks, since as it says, "it emits as little code as possible, for testing purposes"...

const std = @import("std");

pub const panic = std.debug.no_panic;

pub export fn _start() callconv(.naked) noreturn {
    while (true) {}
}

...was still pretty big (1380660 bytes vs 1393084 without overriding panic).

I inspected the disassembly output with objdump and noticed that there was a lot of calls to Io.Writer and std.fmt functions, which lead to me discovering that even though std.debug.no_panic doesn't itself invoke anything with formatting (while std.debug.FullPanic does by calling std.debug.panicExtra which does formatting), it doesn't matter, because everywhere that calls std.debug.panic or std.debug.panicExtra is circumventing my desire to disallow formatted panics at all (even if that's what std.debug.panic and std.debug.panicExtra are "meant for").

When I replaced the implementation of panicExtra with this, just as an example that removes the formatting...

pub fn panicExtra(
    ret_addr: ?usize,
    comptime format: []const u8,
    args: anytype,
) noreturn {
    @branchHint(.cold);
    _ = args;
    std.builtin.panic.call(format, ret_addr);
}

...the size dropped down to 1141188 bytes total, and after inspecting the disassembly again, I saw that indeed, none of the formatted printing code was being included in my binary anymore.
At this scale, it maybe doesn't seem like huge savings (only 83% of the original file size), but it's not the entire ELF file that gets flashed to my microcontroller anyways, it's just the .text, .data, and .rodata sections. When I compare those sizes, the difference is a lot bigger - with the current std.debug.panicExtra implementation, the total of the actually written sections is 336714 bytes, whereas with my modified non-formatting version, the size is 172630 bytes, which is almost 50% of the original size.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions