Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions cuda-oxide/crates/cargo-oxide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ cargo oxide update --force # inside the workspace, run setup via update

| Flag | Applies to | Description |
|------------------------------|----------------------------------|-------------------------------------------------|
| `--link-ltoir <path>` | run, sanitize, build, test, pipeline, debug | Add an ordered external LTO-IR file to `--materialize-cubin` (repeatable) |
| `--materialize-cubin` | run, sanitize, build, test, pipeline, debug | Finalize and embed target-specific native GPU code during the host build |
| `--emit-nvvm-ir` | run, build, pipeline | Generate NVVM IR for libNVVM |
| `--arch <sm_XX>` | run, sanitize, build, test, pipeline, emit-ltoir, tilelib, inspect, debug | Target architecture override |
Expand Down Expand Up @@ -121,9 +122,32 @@ rechecks them inside the backend; use the wrapper flag instead of setting
`CUDA_OXIDE_MATERIALIZE_CUBIN` around raw Cargo. That variable is an internal
wrapper/backend signal, not a supported user interface: raw Cargo may reuse a
previous artifact without running the backend, and a backend invocation without
the wrapper's fingerprint is rejected. Generic `#[cuda_module]` kernels and
`#[device] extern` declarations are rejected because their run-time
multi-artifact linking is not yet represented by this single-cubin path.
the wrapper's fingerprint is rejected. Generic `#[cuda_module]` kernels remain
unsupported by this single-cubin path because they merge PTX bundles at run time.

For non-generic kernels calling `#[device] extern` functions, pass the definitions
as ordered, repeatable `--link-ltoir <path>` inputs alongside `--materialize-cubin`.
Inputs must be raw CUDA LTO-IR, not object files or fatbins. Each supplied file
is linked into every materialized device artifact in that Cargo invocation.
Keep these inputs separate from generated artifact paths; an overlap is rejected
before cleanup or device output emission.
Paths are relative to the invocation directory. The wrapper fingerprints their
canonical names and contents, and the backend verifies and owns those bytes
before linking. Changing an input rebuilds the device artifact; a change during
the wrapper/backend handoff is an error. The Rust module is linked first, followed
by the supplied files in command-line order. CUDA reports unresolved symbols,
invalid LTO-IR, and unsupported toolkit/target combinations during the build.
Use compatible CUDA compiler and linker versions when generating the inputs.

```bash
cargo oxide run --materialize-cubin --arch sm_120 \
--link-ltoir build/device-libs/scale.ltoir --link-ltoir build/device-libs/add.ltoir
```

The [`external_ltoir` example](../rustc-codegen-cuda/examples/external_ltoir/README.md)
includes C++ sources and commands to generate these files and run the ordinary
embedded-artifact loader. Generated binaries are not source dependencies.

Metadata-interop projects keep rejecting this global flag; they can instead
request one independently finalized cubin per declared device crate with
`artifact-kind = "cubin"`, as described below. Materialization is also a
Expand Down
17 changes: 15 additions & 2 deletions cuda-oxide/crates/cargo-oxide/src/commands/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,25 @@ pub(super) fn generated_artifact_paths(project_dir: &Path, package_name: &str) -

/// Remove stale generated artifacts (`.ptx`, `.ll`, `.ltoir`, `.cubin`) from a
/// previous run so we can verify the build produces fresh output.
pub(super) fn clean_generated_files(example_dir: &Path, example: &str) {
for file in generated_artifact_paths(example_dir, example) {
pub(super) fn clean_generated_files(
example_dir: &Path,
example: &str,
external_inputs: &[PathBuf],
) -> Result<(), String> {
let outputs = generated_artifact_paths(example_dir, example);
// Check the whole deletion set before removing even the first file.
for output in &outputs {
for input in external_inputs {
cuda_artifact_finalizer::check_ltoir_input_output(input, output)
.map_err(|error| error.to_string())?;
}
}
for file in outputs {
if file.exists() {
let _ = std::fs::remove_file(file);
}
}
Ok(())
}

/// Human-readable label for the selected output format.
Expand Down
15 changes: 12 additions & 3 deletions cuda-oxide/crates/cargo-oxide/src/commands/build_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ pub fn codegen_run(
std::process::exit(2);
}

clean_generated_files(&example_dir, example);
clean_generated_files(&example_dir, example, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});

println!("=========================================");
println!("RUSTC-CODEGEN-CUDA: {}", example);
Expand Down Expand Up @@ -256,7 +259,10 @@ pub fn codegen_sanitize(
return;
}

clean_generated_files(&example_dir, example);
clean_generated_files(&example_dir, example, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});

println!("=========================================");
println!("RUSTC-CODEGEN-CUDA SANITIZE: {}", example);
Expand Down Expand Up @@ -381,7 +387,10 @@ pub fn codegen_build(
std::process::exit(2);
}

clean_generated_files(&example_dir, example);
clean_generated_files(&example_dir, example, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});

println!("=========================================");
println!("RUSTC-CODEGEN-CUDA BUILD: {}", example);
Expand Down
6 changes: 6 additions & 0 deletions cuda-oxide/crates/cargo-oxide/src/commands/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub struct Context {
pub is_workspace: bool,
/// Project-local cuda-oxide defaults.
pub config: OxideConfig,
/// Ordered external LTO-IR inputs from the command line.
pub link_ltoir: Vec<PathBuf>,
}

/// Resolve the workspace root and backend, or exit with a helpful error.
Expand All @@ -60,6 +62,7 @@ pub fn resolve_context() -> Context {
let config = load_oxide_config(&workspace_root);
let backend_so = backend::find_or_build_backend(&workspace_root, config.backend.as_deref());
return Context {
link_ltoir: Vec::new(),
workspace_root,
codegen_crate,
examples_dir,
Expand All @@ -78,6 +81,7 @@ pub fn resolve_context() -> Context {
let config = load_oxide_config(&cwd);
let backend_so = backend::find_or_build_backend(&cwd, config.backend.as_deref());
return Context {
link_ltoir: Vec::new(),
workspace_root: cwd.clone(),
codegen_crate: cwd.clone(),
examples_dir: cwd.clone(),
Expand Down Expand Up @@ -110,6 +114,7 @@ pub fn resolve_passive_context() -> Context {
let config = load_oxide_config_lenient(&workspace_root);
let backend_so = backend::backend_so_candidate(&workspace_root, config.backend.as_deref());
return Context {
link_ltoir: Vec::new(),
workspace_root,
codegen_crate,
examples_dir,
Expand All @@ -128,6 +133,7 @@ pub fn resolve_passive_context() -> Context {
let config = load_oxide_config_lenient(&cwd);
let backend_so = backend::backend_so_candidate(&cwd, config.backend.as_deref());
return Context {
link_ltoir: Vec::new(),
workspace_root: cwd.clone(),
codegen_crate: cwd.clone(),
examples_dir: cwd.clone(),
Expand Down
5 changes: 5 additions & 0 deletions cuda-oxide/crates/cargo-oxide/src/commands/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub(super) fn passthrough_codegen_fingerprint_with_env(
// Descriptor identity only accelerates verification; artifact identity is
// already represented by the content-derived provenance above.
effective_env.remove(MATERIALIZER_HANDSHAKE_ENV);
effective_env.remove(EXTERNAL_LTOIR_ENV);

if opts.verbose {
effective_env.insert("CUDA_OXIDE_VERBOSE".to_string(), b"1".to_vec());
Expand All @@ -95,6 +96,10 @@ pub(super) fn passthrough_codegen_fingerprint_with_env(
effective_env.insert("CUDA_OXIDE_EMIT_NVVM_IR".to_string(), b"1".to_vec());
}
if let Some(prepared) = &materialization.prepared {
effective_env.insert(
EXTERNAL_LTOIR_ENV.to_string(),
prepared.external_ltoir_json.as_bytes().to_vec(),
);
effective_env.insert(MATERIALIZE_ENV.to_string(), b"1".to_vec());
effective_env.insert(
EXPECTED_PROVENANCE_ENV.to_string(),
Expand Down
5 changes: 4 additions & 1 deletion cuda-oxide/crates/cargo-oxide/src/commands/interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,10 @@ fn build_interop_device_crate(
}

let artifact_name = interop_device_artifact_name(&manifest_path, device_crate);
clean_generated_files(&artifact_dir, &artifact_name);
clean_generated_files(&artifact_dir, &artifact_name, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});
if let Some(target) = &binary_target {
touch_source_file(&target.source_path);
} else {
Expand Down
16 changes: 15 additions & 1 deletion cuda-oxide/crates/cargo-oxide/src/commands/materialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use std::process::Command;

use super::*;

pub(super) const EXTERNAL_LTOIR_ENV: &str = reserved_oxide_symbols::EXTERNAL_LTOIR_ENV;
pub(super) const MATERIALIZE_ENV: &str = reserved_oxide_symbols::MATERIALIZE_CUBIN_ENV;
pub(super) const EXPECTED_PROVENANCE_ENV: &str =
reserved_oxide_symbols::MATERIALIZER_PROVENANCE_ENV;
Expand All @@ -31,6 +32,7 @@ pub(super) struct MaterializationMode {
pub(super) struct PreparedMaterialization {
pub(super) provenance_sha256_hex: String,
pub(super) tool_identity_handshake_json: String,
pub(super) external_ltoir_json: String,
}

impl MaterializationMode {
Expand All @@ -39,10 +41,13 @@ impl MaterializationMode {
}

pub(super) fn apply(&self, cmd: &mut Command) {
// Never honor an inherited internal external-input list.
cmd.env_remove(EXTERNAL_LTOIR_ENV);
if let Some(prepared) = &self.prepared {
// These override inherited/project values: they are a single
// wrapper-generated handshake tied to this Cargo invocation.
cmd.env(MATERIALIZE_ENV, "1")
cmd.env(EXTERNAL_LTOIR_ENV, &prepared.external_ltoir_json)
.env(MATERIALIZE_ENV, "1")
.env(EXPECTED_PROVENANCE_ENV, &prepared.provenance_sha256_hex)
.env(
MATERIALIZER_HANDSHAKE_ENV,
Expand Down Expand Up @@ -205,13 +210,22 @@ fn prepare_materialization_result_with_env(
.parse()
.map_err(|error| format!("invalid materialization target {arch:?}: {error}"))?;

let external_inputs = ctx
.link_ltoir
.iter()
.map(|path| cuda_artifact_finalizer::LtoirFile::capture(path))
.collect::<Result<Vec<_>, _>>()
.map_err(|error| format!("could not capture external LTO-IR: {error}"))?;
let external_ltoir_json = serde_json::to_string(&external_inputs)
.map_err(|error| format!("could not encode external LTO-IR inputs: {error}"))?;
let handshake = discover_materializer_handshake(ctx)?;
let handshake_json = serde_json::to_string(&handshake)
.map_err(|error| format!("could not encode materializer handshake: {error}"))?;
Ok(MaterializationMode {
prepared: Some(PreparedMaterialization {
provenance_sha256_hex: digest_hex(&handshake.provenance_sha256),
tool_identity_handshake_json: handshake_json,
external_ltoir_json,
}),
})
}
Expand Down
10 changes: 8 additions & 2 deletions cuda-oxide/crates/cargo-oxide/src/commands/pipeline_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ pub fn codegen_show_pipeline(
reject_interop_output_mode(emit_nvvm_ir, &materialization);
}

clean_generated_files(&example_dir, example);
clean_generated_files(&example_dir, example, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});

println!("=========================================");
println!("RUSTC-CODEGEN-CUDA PIPELINE: {}", example);
Expand Down Expand Up @@ -196,7 +199,10 @@ pub fn codegen_debug(
println!("Detected GPU arch: {dev} (via nvidia-smi)");
}

clean_generated_files(&example_dir, example);
clean_generated_files(&example_dir, example, &ctx.link_ltoir).unwrap_or_else(|error| {
eprintln!("Error: {error}");
std::process::exit(2);
});

touch_main_rs(&example_dir);

Expand Down
Loading
Loading