Skip to content
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

Use Ceescape's sigsetjmp() wrapper #1621

Merged
merged 1 commit into from
Mar 25, 2024

Conversation

NotGyro
Copy link
Contributor

@NotGyro NotGyro commented Mar 22, 2024

The cee-scape crate uses a hand-rolled assembly wrapper around libc's sigsetjmp(). This has better correctness guarantees than calling sigsetjmp() directly, because LLVM is not allowed to reason about / attempt to optimize inside hand-written assembly. sigsetjmp() is used for pg_guard_ffi_boundary_impl(), which is our wrapper around Postgres' exception handling, since Postgres uses sigsetjmp() and siglongjmp() to handle multiple return points. Rust's paradigm doesn't understand this - in Rust, a a function call can only return to its caller at one point in time: when it has finished executing. Whereas, sigsetjmp can return to two places chronologically - and so this needs to be handled carefully.

Fixes #1144

@eeeebbbbrrrr
Copy link
Contributor

eeeebbbbrrrr commented Mar 22, 2024

What I worry about here is cee-scape’s correctness.

I don’t know if our CI passing all tests is a robust enough exercise. I also don’t have helpful suggestions to really ensure correctness.

Don’t let this stand in the way of approval. Just wanted to voice a concern.

@NotGyro
Copy link
Contributor Author

NotGyro commented Mar 22, 2024

What I worry about here is cee-scape’s correctness.

I don’t know if our CI passing all tests is a robust enough exercise. I also don’t have a helpful suggestions to really sure correctness.

Don’t let this stand in the way of approval. Just wanted to voice a concern.

Yeah, that's reasonable. I have similar worries. Unfortunately, I'm not trained in the theory and practice of formal verification, so I can't guarantee this is correct.

Would it be possible to introduce tests that would more thoroughly check the validity of our sigsetjmp usage?

@workingjubilee
Copy link
Member

in practice all of our "try/catch" suite does that.

@eeeebbbbrrrr
Copy link
Contributor

in practice all of our "try/catch" suite does that.

Aren’t we also assuming this prevents possible compiler optimizations?

@eeeebbbbrrrr
Copy link
Contributor

On the flip side, I’m super jazzed this isn’t an invasive change! A++

@workingjubilee
Copy link
Member

in practice all of our "try/catch" suite does that.

Aren’t we also assuming this prevents possible compiler optimizations?

Yes? The fact that rustc cannot elide an asm block that control flow passes through, and must execute it, without reasoning about its contents except where annotated (pure and the like) is a documented property of the Rust abstract machine:

https://doc.rust-lang.org/reference/inline-assembly.html?highlight=black%20box#rules-for-inline-assembly

This is heavily relied on for allowing the Rust abstract machine to ignore machine-specific details in many cases and instead implementing those in assembly.

@workingjubilee
Copy link
Member

...looks like the macOS machines are using rustc 1.77 and the Linux ones are using rustc 1.76...

@eeeebbbbrrrr
Copy link
Contributor

eeeebbbbrrrr commented Mar 22, 2024

Okay. That closes the primary concern I had and it’s good to have that linked here.

Then, I guess the only concern is just “what if cee-scape is bugged?” But that’s not really any different than any other dependency we have. And the upstream folks seem receptive to patches.

ship it!

@workingjubilee
Copy link
Member

@NotGyro can you update the PR and/or commit message with that link and adjusted appropriately? i.e. that sigsetjmp is called via the assembly, and the assembly is opaque.

@workingjubilee
Copy link
Member

The tests should pass after rebase.

Comment on lines +115 to +126
let mut result: std::mem::MaybeUninit<T> = MaybeUninit::uninit();
let jump_value = cee_scape::call_with_sigsetjmp(false, |jump_buffer| {
// Make Postgres' error-handling system aware of our new
// setjmp/longjmp restore point.
pg_sys::PG_exception_stack = std::mem::transmute(jump_buffer as *const SigJmpBufFields);

// execute the closure, which will be a wrapped internal Postgres function
let result = f();
result.write(f());
0
});

// restore Postgres' understanding of where its next longjmp should go
if jump_value == 0 {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we'll need to tweak the scope this includes... maybe. This should be good for now, though.

Copy link
Member

@workingjubilee workingjubilee left a comment

Choose a reason for hiding this comment

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

Ultimately if needed, we can always fork/patch or even vendor cee-scape. Having this wired in means we can actually own whatever charming fuckups in control flow ensue, instead of being at the mercy of whether the compiler likes us that day or not.

@workingjubilee workingjubilee merged commit 61e94a9 into pgcentralfoundation:develop Mar 25, 2024
11 checks passed
@tucnak
Copy link

tucnak commented Oct 1, 2024

This seems to have broken builds on ppc64el which is an architecture supported by Postgres and Rust. Is there a mitigation?

Related #1897

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.

Use asm-based shim for sigsetjmp
4 participants