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

cfi: Simpler launder implementation for common types #1714

Merged
merged 3 commits into from
Nov 6, 2024
Merged

Conversation

swenson
Copy link
Contributor

@swenson swenson commented Oct 10, 2024

cfi_launder is meant to prevent the Rust compiler from optimizing a value away.

Our current implementation uses core::hint::black_box(), which is the recommended way in Rust. The problem is, this appears to often force the argument to spill into memory and to be reloaded, which can be a lot of extra instructions.

The original inspiration for this function is from, I believe, OpenTitan's launder* functions. There, they use an LLVM-specific trick of a blank inline assembly block to force the compiler to keep the argument in a register.

After reviewing our code and speaking with @vsonims, it sounds like the intention of the launder in our code is to prevent the compiler from optimizing the value away (as the comments suggest), so the simpler inline assembly trick may be sufficient (since we use the official Rust compiler, which uses LLVM).

The biggest problem is that we launder many types of values in our code and not all of them fit into a register.

So, this PR represents an incremental change: for u32s and similar small types, we implement cfi_launder using the inline assembly trick from OpenTitan. For any other types, we have a trait that can be derived that will call core::hint::black_box in the same way as today.

We can do future follow-up PRs to try to try to clean up some of those other uses of cfi_launder to hopefully shrink the code more.

I also slipped in avoid a few extra copies in the verifier by using references instead of copies (this saves ~80 bytes of instruction space).

This PR appears to shrink the ROM code size by 1232 bytes and the runtime firmware by 700 bytes.

@swenson swenson force-pushed the cfi-launder branch 2 times, most recently from 1921e07 to 05a5902 Compare October 14, 2024 16:15
jhand2
jhand2 previously approved these changes Oct 14, 2024
@swenson
Copy link
Contributor Author

swenson commented Oct 14, 2024

(I've also looked at removing Copy traits from a few other types and trying to use more references so that the laundering could be more effective without copying, but I was surprised to find that this increased the code size.

There are more likely a few more types though that would be worth streamlining so we can save more code space, but I leave that to a future PR.)

jhand2
jhand2 previously approved these changes Oct 14, 2024
rusty1968
rusty1968 previously approved these changes Oct 24, 2024
nquarton
nquarton previously approved these changes Nov 4, 2024
@swenson
Copy link
Contributor Author

swenson commented Nov 4, 2024

@jhand2, @nquarton, @rusty1968 -- approvals were wiped when I rebased and updated the SHAs. No other changes were made. Please re-review and I'll get someone to merge this ASAP for the 1.2 ROM release.

… value away.

Our current implementation uses `core::hint::black_box()`, which is the recommended way in Rust. The problem is, this appears to often force the argument to spill into memory and to be reloaded, which can be a lot of extra instructions.

The original inspiration for this function is from, I believe, [OpenTitan's launder* functions](https://github.com/lowRISC/opentitan/blob/master/sw/device/lib/base/hardened.h#L193). There, they use an LLVM-specific trick of a blank inline assembly block to force the compiler to keep the argument in a register.

After reviewing our code and speaking with @vsonims, it sounds like the intention of the launder in our code is to prevent the compiler from optimizing the value away (as the comments suggest), so the simpler inline assembly trick may be sufficient (since we use the official Rust compiler, which uses LLVM).

The biggest problem is that we launder many types of values in our code and not all of them fit into a register.

So, this PR represents an incremental change: for `u32`s and similar small types, we implement `cfi_launder` using the inline assembly trick from OpenTitan. For any other types, we have a trait that can be derived that will call `core::hint::black_box` in the same way as today.

We can do future follow-up PRs to try to try to clean up some of those other uses of `cfi_launder` to hopefully shrink the code more.

I also slipped in avoid a few extra copies in the verifier by using references instead of copies (this saves ~80 bytes of instruction space).

This PR appears to shrink the ROM code size by 1232 bytes and the runtime firmware by 700 bytes.
nquarton
nquarton previously approved these changes Nov 4, 2024
mhatrevi
mhatrevi previously approved these changes Nov 6, 2024
@swenson swenson dismissed stale reviews from mhatrevi and nquarton via 8356cf6 November 6, 2024 17:32
@mhatrevi mhatrevi enabled auto-merge (squash) November 6, 2024 17:46
@swenson
Copy link
Contributor Author

swenson commented Nov 6, 2024

Thanks!

@mhatrevi mhatrevi merged commit 571d253 into main Nov 6, 2024
11 checks passed
@swenson swenson deleted the cfi-launder branch November 6, 2024 18:03
mhatrevi pushed a commit that referenced this pull request Nov 18, 2024
* `cfi_launder` is meant to prevent the Rust compiler from optimizing a value away.

Our current implementation uses `core::hint::black_box()`, which is the recommended way in Rust. The problem is, this appears to often force the argument to spill into memory and to be reloaded, which can be a lot of extra instructions.

The original inspiration for this function is from, I believe, [OpenTitan's launder* functions](https://github.com/lowRISC/opentitan/blob/master/sw/device/lib/base/hardened.h#L193). There, they use an LLVM-specific trick of a blank inline assembly block to force the compiler to keep the argument in a register.

After reviewing our code and speaking with @vsonims, it sounds like the intention of the launder in our code is to prevent the compiler from optimizing the value away (as the comments suggest), so the simpler inline assembly trick may be sufficient (since we use the official Rust compiler, which uses LLVM).

The biggest problem is that we launder many types of values in our code and not all of them fit into a register.

So, this PR represents an incremental change: for `u32`s and similar small types, we implement `cfi_launder` using the inline assembly trick from OpenTitan. For any other types, we have a trait that can be derived that will call `core::hint::black_box` in the same way as today.

We can do future follow-up PRs to try to try to clean up some of those other uses of `cfi_launder` to hopefully shrink the code more.

I also slipped in avoid a few extra copies in the verifier by using references instead of copies (this saves ~80 bytes of instruction space).

This PR appears to shrink the ROM code size by 1232 bytes and the runtime firmware by 700 bytes.

(cherry picked from commit 571d253)
mhatrevi pushed a commit that referenced this pull request Nov 19, 2024
* `cfi_launder` is meant to prevent the Rust compiler from optimizing a value away.

Our current implementation uses `core::hint::black_box()`, which is the recommended way in Rust. The problem is, this appears to often force the argument to spill into memory and to be reloaded, which can be a lot of extra instructions.

The original inspiration for this function is from, I believe, [OpenTitan's launder* functions](https://github.com/lowRISC/opentitan/blob/master/sw/device/lib/base/hardened.h#L193). There, they use an LLVM-specific trick of a blank inline assembly block to force the compiler to keep the argument in a register.

After reviewing our code and speaking with @vsonims, it sounds like the intention of the launder in our code is to prevent the compiler from optimizing the value away (as the comments suggest), so the simpler inline assembly trick may be sufficient (since we use the official Rust compiler, which uses LLVM).

The biggest problem is that we launder many types of values in our code and not all of them fit into a register.

So, this PR represents an incremental change: for `u32`s and similar small types, we implement `cfi_launder` using the inline assembly trick from OpenTitan. For any other types, we have a trait that can be derived that will call `core::hint::black_box` in the same way as today.

We can do future follow-up PRs to try to try to clean up some of those other uses of `cfi_launder` to hopefully shrink the code more.

I also slipped in avoid a few extra copies in the verifier by using references instead of copies (this saves ~80 bytes of instruction space).

This PR appears to shrink the ROM code size by 1232 bytes and the runtime firmware by 700 bytes.

(cherry picked from commit 571d253)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants