Skip to content

Conversation

@luoliwoshang
Copy link
Member

Summary

Move platform-specific sigsetjmp/siglongjmp logic from ssa/eh.go to runtime using build tags, following the same pattern as AllocU.

Architecture

ssa/eh.go
    ↓ calls rtFunc("Sigsetjmp")/rtFunc("Siglongjmp")
runtime/setjmp.go (wrapper)
    ↓ calls setjmp.Sigsetjmp()
clite/setjmp (build tags)
    ├── sigsetjmp.go: default (C.sigsetjmp/C.siglongjmp)
    ├── sigsetjmp_linux.go: Linux (C.__sigsetjmp/C.siglongjmp)
    └── sigsetjmp_baremetal.go: baremetal/wasm (setjmp/longjmp)

Changes

  • Remove platform checks from ssa/eh.go Sigsetjmp/Siglongjmp
  • Add runtime wrapper functions for Sigsetjmp/Siglongjmp
  • Add platform-specific implementations in clite/setjmp
  • Change clite/setjmp LLGoPackage from "decl" to "link"

⚠️ Known Issue

This approach has issues with setjmp/longjmp semantics.

The wrapper functions create intermediate stack frames which break the non-local jump behavior:

defer code -> runtime.Sigsetjmp() -> setjmp.Sigsetjmp() -> C.sigsetjmp
              ↑ new stack frame 1    ↑ new stack frame 2

sigsetjmp saves the stack frame of setjmp.Sigsetjmp(), not the original defer code location. When longjmp jumps back, it goes to the wrong stack position.

This PR is for discussion purposes to explore whether there's a way to make this work (e.g., inline expansion in llgo).

Test Results

  • macOS: crash (exit code -1)
  • Linux: defer execution order is wrong

🤖 Generated with Claude Code

@gemini-code-assist
Copy link

Summary of Changes

Hello @luoliwoshang, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request centralizes the handling of platform-specific sigsetjmp and siglongjmp functions by moving their implementation details from compile-time checks within ssa/eh.go to a more modular runtime system utilizing Go build tags. The goal is to create a cleaner, more maintainable codebase by adopting a consistent pattern for platform-dependent functions. However, the current implementation introduces a significant problem where the non-local jump semantics are disrupted by the added stack frames from the new wrapper functions, rendering it non-functional on tested platforms and prompting its submission as a basis for further discussion and problem-solving.

Highlights

  • Refactoring sigsetjmp/siglongjmp: The pull request refactors the platform-specific detection logic for sigsetjmp and siglongjmp.
  • Runtime-based Platform Detection: The platform detection has been moved from compile-time checks in ssa/eh.go to runtime-level wrappers that utilize Go build tags for different operating systems and architectures, following the pattern of AllocU.
  • New Architecture: The new architecture involves ssa/eh.go calling runtime.Sigsetjmp/runtime.Siglongjmp wrappers, which then delegate to clite/setjmp with platform-specific implementations (default C.sigsetjmp, Linux C.__sigsetjmp, baremetal/wasm setjmp/longjmp).
  • Known Issue with Semantics: A critical known issue exists where intermediate stack frames introduced by the wrapper functions break the non-local jump behavior of sigsetjmp/siglongjmp, leading to crashes on macOS and incorrect defer execution on Linux.
  • Discussion-Oriented PR: This pull request is explicitly marked as being for discussion purposes to explore potential solutions, such as inline expansion in llgo, rather than a fully functional solution.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the platform-specific sigsetjmp/siglongjmp logic by moving it from the compiler to the runtime, using build tags for platform detection. The changes are well-structured and correctly implement the intended refactoring.

As you've correctly identified in the description, this approach introduces a critical issue with the setjmp/longjmp semantics due to the extra stack frames created by the runtime wrappers. This breaks the non-local jump mechanism.

The correct solution, as you've hinted, would likely involve compiler support. The llgo compiler would need to treat runtime.Sigsetjmp and runtime.Siglongjmp as intrinsics and inline them at the call site, thus eliminating the problematic intermediate stack frames. This would achieve the desired separation of concerns without breaking functionality.

This PR is a valuable exploration of the problem space. I've added one minor suggestion to mark an unused parameter for clarity.

Comment on lines +27 to +29
func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int {
return Setjmp((*JmpBuf)(env))
}

Choose a reason for hiding this comment

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

medium

The savemask parameter is unused in this function because setjmp (which is used here for baremetal/wasm) does not support it. To make this explicit and avoid potential linter warnings, it's good practice to rename it to _.

Suggested change
func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int {
return Setjmp((*JmpBuf)(env))
}
func Sigsetjmp(env *SigjmpBuf, _ c.Int) c.Int {
return Setjmp((*JmpBuf)(env))
}

Move platform-specific sigsetjmp/siglongjmp logic from ssa/eh.go to
runtime using build tags. This follows the same pattern as AllocU.

Architecture:
- ssa/eh.go calls rtFunc("Sigsetjmp")/rtFunc("Siglongjmp")
- runtime/setjmp.go wraps clite/setjmp functions
- clite/setjmp uses build tags for platform selection:
  - sigsetjmp.go: default (C.sigsetjmp/C.siglongjmp)
  - sigsetjmp_linux.go: Linux (C.__sigsetjmp/C.siglongjmp)
  - sigsetjmp_baremetal.go: baremetal/wasm (setjmp/longjmp)

Changes:
- Remove platform checks from ssa/eh.go Sigsetjmp/Siglongjmp
- Add runtime wrapper functions for Sigsetjmp/Siglongjmp
- Add platform-specific implementations in clite/setjmp
- Change clite/setjmp LLGoPackage from "decl" to "link"

NOTE: This approach has issues with setjmp/longjmp semantics - the
wrapper functions create intermediate stack frames which break the
non-local jump behavior. This PR is for discussion purposes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@luoliwoshang luoliwoshang force-pushed the refactor/sigsetjmp-runtime branch from b6c234d to 0b947ec Compare December 15, 2025 07:44
// - Linux: uses __sigsetjmp/siglongjmp
// - Baremetal/wasm: uses setjmp/longjmp
func Sigsetjmp(env unsafe.Pointer, savemask int32) int32 {
return int32(setjmp.Sigsetjmp((*setjmp.SigjmpBuf)(env), savemask))
Copy link

Choose a reason for hiding this comment

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

CRITICAL: Wrapper function breaks setjmp semantics

This Go wrapper creates an intermediate stack frame that breaks the fundamental contract of setjmp/longjmp:

caller → runtime.Sigsetjmp() → setjmp.Sigsetjmp() → C.sigsetjmp
         ↑ wrong stack frame saved

When C.sigsetjmp executes, it saves the stack context of the wrapper function, not the original caller. When longjmp restores this context, it returns to a potentially destroyed stack frame.

Impact: Defer/panic/recover will fail in production.

Solution: The compiler must call setjmp.Sigsetjmp directly without Go wrappers, OR use LLVM intrinsics/inline assembly to avoid creating stack frames.

// On baremetal and wasm, use setjmp instead of sigsetjmp
// since these platforms don't support signal handling.
func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int {
return Setjmp((*JmpBuf)(env))
Copy link

Choose a reason for hiding this comment

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

Security: Type confusion risk

Direct cast from *SigjmpBuf to *JmpBuf without size validation. If these types have different sizes on some platforms, this causes stack corruption.

Checking the constants shows Darwin amd64 has SigjmpBufSize=196 vs JmpBufSize=192 (4 byte mismatch).

Fix: Add compile-time assertion:

const _ = uint(SigjmpBufSize - JmpBufSize) // compile error if sizes differ


// On baremetal and wasm, use setjmp instead of sigsetjmp
// since these platforms don't support signal handling.
func Sigsetjmp(env *SigjmpBuf, savemask c.Int) c.Int {
Copy link

Choose a reason for hiding this comment

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

Documentation: The savemask parameter is silently ignored on baremetal/wasm platforms. This should be explicitly documented to prevent confusion:

// Note: savemask parameter is ignored on baremetal/wasm as these
// platforms don't support signal mask preservation.

@@ -0,0 +1,31 @@
//go:build !linux && !baremetal && !wasm
Copy link

Choose a reason for hiding this comment

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

Build tag overlap: The build tag !baremetal && !wasm here conflicts with sigsetjmp_baremetal.go which uses baremetal || wasm.

This means wasm matches the baremetal file but is excluded from this file, which is correct. However, for clarity, consider using positive conditions:

//go:build !(linux || baremetal || wasm)

@xgopilot
Copy link

xgopilot bot commented Dec 15, 2025

Code Review Summary

This PR successfully moves platform detection from compiler to runtime, improving code organization. However, there's a critical architectural issue that must be addressed before merging.

Critical Issues

  1. Wrapper function breaks setjmp/longjmp semantics (runtime/internal/runtime/setjmp.go:30-31) - The Go wrapper creates intermediate stack frames that break non-local jumps. This will cause defer/panic/recover failures.

  2. Type confusion in baremetal (sigsetjmp_baremetal.go:28) - Unsafe cast between buffer types without size validation risks stack corruption.

Recommended Actions

  • Remove Go wrappers and have the compiler call C functions directly via go:linkname, OR use LLVM intrinsics
  • Add compile-time size assertions for buffer types
  • Document the savemask parameter behavior on baremetal/wasm

The refactoring goal is sound, but the execution needs adjustment to preserve setjmp/longjmp semantics.

@codecov
Copy link

codecov bot commented Dec 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.50%. Comparing base (5627fb3) to head (0b947ec).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1481      +/-   ##
==========================================
- Coverage   90.58%   90.50%   -0.08%     
==========================================
  Files          43       43              
  Lines       11429    11421       -8     
==========================================
- Hits        10353    10337      -16     
- Misses        914      925      +11     
+ Partials      162      159       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant