Skip to content

feat: artifact precompile pipeline (MVP)#5125

Draft
Mees-Molenaar wants to merge 41 commits into
wasmCloud:mainfrom
bettyblocks:feat/precompile-mvp
Draft

feat: artifact precompile pipeline (MVP)#5125
Mees-Molenaar wants to merge 41 commits into
wasmCloud:mainfrom
bettyblocks:feat/precompile-mvp

Conversation

@Mees-Molenaar

@Mees-Molenaar Mees-Molenaar commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Status: feedback request, not merge-ready. The MVP works end-to-end on kind, but some work is still open (see "Open work" below). However, before going on I would like a sanity check on the overall direction of this PR.

As discussed in #wasmcloud-precompile Slack thread.

Adds an opt-in precompile pipeline so a wasmCloud host can skip compilation at workload start and load a precompiled .cwasm produced by a separate worker. The pipeline is disabled unless the operator is started with --precompile-target and --precompile-wasmtime-version.

End-to-end flow

  1. A new PrecompileReconciler spawns a Kubernetes Job per Artifact
  2. The Job runs the wash-precompile binary. This binary pulls the OCI image, compiles it to .cwasm and writes it to a configured store (nats:// for production, file:// for dev/test).
  3. When the Job is finished, the operator records the results in Artifact.status.precompiled
  4. The WorkloadDeploymentReconciler waits for the precompilation to be complete and sends the PrecompiledURL via the workload proto to the wasmCloud Host.
  5. The host loads the .cwasm if it was passed as PrecompiledURL. Otherwise the host will use the existing pull + compile path.

Open work

  • Re-precompile on Artifact.spec.image change
  • Cleanup unused .cwasm
  • Persistent storage NATS
  • NATS using TLS
  • Wasmtime config matching between host and precompiler

Looking forward to your thoughts.
Mees

# -- Wasmtime version the worker links against. Must match the host's
# wasmtime version exactly — cwasm bytes are version-specific.
# No default; required when precompile.enabled is true.
wasmtimeVersion: ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why not infer this from the installed wasmtime version?

#[arg(long)]
image: String,

/// URL where the precompiled .cwasm bytes will be written

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// URL where the precompiled .cwasm bytes will be written
/// URL where the precompiled .cwasm bytes will be written
///
/// Supported schemes:
/// - file:///path/to/component.cwasm
/// - nats://<bucket>/<key> (requires NATS_URL env var)

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let output_url = Url::parse(&args.output)?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let output_url = Url::parse(&args.output)?;
let output_url = Url::parse(&args.output)
.with_context(|| format!("invalid output URL '{}'", args.output))?;

better to have a descriptive error

```

For `nats://`, set `NATS_URL`. For pulling from a private registry, set `DOCKER_CONFIG` pointing at a directory containing a `config.json` (the same format `kubectl create secret docker-registry` produces).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we add a short section clarifying the semantics of nats:// outputs?

Right now it’s not obvious that:

  • NATS_URL configures the NATS server connection
  • nats://<bucket>/<key> identifies the JetStream object store destination

An example mapping of bucket/key from the URL would probably help too, since the scheme is behaving more like an object-store locator (s3://bucket/key I think) than a direct connection URL.

operator:
precompile:
enabled: true
wasmtimeVersion: "43"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

probably should match the host's version 44

let mut config = wasmtime::Config::new();
config.wasm_component_model(true);
let engine = wasmtime::Engine::new(&config)?;
let wasm = wat::parse_str("(component)")?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we'd prefer using an actual component like the http-hello-world just so we can validate that the rest of the flow executes too.

}
}

#[allow(unsafe_code)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know the deserialize has been pointed out in the slack threads, so it's more of a call back.
perhaps an upstream modification to wasmtime? Signing/Attestation or a checksum of sorts

.map_err(|e| anyhow::anyhow!("Error setting up wasmtime engine: {e}"))?;
let cwasm = engine
.precompile_component(wasm_bytes)
.map_err(|e| anyhow::anyhow!("Error precompiling wasm component: {e}"))?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As a compatibility step I think It makes sense to store some additional metadata like a hash https://docs.wasmtime.dev/api/wasmtime/struct.Engine.html#method.precompile_compatibility_hash

url = %component.precompiled_url,
"using precompiled component"
);
let bytes = match fetch(&component.precompiled_url).await {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since cwasm bytes are only valid for compatible engine configs, this fetch path should probably return both the bytes and attached metadata (at least a stored precompile_compatibility_hash). Then, before setting is_precompiled: true, we can compare that metadata against the current host engine and probably fall back to pulling/compiling the original wasm when it does not match

See comment in precompiler.rs for the precompile hash context.
ref: https://docs.wasmtime.dev/api/wasmtime/struct.Engine.html#method.precompile_compatibility_hash

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.

3 participants