Skip to content

perf(docker): enable -Zshare-generics in the image build - #845

Merged
jlucaso1 merged 7 commits into
mainfrom
perf/dockerfile-share-generics
Jun 11, 2026
Merged

perf(docker): enable -Zshare-generics in the image build#845
jlucaso1 merged 7 commits into
mainfrom
perf/dockerfile-share-generics

Conversation

@jlucaso1

Copy link
Copy Markdown
Collaborator

What

Adds -Zshare-generics=y to the Docker image's RUSTFLAGS.

Why

The cargo-bloat audit showed that rustc instantiates generics and async state machines in every crate that uses them; the per-crate symbol copies carry distinct instantiating-crate hashes that even fat LTO cannot merge. #842/#843 removed the biggest sources at the library level, but the long tail (moka cache machinery, async send graphs reached from handler closures, drop glue) is inherent to how stable rustc codegens cross-crate. share-generics is the upstream switch for exactly this: downstream crates reuse upstream monomorphizations instead of re-codegening them.

Measured (release bin, current main)

  • .text: 11.65 MiB -> 11.00 MiB (-666 KiB, -5.6%)
  • cross-crate duplicate-symbol waste: 1414 KiB -> 475 KiB
  • consumer-crate reinstantiation: 1484 KiB -> 531 KiB

Cumulative with #842/#843/#844 the audit took .text from 13.03 MiB to 11.00 MiB (-15.6%).

Safety

  • Nightly-only flag, and this image already pins the nightly toolchain through rust-toolchain.toml. The stable CI lane and stable consumers are untouched: the flag lives only in the Dockerfile.
  • The historical reason share-generics is off in release builds is lost cross-crate inlining of shared instantiations; with lto = "fat" that does not apply, since LTO sees all bitcode and re-inlines freely.
  • CodSpeed cannot exercise a Dockerfile-only flag, so the functional validation is local: the full wacore + lib suites built and run with the flag pass (1838 tests).

Note for downstream images (e.g. Veloz): the same one-line flag applies to any consumer building on the pinned nightly.

rustc instantiates generics and async state machines in every crate that uses them, and the per-crate symbols defeat LTO merging; share-generics makes downstream crates reuse upstream monomorphizations instead. Measured on the release bin: .text 11.65 MiB to 11.00 MiB (-666 KiB, -5.6%), cross-crate duplicate-symbol waste 1414 KiB to 475 KiB, consumer-crate reinstantiation 1484 KiB to 531 KiB. Full wacore + lib suites pass built with the flag (1838 tests).

Nightly-only, which the image already pins through rust-toolchain.toml; the stable CI lane is untouched since the flag lives only here. With fat LTO the historical cross-crate inlining downside does not apply (LTO sees all bitcode), and CodSpeed cannot exercise a Dockerfile-only flag, so the test run above is the functional validation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c2fcd6016

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Dockerfile
The cook already ran on the pinned nightly (the chef stage's copy sits at / and rustup walks parent directories), but with nightly-only RUSTFLAGS in play that subtlety becomes load-bearing; an explicit copy in /app keeps the build independent of the base image's default WORKDIR.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Dockerfile updates: chef stage now installs rust-src and a locked cargo-chef; builder sets nightly RUSTFLAGS and CARGO_UNSTABLE_BUILD_STD, copies rust-toolchain.toml before cook, computes an explicit host target triple for cargo chef cook and cargo build, and adjusts the runtime COPY to use the staged binary at /app/whatsapp-rust-bin.

Changes

Docker build updates

Layer / File(s) Summary
Chef stage: rust-src and pinned cargo-chef
Dockerfile
Adds rust-src and installs cargo-chef pinned to 0.1.77 with --locked.
Builder: nightly flags and unstable build-std
Dockerfile
Sets RUSTFLAGS to include -Zshare-generics=y plus -C target-cpu=native, and sets CARGO_UNSTABLE_BUILD_STD="std,panic_abort".
Builder: copy rust-toolchain.toml before cook
Dockerfile
Copies rust-toolchain.toml into the build context so the toolchain override is available before dependency cook.
Builder: compute host target and cook dependencies
Dockerfile
Writes host triple from rustc -vV to /rust-target and runs cargo chef cook --target $(cat /rust-target) for dependency caching.
Build and runtime binary handoff
Dockerfile
Runs cargo build --target $(cat /rust-target) --release, copies binary from target/<host-triple>/release/whatsapp-rust to /app/whatsapp-rust-bin, and runtime stage copies from that path.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: enabling the -Zshare-generics flag in the Docker image build, which is the primary performance optimization introduced in this PR.
Description check ✅ Passed The description comprehensively explains the what, why, measured results, and safety considerations of the changes, directly addressing the PR's objectives and technical rationale.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/dockerfile-share-generics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq

codspeed-hq Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 140 untouched benchmarks


Comparing perf/dockerfile-share-generics (881b4b1) with main (0aa6cb9)

Open in CodSpeed

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 27s —— View job


Claude PR Review

  • Read the Dockerfile diff
  • Check the toolchain copy ordering (Codex P1 concern)
  • Verify nightly is actually used by cargo chef cook
  • Sanity-check the RUSTFLAGS value & scope
  • Post the final review

Summary

LGTM. The change is small, well-targeted, and the Codex P1 was addressed correctly by the follow-up commit f34d9d15. The justification in the PR body holds up against the repo state.

Codex P1 — resolved

Codex correctly pointed out that on the original commit, cargo chef cook ran with the nightly-only -Zshare-generics=y while only the chef stage had copied rust-toolchain.toml (into /, not /app). The follow-up commit adds COPY rust-toolchain.toml . to the builder stage at Dockerfile:38, before the cook on line 40. That makes the override unambiguously live in /app/rust-toolchain.toml at cook time.

Worth noting: rustup walks up from cwd, so the override at /rust-toolchain.toml inherited from the chef stage would also have been found from /app. But the explicit copy is the right call — it makes the intent obvious and avoids relying on parent-dir traversal, which is brittle if WORKDIR or the chef-stage layout ever change.

Correctness checks

  • -Zshare-generics=y is gated to nightly. rust-toolchain.toml pins nightly-2026-04-05, and the flag lives only in the Dockerfile — stable CI and downstream stable consumers are not touched. ✅
  • The PR body's claim that lost cross-crate inlining of shared instantiations doesn't apply here is validated by Cargo.toml: [profile.release] sets lto = "fat" and codegen-units = 1, so the linker re-inlines from full bitcode. ✅
  • RUSTFLAGS is an ENV, so it's inherited by every rustc invocation in the builder stage including build-scripts compiled with nightly. No conflict with -C target-cpu=native. ✅

Minor observations (not blocking)

  • Layer-cache invalidation: the new COPY rust-toolchain.toml . in the builder stage means a toolchain bump will invalidate the cook layer. That is the correct behavior (a toolchain change should re-cook deps), just worth being aware of.
  • cargo install cargo-chef on nightly: pre-existing — the chef stage installs cargo-chef under nightly because the toolchain override is already in / at that point. Not introduced by this PR; just noting.
  • Static-binary target: the Dockerfile produces an alpine/musl static binary. -C target-cpu=native is fine for a one-off image build, but means the resulting image is non-portable across CPUs. Pre-existing concern unrelated to this PR.

Verdict

Approving the approach. The Codex P1 is resolved by the follow-up commit, the flag's safety story checks out against Cargo.toml and rust-toolchain.toml, and the diff is correctly scoped to the Docker image only.


• branch: perf/dockerfile-share-generics

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile`:
- Line 33: The Dockerfile currently sets RUSTFLAGS to include "-C
target-cpu=native -Zshare-generics=y", which bakes the build host CPU features
into the binary; remove the "-C target-cpu=native" token from the ENV RUSTFLAGS
assignment (keep "-Zshare-generics=y") or replace it with a portable baseline
CPU flag (e.g., a conservatively chosen target) so the builder-stage ENV
RUSTFLAGS line no longer forces native CPU instructions and the produced
artifact is portable to older production CPUs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d0d08a56-9aa9-4843-99d6-6a6930dfcfc5

📥 Commits

Reviewing files that changed from the base of the PR and between 0aa6cb9 and f34d9d1.

📒 Files selected for processing (1)
  • Dockerfile

Comment thread Dockerfile
Pins the tool's own dependency graph to its published lockfile so an image rebuild cannot break on a freshly released or yanked transitive dep.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile`:
- Line 18: The RUN invocation installing cargo-chef should pin the cargo-chef
release to ensure deterministic rebuilds; update the RUN line that currently
says `cargo install cargo-chef --locked` to include an explicit `--version
<exact-version>` (e.g., `--version 0.x.y`) alongside `--locked` so the install
uses a fixed cargo-chef release rather than the latest.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e61ef7d0-9aa7-48b2-b4ba-353887fd43a5

📥 Commits

Reviewing files that changed from the base of the PR and between f34d9d1 and 7f7085f.

📒 Files selected for processing (1)
  • Dockerfile

Comment thread Dockerfile Outdated
The prebuilt rustup std does not participate in fat LTO, so its code ships as-is. Building it from rust-src with the image's release profile lets LTO inline and dead-code-eliminate it like any other crate: another -303 KiB of .text on top of share-generics, with the full wacore + lib suites passing built this way (1838 tests).

The env form (CARGO_UNSTABLE_BUILD_STD) reaches both the chef cook and the final build so the dependency cache layer stays valid; build-std requires an explicit target, hence the musl triple on both invocations and the adjusted binary path in the runtime stage.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread Dockerfile Outdated
--locked freezes the tool's dependency graph but not which release gets fetched; the explicit --version makes image rebuilds deterministic.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
Dockerfile (2)

18-19: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Pin cargo-chef to an exact version for deterministic builds.

Look, --locked only freezes cargo-chef's own dependency graph—it doesn't stop cargo install from grabbing whatever the latest cargo-chef release happens to be. For a production image we ship to billions of users, we need reproducibility. Add --version <exact> so CI builds are byte-for-byte consistent.

-RUN rustup show && rustup component add rust-src && cargo install cargo-chef --locked
+RUN rustup show && rustup component add rust-src && cargo install cargo-chef --locked --version 0.1.71

(Replace 0.1.71 with the current stable cargo-chef release.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 18 - 19, Pin the cargo-chef installation to an exact
release so builds are deterministic: update the RUN that installs cargo-chef
(the line calling "cargo install cargo-chef --locked") to include "--version
0.1.71" (or the current stable release) in addition to "--locked" so
cargo-install cannot pull a newer release; leave the rustup component add
rust-src call unchanged.

34-34: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Drop target-cpu=native—this is a portability time bomb.

This is critical. We're baking whatever CPU features my build machine has into the binary, then shipping it in a scratch image that could run anywhere. On a production server with an older CPU, users get SIGILL and a dead container. That's not the kind of user experience I'm trying to deliver here.

Keep -Zshare-generics=y for the codegen wins, but remove target-cpu=native or replace it with a safe baseline like x86-64-v2 if you need some SIMD.

-ENV RUSTFLAGS="-C target-cpu=native -Zshare-generics=y"
+ENV RUSTFLAGS="-Zshare-generics=y"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` at line 34, The Dockerfile sets ENV RUSTFLAGS="-C
target-cpu=native -Zshare-generics=y", which bakes host CPU features into the
binary and can cause SIGILL on older machines; remove the "-C target-cpu=native"
token (or replace it with a portable baseline like "-C target-cpu=x86-64-v2" if
you need SIMD) and keep "-Zshare-generics=y" for codegen benefits, i.e., edit
the ENV RUSTFLAGS line to omit target-cpu=native and ensure only the desired,
portable flags remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@Dockerfile`:
- Around line 18-19: Pin the cargo-chef installation to an exact release so
builds are deterministic: update the RUN that installs cargo-chef (the line
calling "cargo install cargo-chef --locked") to include "--version 0.1.71" (or
the current stable release) in addition to "--locked" so cargo-install cannot
pull a newer release; leave the rustup component add rust-src call unchanged.
- Line 34: The Dockerfile sets ENV RUSTFLAGS="-C target-cpu=native
-Zshare-generics=y", which bakes host CPU features into the binary and can cause
SIGILL on older machines; remove the "-C target-cpu=native" token (or replace it
with a portable baseline like "-C target-cpu=x86-64-v2" if you need SIMD) and
keep "-Zshare-generics=y" for codegen benefits, i.e., edit the ENV RUSTFLAGS
line to omit target-cpu=native and ensure only the desired, portable flags
remain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b43ffbc9-b2c9-491c-8f9b-84b77507d495

📥 Commits

Reviewing files that changed from the base of the PR and between 7f7085f and 12121ed.

📒 Files selected for processing (1)
  • Dockerfile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14897712ce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Dockerfile Outdated
build-std needs an explicit --target, but hard-coding x86_64 broke multi-arch image builds that previously used the base image's native target. Reading the triple from rustc keeps native semantics on every platform; the binary is copied to a fixed path so the scratch stage stays arch-agnostic.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
Dockerfile (1)

36-36: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t ship host-CPU-specific instructions in a portable scratch image.

Line 36 still bakes build-host CPU features into the release binary. That can crash on less-capable production CPUs (Illegal instruction). Keep -Zshare-generics=y, drop target-cpu=native or replace it with an explicit portable baseline.

Suggested minimal fix
-ENV RUSTFLAGS="-C target-cpu=native -Zshare-generics=y"
+ENV RUSTFLAGS="-Zshare-generics=y"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` at line 36, The Dockerfile currently sets ENV RUSTFLAGS including
"-C target-cpu=native" which bakes host-specific CPU instructions into the
release binary; remove the "-C target-cpu=native" token (or replace it with an
explicit portable baseline like a generic x86_64 target flag) and keep the
"-Zshare-generics=y" option so portability is preserved; update the ENV
RUSTFLAGS assignment (the single line containing ENV RUSTFLAGS) to exclude the
native target and verify the resulting build artifacts run on older CPUs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile`:
- Line 53: The RUN line that extracts the Rust host triple (currently using
`rustc -vV | sed -n 's/^host: //p' > /rust-target`) should be made fail-fast:
capture the output into an intermediate file (e.g., `/tmp/rust-host`) and then
verify it is non-empty before moving to `/rust-target`; if empty, exit with a
non-zero status and print a helpful error. Update the Dockerfile to run `rustc
-vV` into the temp file, strip the `host:` field with `sed` into the same temp,
assert the file has content (or `test -s` / `[[ -s ]]`) and only then `mv` to
`/rust-target`, so any failure is detected immediately during image build.

---

Duplicate comments:
In `@Dockerfile`:
- Line 36: The Dockerfile currently sets ENV RUSTFLAGS including "-C
target-cpu=native" which bakes host-specific CPU instructions into the release
binary; remove the "-C target-cpu=native" token (or replace it with an explicit
portable baseline like a generic x86_64 target flag) and keep the
"-Zshare-generics=y" option so portability is preserved; update the ENV
RUSTFLAGS assignment (the single line containing ENV RUSTFLAGS) to exclude the
native target and verify the resulting build artifacts run on older CPUs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 04341d49-921a-47e8-8b3e-bfd0cee415dc

📥 Commits

Reviewing files that changed from the base of the PR and between 1489771 and ac669a0.

📒 Files selected for processing (1)
  • Dockerfile

Comment thread Dockerfile Outdated
Docker's default shell has no pipefail, so a failing rustc would be masked by the pipe and surface later as a cryptic empty --target; test -s trips at the right layer.
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