diff --git a/cuda-oxide/Cargo.lock b/cuda-oxide/Cargo.lock index cdef05af07..3a4f770812 100644 --- a/cuda-oxide/Cargo.lock +++ b/cuda-oxide/Cargo.lock @@ -211,7 +211,7 @@ dependencies = [ "cuda-artifact-finalizer", "libnvvm-sys", "nvjitlink-sys", - "oxide-artifacts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oxide-artifacts 0.2.1", "oxide-tile-export", "oxide-tilebc", "ptx-parse", @@ -406,7 +406,7 @@ dependencies = [ "cuda-bindings", "cuda-core-derive", "half", - "oxide-artifacts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oxide-artifacts 0.2.1", ] [[package]] @@ -438,7 +438,7 @@ dependencies = [ "half", "libc", "libloading", - "oxide-artifacts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oxide-artifacts 0.2.1", "ptx-parse", "sha2", "thiserror 2.0.21", @@ -996,7 +996,7 @@ dependencies = [ "llvm-export", "mir-lower", "once_cell", - "oxide-artifacts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oxide-artifacts 0.2.1", "pliron", "pliron-derive", "reserved-oxide-symbols", @@ -1091,15 +1091,15 @@ checksum = "0b61fbf7ec37342ce171d9f5e3b82be74bda6c4e84e3b28338a03d553913e6aa" [[package]] name = "oxide-artifacts" version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9a941c0c43d2dceaf25f5c9ca4c9f9cceb28dfa5e4e3cfd14e476f003e7c9" dependencies = [ "object", ] [[package]] name = "oxide-artifacts" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d9a941c0c43d2dceaf25f5c9ca4c9f9cceb28dfa5e4e3cfd14e476f003e7c9" +version = "0.2.2" dependencies = [ "object", ] @@ -1223,7 +1223,7 @@ version = "0.1.0" dependencies = [ "clap", "dialect-ptx", - "oxide-artifacts 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oxide-artifacts 0.2.1", "ptx-parse", "serde", "serde_json", diff --git a/cuda-oxide/crates/oxide-artifacts/Cargo.toml b/cuda-oxide/crates/oxide-artifacts/Cargo.toml index 42e95b8424..e7dd00b07d 100644 --- a/cuda-oxide/crates/oxide-artifacts/Cargo.toml +++ b/cuda-oxide/crates/oxide-artifacts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxide-artifacts" -version = "0.2.1" +version = "0.2.2" edition.workspace = true description = "Architecture-neutral embedded device artifact metadata for cuda-oxide" readme = "README.md" diff --git a/cuda-oxide/crates/oxide-artifacts/src/lib.rs b/cuda-oxide/crates/oxide-artifacts/src/lib.rs index 99ef56fea2..e317617a31 100644 --- a/cuda-oxide/crates/oxide-artifacts/src/lib.rs +++ b/cuda-oxide/crates/oxide-artifacts/src/lib.rs @@ -755,6 +755,58 @@ pub fn build_host_object_for_target_with_legacy_anchor( pub fn build_host_anchor_object_for_target( target: &str, anchor_symbol: &str, +) -> Result, ArtifactError> { + build_host_anchor_object_for_target_with_strength(target, anchor_symbol, true) +} + +/// Build a host object that only defines a strong artifact link-anchor symbol. +/// +/// Unlike [`build_host_anchor_object_for_target`], the anchor is a strong +/// global definition, the same binding a full `.oxart` embed gives it. The +/// CUDA backend uses this for a selected crate whose `#[cuda_module]` only has +/// generic kernels that this crate does not monomorphize: the macro still +/// references the anchor to keep the rlib member linked (#72, #1365), but +/// there is no device bundle to embed. Artifact discovery sees no `.oxart` +/// section in the result. +#[cfg(feature = "object-write")] +pub fn build_host_strong_anchor_object_for_target( + target: &str, + anchor_symbol: &str, +) -> Result, ArtifactError> { + build_host_anchor_object_for_target_with_strength(target, anchor_symbol, false) +} + +/// Like [`build_host_strong_anchor_object_for_target`], plus a weak legacy +/// alias. +/// +/// The target-specific anchor is strong and the legacy package anchor is weak, +/// matching [`build_host_object_for_target_with_legacy_anchor`] for full +/// embeds, so older macro expansions that still reference the legacy name +/// link against the same placeholder. +#[cfg(feature = "object-write")] +pub fn build_host_strong_anchor_object_for_target_with_legacy_anchor( + target: &str, + anchor_symbol: &str, + legacy_anchor_symbol: &str, +) -> Result, ArtifactError> { + if anchor_symbol.is_empty() || legacy_anchor_symbol.is_empty() { + return Err(ArtifactError::Malformed( + "embedded artifact anchor symbol is empty".to_string(), + )); + } + build_host_object_with_section( + ARTIFACT_ANCHOR_SECTION_NAME, + &[0], + target, + &[(anchor_symbol, false), (legacy_anchor_symbol, true)], + ) +} + +#[cfg(feature = "object-write")] +fn build_host_anchor_object_for_target_with_strength( + target: &str, + anchor_symbol: &str, + weak: bool, ) -> Result, ArtifactError> { if anchor_symbol.is_empty() { return Err(ArtifactError::Malformed( @@ -766,7 +818,7 @@ pub fn build_host_anchor_object_for_target( ARTIFACT_ANCHOR_SECTION_NAME, &[0], target, - &[(anchor_symbol, true)], + &[(anchor_symbol, weak)], ) } @@ -1306,6 +1358,68 @@ mod tests { ); } + /// A strong anchor-only object binds the anchor like a full embed does + /// (#72, #1365) and still carries no artifact bundle. + #[cfg(all(feature = "object-read", feature = "object-write"))] + #[test] + fn strong_anchor_only_object_is_global_and_not_weak() { + use object::{Object, ObjectSymbol}; + + let bytes = build_host_strong_anchor_object_for_target( + "x86_64-unknown-linux-gnu", + "selected_empty_merge_anchor", + ) + .unwrap(); + let file = object::File::parse(bytes.as_slice()).unwrap(); + let anchor = file + .symbols() + .find(|symbol| symbol.name() == Ok("selected_empty_merge_anchor")) + .expect("strong anchor symbol missing"); + + assert!(anchor.is_definition()); + assert!(anchor.is_global()); + assert!(!anchor.is_weak()); + assert!( + read_artifact_bundles_from_object_bytes(&bytes) + .unwrap() + .is_empty() + ); + } + + #[cfg(all(feature = "object-read", feature = "object-write"))] + #[test] + fn strong_target_anchor_also_defines_weak_legacy_alias() { + use object::{Object, ObjectSymbol}; + + let bytes = build_host_strong_anchor_object_for_target_with_legacy_anchor( + "x86_64-unknown-linux-gnu", + "target_stub_anchor", + "legacy_stub_anchor", + ) + .unwrap(); + let file = object::File::parse(bytes.as_slice()).unwrap(); + let target = file + .symbols() + .find(|symbol| symbol.name() == Ok("target_stub_anchor")) + .expect("strong target anchor missing"); + let legacy = file + .symbols() + .find(|symbol| symbol.name() == Ok("legacy_stub_anchor")) + .expect("weak legacy alias missing"); + + assert!(target.is_definition()); + assert!(target.is_global()); + assert!(!target.is_weak()); + assert!(legacy.is_definition()); + assert!(legacy.is_global()); + assert!(legacy.is_weak()); + assert!( + read_artifact_bundles_from_object_bytes(&bytes) + .unwrap() + .is_empty() + ); + } + #[cfg(feature = "object-write")] #[test] fn anchor_only_object_rejects_empty_symbol() {