Skip to content

Share a canonical panic handler across compilation units #20240

Description

@mlugg

Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


The panic handler is provided by a function with the following signature (note that this depends on #17969):

/// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
constCompletedStackTrace=externstruct {
index: usize,
instruction_addresses: [*]usize,
};

Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

  • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
  • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
  • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
    • If we are building an executable, the panic handler is exported with strong linkage.
    • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

  • a binary has no panic handler
  • a binary has multiple panic handlers, all with weak linkage
  • a binary has multiple panic handlers, at least two with strong linkage

A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

  • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

Metadata

Metadata

Assignees

No one assigned

    Labels

    breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
      Skip to content

      Share a canonical panic handler across compilation units #20240

      Description

      @mlugg

      Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

      The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


      The panic handler is provided by a function with the following signature (note that this depends on #17969):

      /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
      constCompletedStackTrace=externstruct {
      index: usize,
      instruction_addresses: [*]usize,
      };

      Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

      The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

      • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
      • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
      • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
        • If we are building an executable, the panic handler is exported with strong linkage.
        • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

      This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

      • a binary has no panic handler
      • a binary has multiple panic handlers, all with weak linkage
      • a binary has multiple panic handlers, at least two with strong linkage

      A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

      A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

      A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

      • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

      This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


      Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

      Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

      Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

      Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

      Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


      The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

      I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
          Skip to content

          Share a canonical panic handler across compilation units #20240

          Description

          @mlugg

          Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

          The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


          The panic handler is provided by a function with the following signature (note that this depends on #17969):

          /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
          constCompletedStackTrace=externstruct {
          index: usize,
          instruction_addresses: [*]usize,
          };

          Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

          The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

          • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
          • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
          • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
            • If we are building an executable, the panic handler is exported with strong linkage.
            • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

          This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

          • a binary has no panic handler
          • a binary has multiple panic handlers, all with weak linkage
          • a binary has multiple panic handlers, at least two with strong linkage

          A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

          A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

          A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

          • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

          This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


          Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

          Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

          Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

          Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

          Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


          The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

          I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
              Skip to content

              Share a canonical panic handler across compilation units #20240

              Description

              @mlugg

              Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

              The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


              The panic handler is provided by a function with the following signature (note that this depends on #17969):

              /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
              constCompletedStackTrace=externstruct {
              index: usize,
              instruction_addresses: [*]usize,
              };

              Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

              The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

              • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
              • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
              • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
                • If we are building an executable, the panic handler is exported with strong linkage.
                • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

              This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

              • a binary has no panic handler
              • a binary has multiple panic handlers, all with weak linkage
              • a binary has multiple panic handlers, at least two with strong linkage

              A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

              A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

              A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

              • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

              This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


              Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

              Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

              Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

              Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

              Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


              The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

              I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

                Type

                No type

                Projects

                No projects

                  Milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
                  Skip to content

                  Share a canonical panic handler across compilation units #20240

                  Description

                  @mlugg

                  Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

                  The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


                  The panic handler is provided by a function with the following signature (note that this depends on #17969):

                  /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
                  constCompletedStackTrace=externstruct {
                  index: usize,
                  instruction_addresses: [*]usize,
                  };

                  Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

                  The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

                  • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
                  • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
                  • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
                    • If we are building an executable, the panic handler is exported with strong linkage.
                    • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

                  This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

                  • a binary has no panic handler
                  • a binary has multiple panic handlers, all with weak linkage
                  • a binary has multiple panic handlers, at least two with strong linkage

                  A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

                  A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

                  A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

                  • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

                  This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


                  Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

                  Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

                  Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

                  Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

                  Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


                  The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

                  I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
                      Skip to content

                      Share a canonical panic handler across compilation units #20240

                      Description

                      @mlugg

                      Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

                      The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


                      The panic handler is provided by a function with the following signature (note that this depends on #17969):

                      /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
                      constCompletedStackTrace=externstruct {
                      index: usize,
                      instruction_addresses: [*]usize,
                      };

                      Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

                      The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

                      • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
                      • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
                      • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
                        • If we are building an executable, the panic handler is exported with strong linkage.
                        • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

                      This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

                      • a binary has no panic handler
                      • a binary has multiple panic handlers, all with weak linkage
                      • a binary has multiple panic handlers, at least two with strong linkage

                      A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

                      A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

                      A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

                      • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

                      This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


                      Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

                      Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

                      Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

                      Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

                      Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


                      The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

                      I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
                          Skip to content

                          Share a canonical panic handler across compilation units #20240

                          Description

                          @mlugg

                          Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

                          The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


                          The panic handler is provided by a function with the following signature (note that this depends on #17969):

                          /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
                          constCompletedStackTrace=externstruct {
                          index: usize,
                          instruction_addresses: [*]usize,
                          };

                          Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

                          The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

                          • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
                          • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
                          • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
                            • If we are building an executable, the panic handler is exported with strong linkage.
                            • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

                          This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

                          • a binary has no panic handler
                          • a binary has multiple panic handlers, all with weak linkage
                          • a binary has multiple panic handlers, at least two with strong linkage

                          A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

                          A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

                          A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

                          • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

                          This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


                          Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

                          Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

                          Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

                          Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

                          Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


                          The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

                          I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Share a canonical panic handler across compilation units · Issue #20240 · ziglang/zig · GitHub
                              Skip to content

                              Share a canonical panic handler across compilation units #20240

                              Description

                              @mlugg

                              Whilst looking into porting the UBSan runtime to Zig, I recalled a conversation with Andrew about the fact that @panic ought to only result in a single panic handler across all compilation units in a binary. Ideally, it should be possible to both reference the handler from C code, and define it in C code.

                              The self-hosted compiler does not yet implement this behavior: uses of @panic just emit calls to std.builtin.panic. This issue details a proposal for how to implement this behavior.


                              The panic handler is provided by a function with the following signature (note that this depends on #17969):

                              /// The panic cause is unpacked into `cause`, `data0`, and `data1`; depending on `cause`, the data parameters may be ignored./// If there is no error return trace, then `error_return_trace.index == std.math.maxInt(usize)`./// If there is no return address, then `ret_addr == 0`.externfn__zig_panic(cause: @typeInfo(PanicCause).Union.tag_type.?, data0: usize, data1: usize, error_return_trace: CompletedStackTrace, ret_addr: u64);
                              constCompletedStackTrace=externstruct {
                              index: usize,
                              instruction_addresses: [*]usize,
                              };

                              Calls to @panic are translated into calls to this function, converting the PanicCause, ?StackTrace and ?u64 to the parameter types according to the doc comments.

                              The more difficult question to answer is which compilation unit provides this handler, and how. I propose the following rules.

                              • If -fpanic-handler is passed to a compilation, the panic handler is exported from the ZCU if Zig source files are provided. Otherwise, a single compilation unit containing only the panic handler is implicitly added to the compilation. (The resulting object can be cached globally.)
                              • If -fno-panic-handler is passed to a compilation, no panic handler is exported.
                              • Otherwise, we export the panic handler according to the logic from the -fpanic-handler case, but:
                                • If we are building an executable, the panic handler is exported with strong linkage.
                                • If we are building an object or library (shared or static), the panic handler is exported with weak linkage.

                              This system allows the user to set a preference using -f[no-]panic-handler if desired, but otherwise provides reasonable defaults. Let's analyze the possible failure cases:

                              • a binary has no panic handler
                              • a binary has multiple panic handlers, all with weak linkage
                              • a binary has multiple panic handlers, at least two with strong linkage

                              A binary having no panic handler is not possible under the default settings (it can only happen if -fno-panic-handler is passed to every compilation). In this case, a link error occurs if any CU references the panic handler, but it is due to explicit user overrides preventing successful compilation, which seems like a non-issue.

                              A binary having multiple panic handlers, all with weak linkage, can occur by default if the final executable is not built with Zig - for instance, if a library (static or shared) is built with Zig and linked into a C program which is compiled with clang or gcc. In this case, the linker will functionally choose a random implementation (I think it's technically the first one?). This isn't ideal, but isn't easily resolvable, because no compilation unit is more authoritative than any other in this context. In practice, it will be rare for libraries to override the panic handler, so all will probably have the default, meaning the "randomness" here is typically unobservable. If any CU provides a custom panic handler, then the user can set -fpanic-handler on that one in their build script to give it strong linkage and hence make it override the others.

                              A binary having multiple panic handlers, at least two of which have strong linkage, can never occur by default. It only happens when -fpanic-handler is passed to at least one compilation. The main case I see being confusing here is passing -fpanic-handler to a library, and linking it to a full executable, all with Zig: both will provide panic handlers with strong linkage, at which point we again defer to functionally random selection. This case is unfortunate: the user presumably expected the override to cause the panic handler to deterministically come from the library. For this reason, we augment the executable rule a little:

                              • If we are building an executable, the panic handler is exported with strong linkage, unless another link object already provides it with strong linkage.

                              This extra case removes this unintuitive behavior, whilst still making sure to provide a panic handler in all cases. It makes sure that doing everything with the Zig compiler is a happy case, where everything "just works".


                              Let us now look at the impact of this design in practice. I'll list a few scenarios, and explain how the logic would play out (assuming no -f[no-]panic-handler overrides).

                              Scenario 1: building an executable, from Zig code, which links to a static library, also written in Zig. In this case, the library exports a panic handler with weak linkage, and the executable overrides it with strong linkage. Thus, as is intuitive, the Zig code providing the entrypoint also provides the panic handler.

                              Scenario 2: building a shared library written in Zig, which is used by C code. Here, the shared library exports a weak symbol, and the C binary which uses it naturally exports nothing, so the weak symbol from the shared library is used. The C code could provide a strong __zig_panic symbol if it wants; I'm unsure if this would be used in this case (how does the dynamic linker handle weak symbols?).

                              Scenario 3: building a static library written in Zig, which itself links another static library, also written in Zig. In this case, both handlers have weak linkage, so we get the "random" behavior discussed above. This, of course, assumes that the consumer of the library is compiled with a C compiler; if the final executable is built with Zig, then the executable overrides both panic handlers.

                              Scenario 4: building an executable written in Zig, with a C compilation unit which provides __zig_panic. In this case, the "unless another link object already provides it with strong linkage" rule kicks in, and the C definition overrides the Zig one. I think this is the intuitive behavior in this case.


                              The main thing I'm not certain of is how all of this interacts with shared libraries, since I don't know how they interact with weak linkage in general (are the links resolved at link time or load time?). @kubkon, could you shed some light on this?

                              I'm also unsure if weak linkage is an issue on any target. From what I can see, Windows is a little shaky on it, but does seem to support it in at least some sense -- this will be another @kubkon question I suppose!

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                breakingImplementing this issue could cause existing code to no longer compile or have different behavior.linkingproposalThis issue suggests language modifications. If it also has the "accepted" label then it is planned.

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions