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
45 changes: 45 additions & 0 deletions .github/workflows/examples-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,51 @@ jobs:
"${RUNNER_TEMP}/cross-crate-sm90-build.log" \
'^(kernel-lib|cross_crate_kernel)$' kernel-lib cross_crate_kernel

# With LTO, rustc reads bitcode from the Rust objects of each rlib and
# removes these objects from the link. The artifact objects of the
# libraries must not be Rust objects.
- name: LTO keeps the PTX bundles of libraries
timeout-minutes: 15
working-directory: cuda-oxide
env:
CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }}
CUDA_OXIDE_LLC: /usr/bin/llc-22
CARGO_TARGET_DIR: ${{ runner.temp }}/examples-shared-target
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_RELEASE_LTO: fat
run: |
set -euo pipefail
sysroot="$(rustc --print sysroot)"
export LIBRARY_PATH="${sysroot}/lib:${LIBRARY_PATH:-}"
export LD_LIBRARY_PATH="${CUDA_TOOLKIT_PATH}/lib64:${sysroot}/lib:${LD_LIBRARY_PATH:-}"

assert_bundles() {
local file="$1"
shift
local section="${RUNNER_TEMP}/$(basename "${file}").oxart"
objcopy --dump-section ".oxart=${section}" "${file}"
local bundle
for bundle in "$@"; do
grep -aFq "${bundle}" "${section}"
done
}
binary="${CARGO_TARGET_DIR}/release/cuda_module_in_lib"

cargo oxide build cuda_module_in_lib --arch sm_86 2>&1 \
| tee "${RUNNER_TEMP}/cross-crate-lto-build.log"
assert_bundles "${binary}" cuda_module_in_lib module-kernels

# If a session writes an rlib and a dynamic library, the system
# linker gets the artifact objects as link arguments.
manifest=crates/rustc-codegen-cuda/examples/cuda_module_in_lib/kernel-lib/Cargo.toml
printf '\n[lib]\ncrate-type = ["rlib", "cdylib"]\n' >> "${manifest}"
cargo oxide build cuda_module_in_lib --arch sm_86 2>&1 \
| tee "${RUNNER_TEMP}/cross-crate-lto-cdylib-build.log"
git checkout -- "${manifest}"
assert_bundles "${binary}" cuda_module_in_lib module-kernels
assert_bundles \
"$(find "${CARGO_TARGET_DIR}/release" -name libmodule_kernels.so)" module-kernels

- name: Upload failure logs
if: failure()
uses: actions/upload-artifact@v4
Expand Down
170 changes: 158 additions & 12 deletions cuda-oxide/crates/rustc-codegen-cuda/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ extern crate rustc_metadata;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_structures;
extern crate rustc_target;

// rustc_public (stable MIR) and its bridge - for calling mir-importer
Expand All @@ -331,13 +332,15 @@ mod generated_intrinsics;
mod materialize;

use rustc_codegen_ssa::traits::CodegenBackend;
use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleKind};
use rustc_codegen_ssa::{CompiledModule, CompiledModules, CrateInfo, ModuleKind, NativeLib};
use rustc_metadata::EncodedMetadata;
use rustc_middle::dep_graph::WorkProductMap;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::config::OutputFilenames;
use rustc_session::{CodegenBackendInit, EarlySession, IncrCompSession, Session};
use rustc_span::Symbol;
use rustc_structures::{CrateType, NativeLibKind};
use std::any::Any;
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -822,16 +825,12 @@ impl CodegenBackend for CudaCodegenBackend {
crate_info,
);
for (index, object) in ongoing.artifact_objects.into_iter().enumerate() {
compiled_modules.modules.push(CompiledModule {
name: format!("oxide_artifact_embed_{index}"),
kind: ModuleKind::Regular,
object: Some(object),
dwarf_object: None,
bytecode: None,
assembly: None,
llvm_ir: None,
global_asm_object: None,
});
compiled_modules.modules.push(artifact_module(
index,
object,
&crate_info.crate_types,
sess.opts.unstable_opts.link_native_libraries,
));
}
(compiled_modules, work_products)
}
Expand All @@ -840,15 +839,91 @@ impl CodegenBackend for CudaCodegenBackend {
&self,
sess: &Session,
compiled_modules: CompiledModules,
crate_info: CrateInfo,
mut crate_info: CrateInfo,
metadata: EncodedMetadata,
outputs: &OutputFilenames,
) {
for object in artifact_link_args(&compiled_modules.modules, &crate_info.crate_types) {
let Some(object) = object.to_str() else {
sess.dcx().fatal(format!(
"[rustc_codegen_cuda] The path of the artifact object is not UTF-8: {}",
object.display()
));
};
crate_info.used_libraries.push(NativeLib {
kind: NativeLibKind::LinkArg,
name: Symbol::intern(object),
cfg: None,
verbatim: true,
dll_imports: Vec::new(),
});
}
self.llvm_backend
.link(sess, compiled_modules, crate_info, metadata, outputs);
}
}

/// The prefix of the names of the artifact modules.
const ARTIFACT_MODULE_PREFIX: &str = "oxide_artifact_embed_";

/// The module of an artifact object.
///
/// rustc keeps a list of the Rust objects in each rlib. This list contains the
/// objects of all modules. With LTO, rustc reads bitcode from these objects and
/// removes them from the link. The artifact objects have no bitcode. If the
/// session writes an rlib, the artifact object is the DWARF object of its
/// module. rustc puts the DWARF objects in the rlib, but not in the list.
///
/// The system linker does not get the DWARF objects. `link` gives the artifact
/// objects to this linker as link arguments. With `-Zlink-native-libraries=no`,
/// rustc does not give link arguments to the linker. Then, if the session also
/// uses the system linker, the artifact objects are Rust objects.
fn artifact_module(
index: usize,
object: PathBuf,
crate_types: &[CrateType],
link_native_libraries: bool,
) -> CompiledModule {
let dwarf = crate_types.contains(&CrateType::Rlib)
&& (link_native_libraries || !crate_types.iter().any(uses_system_linker));
let (object, dwarf_object) = if dwarf {
(None, Some(object))
} else {
(Some(object), None)
};
CompiledModule {
name: format!("{ARTIFACT_MODULE_PREFIX}{index}"),
kind: ModuleKind::Regular,
object,
dwarf_object,
bytecode: None,
assembly: None,
llvm_ir: None,
global_asm_object: None,
}
}

/// The artifact objects that `link` gives to the system linker.
fn artifact_link_args<'a>(
modules: &'a [CompiledModule],
crate_types: &[CrateType],
) -> Vec<&'a Path> {
if !crate_types.iter().any(uses_system_linker) {
return Vec::new();
}
modules
.iter()
.filter(|module| module.name.starts_with(ARTIFACT_MODULE_PREFIX))
.filter_map(|module| module.dwarf_object.as_deref())
.collect()
}

/// rustc writes rlibs and static libraries. The system linker makes the other
/// crate types.
fn uses_system_linker(crate_type: &CrateType) -> bool {
!matches!(crate_type, CrateType::Rlib | CrateType::StaticLib)
}

#[allow(clippy::too_many_arguments)]
fn write_device_artifact_object(
tcx: TyCtxt<'_>,
Expand Down Expand Up @@ -1217,6 +1292,77 @@ pub(crate) fn host_target_supported(
mod tests {
use super::*;

#[test]
fn the_artifact_objects_of_an_rlib_are_not_rust_objects() {
let object = PathBuf::from("library.0.0.embed.o");
let rlib_sessions: [&[CrateType]; 6] = [
&[CrateType::Rlib],
&[CrateType::Rlib, CrateType::Cdylib],
&[CrateType::Rlib, CrateType::Dylib],
&[CrateType::Rlib, CrateType::StaticLib],
&[CrateType::Rlib, CrateType::StaticLib, CrateType::Cdylib],
&[CrateType::Rlib, CrateType::StaticLib, CrateType::Dylib],
];
for crate_types in rlib_sessions {
let module = artifact_module(0, object.clone(), crate_types, true);
assert_eq!(module.object, None, "{crate_types:?}");
assert_eq!(module.dwarf_object, Some(object.clone()), "{crate_types:?}");
}

// Sessions without an rlib link the artifact objects.
let other_sessions: [&[CrateType]; 4] = [
&[CrateType::Executable],
&[CrateType::Cdylib],
&[CrateType::Dylib],
&[CrateType::StaticLib],
];
for crate_types in other_sessions {
let module = artifact_module(0, object.clone(), crate_types, true);
assert_eq!(module.object, Some(object.clone()), "{crate_types:?}");
assert_eq!(module.dwarf_object, None, "{crate_types:?}");
}

// With `-Zlink-native-libraries=no`, the system linker gets no link
// arguments.
let mixed = artifact_module(
0,
object.clone(),
&[CrateType::Rlib, CrateType::Cdylib],
false,
);
assert_eq!(mixed.object, Some(object.clone()));
let rlib = artifact_module(0, object.clone(), &[CrateType::Rlib], false);
assert_eq!(rlib.dwarf_object, Some(object));
}

#[test]
fn the_system_linker_gets_the_artifact_objects() {
let artifact = PathBuf::from("library.0.0.embed.o");
let crate_types = [CrateType::Rlib, CrateType::Cdylib];
let modules = [
// A module of LLVM with split debug information.
CompiledModule {
name: "library.0".to_string(),
kind: ModuleKind::Regular,
object: Some(PathBuf::from("library.0.rcgu.o")),
dwarf_object: Some(PathBuf::from("library.0.rcgu.dwo")),
bytecode: None,
assembly: None,
llvm_ir: None,
global_asm_object: None,
},
artifact_module(0, artifact.clone(), &crate_types, true),
];
assert_eq!(
artifact_link_args(&modules, &crate_types),
[artifact.as_path()]
);

// rustc puts the DWARF objects in rlibs and static libraries.
assert!(artifact_link_args(&modules, &[CrateType::Rlib]).is_empty());
assert!(artifact_link_args(&modules, &[CrateType::Rlib, CrateType::StaticLib]).is_empty());
}

#[test]
fn host_targets_that_match_the_ptx_data_model_are_accepted() {
use rustc_abi::Endian;
Expand Down
21 changes: 9 additions & 12 deletions cuda-oxide/cuda-oxide-book/compiler/rustc-codegen-cuda.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,20 +122,17 @@ the host pipeline always runs.

The `CodegenBackend` trait also requires `join_codegen()` and `link()`. These
handle waiting for parallel codegen threads and invoking the linker,
respectively. In cuda-oxide, both are pure delegation:
respectively. In cuda-oxide, both call the LLVM backend. The backend also
writes artifact objects for the embedded artifact bundles. `join_codegen()` and
`link()` make sure that the outputs contain these objects:

```rust
fn join_codegen(&self, ongoing: Box<dyn Any>, sess: &Session) -> ... {
self.llvm_backend.join_codegen(ongoing, sess)
}

fn link(&self, sess: &Session, codegen: ..., outputs: &OutputFilenames) -> ... {
self.llvm_backend.link(sess, codegen, outputs)
}
```
- `join_codegen()` adds a module for each artifact object.
- `link()` can give artifact objects to the system linker as link arguments.

No interception, no modification. The LLVM backend handles linking exactly as
it normally would.
With LTO, rustc reads bitcode from the Rust objects of each rlib. The artifact
objects contain no bitcode. The doc comment of `artifact_module()` shows which
artifact objects are Rust objects and which `link()` gives to the system
linker.

```{note}
For most crates in the dependency tree -- `core`, `cuda-core`, random
Expand Down