Skip to content

Commit d63db62

Browse files
Rollup merge of rust-lang#156642 - Sa4dUs:offload-macro, r=ZuseZ4
`offload_kernel` macro expansion r? @ZuseZ4
2 parents 694e1f9 + 5e81772 commit d63db62

12 files changed

Lines changed: 407 additions & 53 deletions

File tree

compiler/rustc_builtin_macros/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ mod format_foreign;
4242
mod global_allocator;
4343
mod iter;
4444
mod log_syntax;
45+
mod offload;
4546
mod pattern_type;
4647
mod source_util;
4748
mod test;
@@ -116,6 +117,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
116117
eii_declaration: eii::eii_declaration,
117118
eii_shared_macro: eii::eii_shared_macro,
118119
global_allocator: global_allocator::expand,
120+
offload_kernel: offload::expand_kernel,
119121
test: test::expand_test,
120122
test_case: test::expand_test_case,
121123
unsafe_eii: eii::unsafe_eii,
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use rustc_ast::token::{Delimiter, Token, TokenKind};
2+
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
3+
use rustc_ast::{AttrItem, ast};
4+
use rustc_expand::base::{Annotatable, ExtCtxt};
5+
use rustc_session::config::Offload;
6+
use rustc_span::{Ident, Span, sym};
7+
use thin_vec::thin_vec;
8+
9+
use crate::errors;
10+
11+
fn compile_for_device(ecx: &mut ExtCtxt<'_>) -> bool {
12+
ecx.sess.opts.unstable_opts.offload.contains(&Offload::Device)
13+
}
14+
15+
fn outer_normal_attr(
16+
kind: &Box<rustc_ast::NormalAttr>,
17+
id: rustc_ast::AttrId,
18+
span: Span,
19+
) -> rustc_ast::Attribute {
20+
let style = rustc_ast::AttrStyle::Outer;
21+
let kind = rustc_ast::AttrKind::Normal(kind.clone());
22+
rustc_ast::Attribute { kind, id, style, span }
23+
}
24+
25+
fn extract_fn(
26+
item: &Annotatable,
27+
) -> Option<(ast::Visibility, ast::FnSig, Ident, ast::Generics, Option<Box<ast::Block>>)> {
28+
match item {
29+
Annotatable::Item(iitem) => match &iitem.kind {
30+
ast::ItemKind::Fn(ast::Fn { sig, ident, generics, body, .. }) => {
31+
Some((iitem.vis.clone(), sig.clone(), *ident, generics.clone(), body.clone()))
32+
}
33+
_ => None,
34+
},
35+
_ => None,
36+
}
37+
}
38+
39+
/// The `offload_kernel` macro expands the function into two separate definitions:
40+
/// one on the host to handle the call, and one on the device for executing the kernel.
41+
///
42+
/// ```
43+
/// #[offload_kernel]
44+
/// fn foo(a: &[f32], b: &[f32], c: *mut f32) {
45+
/// *c = a[0] + b[0];
46+
/// }
47+
/// ```
48+
///
49+
/// This expands to the host-side function:
50+
///
51+
/// ```
52+
/// #[unsafe(no_mangle)]
53+
/// #[inline(never)]
54+
/// fn foo(_: &[f32], _: &[f32], _: *mut f32) {
55+
/// ::core::panicking::panic("not implemented")
56+
/// }
57+
/// ```
58+
///
59+
/// And the device-side kernel:
60+
///
61+
/// ```
62+
/// #[rustc_offload_kernel]
63+
/// #[unsafe(no_mangle)]
64+
/// unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) {
65+
/// *c = a[0] + b[0];
66+
/// }
67+
/// ```
68+
pub(crate) fn expand_kernel(
69+
ecx: &mut ExtCtxt<'_>,
70+
expand_span: Span,
71+
_meta_item: &ast::MetaItem,
72+
item: Annotatable,
73+
) -> Vec<Annotatable> {
74+
let dcx = ecx.sess.dcx();
75+
76+
let Some((vis, sig, ident, generics, body)) = extract_fn(&item) else {
77+
dcx.emit_err(errors::AutoDiffInvalidApplication { span: item.span() });
78+
return vec![item];
79+
};
80+
81+
let span = ecx.with_def_site_ctxt(expand_span);
82+
83+
// device function
84+
let mut device_fn = Box::new(ast::Fn {
85+
defaultness: ast::Defaultness::Implicit,
86+
sig: sig.clone(),
87+
ident,
88+
generics: generics.clone(),
89+
contract: None,
90+
body,
91+
define_opaque: None,
92+
eii_impls: Default::default(),
93+
});
94+
95+
let extern_gpu_kernel = ast::Extern::from_abi(
96+
Some(ast::StrLit {
97+
symbol: sym::gpu_kernel,
98+
suffix: None,
99+
symbol_unescaped: sym::gpu_kernel,
100+
style: ast::StrStyle::Cooked,
101+
span,
102+
}),
103+
span,
104+
);
105+
device_fn.sig.header.ext = extern_gpu_kernel;
106+
device_fn.sig.header.safety = ast::Safety::Unsafe(span);
107+
108+
// rustc_offload_kernel attr
109+
let rustc_offload_kernel_attr =
110+
Box::new(ast::NormalAttr::from_ident(Ident::with_dummy_span(sym::rustc_offload_kernel)));
111+
let rustc_offload_kernel = outer_normal_attr(
112+
&rustc_offload_kernel_attr,
113+
ecx.sess.psess.attr_id_generator.mk_attr_id(),
114+
span,
115+
);
116+
117+
// unsafe(no_mangle) attr
118+
let unsafe_item = AttrItem {
119+
unsafety: ast::Safety::Unsafe(span),
120+
path: ast::Path::from_ident(Ident::new(sym::no_mangle, span)),
121+
args: ast::AttrItemKind::Unparsed(ast::AttrArgs::Empty),
122+
tokens: None,
123+
};
124+
125+
let no_mangle_attr = Box::new(ast::NormalAttr { item: unsafe_item, tokens: None });
126+
let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
127+
let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span);
128+
129+
let device_item = {
130+
let mut item = ecx.item(
131+
span,
132+
thin_vec![rustc_offload_kernel, unsafe_no_mangle],
133+
ast::ItemKind::Fn(device_fn),
134+
);
135+
item.vis = vis.clone();
136+
Annotatable::Item(item)
137+
};
138+
139+
// unimplemented! body
140+
let macro_expr = ecx.expr_macro_call(
141+
span,
142+
ecx.macro_call(
143+
span,
144+
ecx.path_global(
145+
span,
146+
[sym::std, sym::unimplemented].map(|s| Ident::new(s, span)).to_vec(),
147+
),
148+
Delimiter::Parenthesis,
149+
TokenStream::default(),
150+
),
151+
);
152+
let stmt = ecx.stmt_expr(macro_expr);
153+
let body = ecx.block(span, thin_vec![stmt]);
154+
155+
// host function
156+
let mut host_fn = Box::new(ast::Fn {
157+
defaultness: ast::Defaultness::Implicit,
158+
sig: sig.clone(),
159+
ident,
160+
generics: generics.clone(),
161+
contract: None,
162+
body: Some(body),
163+
define_opaque: None,
164+
eii_impls: Default::default(),
165+
});
166+
167+
for param in host_fn.sig.decl.inputs.iter_mut() {
168+
param.pat = Box::new(ecx.pat_wild(param.pat.span));
169+
}
170+
171+
// inline(never) attr
172+
let ts: Vec<TokenTree> = vec![TokenTree::Token(
173+
Token::new(TokenKind::Ident(sym::never, false.into()), span),
174+
Spacing::Joint,
175+
)];
176+
177+
let never_arg = ast::DelimArgs {
178+
dspan: DelimSpan::from_single(span),
179+
delim: Delimiter::Parenthesis,
180+
tokens: TokenStream::from_iter(ts),
181+
};
182+
183+
let inline_item = ast::AttrItem {
184+
unsafety: ast::Safety::Default,
185+
path: ast::Path::from_ident(Ident::with_dummy_span(sym::inline)),
186+
args: rustc_ast::ast::AttrItemKind::Unparsed(ast::AttrArgs::Delimited(never_arg)),
187+
tokens: None,
188+
};
189+
let inline_never_attr = Box::new(ast::NormalAttr { item: inline_item, tokens: None });
190+
191+
let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
192+
let inline_never = outer_normal_attr(&inline_never_attr, new_id, span);
193+
194+
let new_id = ecx.sess.psess.attr_id_generator.mk_attr_id();
195+
let unsafe_no_mangle = outer_normal_attr(&no_mangle_attr, new_id, span);
196+
197+
let host_item = {
198+
let mut item =
199+
ecx.item(span, thin_vec![unsafe_no_mangle, inline_never], ast::ItemKind::Fn(host_fn));
200+
item.vis = vis.clone();
201+
Annotatable::Item(item)
202+
};
203+
204+
if compile_for_device(ecx) { vec![device_item] } else { vec![host_item] }
205+
}

compiler/rustc_span/src/symbol.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ symbols! {
10401040
global_asm,
10411041
global_registration,
10421042
globs,
1043+
gpu_kernel: "gpu-kernel",
10431044
gpu_launch_sized_workgroup_mem,
10441045
gt,
10451046
guard,
@@ -1424,6 +1425,7 @@ symbols! {
14241425
of,
14251426
off,
14261427
offload,
1428+
offload_kernel,
14271429
offset,
14281430
offset_of,
14291431
offset_of_enum,
@@ -2163,6 +2165,7 @@ symbols! {
21632165
underscore_imports,
21642166
underscore_lifetimes,
21652167
uniform_paths,
2168+
unimplemented,
21662169
unit,
21672170
universal_impl_trait,
21682171
unix,

library/core/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ pub mod autodiff {
234234
pub use crate::macros::builtin::{autodiff_forward, autodiff_reverse};
235235
}
236236

237+
#[unstable(feature = "gpu_offload", issue = "131513")]
238+
#[doc = include_str!("../../core/src/offload.md")]
239+
pub mod offload;
240+
237241
#[unstable(feature = "contracts", issue = "128044")]
238242
pub mod contracts;
239243

library/core/src/macros/mod.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,6 +1628,48 @@ pub(crate) mod builtin {
16281628
/* compiler built-in */
16291629
}
16301630

1631+
/// The `offload_kernel` macro is applied to a function to generate two separate
1632+
/// definitions: a host-side wrapper for dispatch and a device-side kernel.
1633+
///
1634+
/// The macro does not perform the offload itself. It generates the necessary
1635+
/// code required by the compiler's offloading infrastructure.
1636+
///
1637+
/// ### Usage example:
1638+
///
1639+
/// ```rust,ignore (offload requires a -Z flag)
1640+
/// #[offload_kernel]
1641+
/// fn foo(a: &[f32], b: &[f32], c: *mut f32) {
1642+
/// *c = a[0] + b[0];
1643+
/// }
1644+
/// ```
1645+
///
1646+
/// This expands to the host-side function:
1647+
///
1648+
/// ```rust,ignore (offload requires a -Z flag)
1649+
/// #[unsafe(no_mangle)]
1650+
/// #[inline(never)]
1651+
/// fn foo(_: &[f32], _: &[f32], _: *mut f32) {
1652+
/// ::core::panicking::panic("not implemented")
1653+
/// }
1654+
/// ```
1655+
///
1656+
/// And the device-side kernel:
1657+
///
1658+
/// ```rust,ignore (offload requires a -Z flag)
1659+
/// #[rustc_offload_kernel]
1660+
/// #[unsafe(no_mangle)]
1661+
/// unsafe extern "gpu-kernel" fn foo(a: &[f32], b: &[f32], c: *mut f32) {
1662+
/// *c = a[0] + b[0];
1663+
/// }
1664+
/// ```
1665+
#[unstable(feature = "gpu_offload", issue = "131513")]
1666+
#[allow_internal_unstable(rustc_attrs)]
1667+
#[allow_internal_unstable(core_intrinsics)]
1668+
#[rustc_builtin_macro]
1669+
pub macro offload_kernel($item:item) {
1670+
/* compiler built-in */
1671+
}
1672+
16311673
/// Asserts that a boolean expression is `true` at runtime.
16321674
///
16331675
/// This will invoke the [`panic!`] macro if the provided expression cannot be

library/core/src/offload.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
This module provides support for gpu offloading. For technical details regarding the `offload_kernel`
2+
and how to use it, see their respective documentation.
3+
4+
## General usage
5+
The `offload_kernel` macro can be applied to a function to generate the necessary code to launch a
6+
kernel on the target device.
7+
8+
```rust,ignore (optional component)
9+
#[offload_kernel]
10+
fn kernel(x: *mut [f64; 256]) {
11+
// SAFETY:
12+
// calling our `arch` functions and dereferencing a raw pointer is unsafe
13+
unsafe {
14+
let n = (*x).len();
15+
let i = (thread_idx_x() + block_idx_x() * block_dim_x()) as usize;
16+
if i < n {
17+
(*x)[i] = i as f64;
18+
}
19+
}
20+
}
21+
```
22+
23+
To launch an offloaded kernel, the only current way is to use the `core::intrinsic::offload`
24+
intrinsic (note that intrinsics usage is discouraged outside the standard library). This
25+
allows you to specify grid and block dimensions and pass the required arguments to the device.
26+
27+
```rust,ignore (optional component)
28+
let mut x = [0.0f64; 256];
29+
core::intrinsics::offload::<_, _, ()>(kernel, [256, 1, 1], [1, 1, 1], (&mut x as *mut [f64; 256],));
30+
```
31+
32+
For precise information on the `offload` intrinsic, see its respective documentation.
33+
34+
## Current limitations:
35+
36+
- Usage is restricted to types supported by the current device-mapping implementation.
37+
- Generics and functions accepting dyn Trait are not supported.
38+
- Kernel execution is currently restricted to intrinsics usage, which is discouraged outside of the
39+
standard library.

library/core/src/offload/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// offload module
2+
#[unstable(feature = "gpu_offload", issue = "131513")]
3+
pub use crate::macros::builtin::offload_kernel;
4+
#[unstable(feature = "gpu_offload", issue = "131513")]
5+
pub use crate::offload;

library/std/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@
289289
#![feature(f16)]
290290
#![feature(f128)]
291291
#![feature(ffi_const)]
292+
#![feature(gpu_offload)]
292293
#![feature(intra_doc_pointers)]
293294
#![feature(lang_items)]
294295
#![feature(link_cfg)]
@@ -663,6 +664,12 @@ pub mod autodiff {
663664
pub use core::autodiff::{autodiff_forward, autodiff_reverse};
664665
}
665666

667+
#[unstable(feature = "gpu_offload", issue = "131513")]
668+
#[doc = include_str!("../../core/src/offload.md")]
669+
pub mod offload {
670+
pub use core::offload::{offload, offload_kernel};
671+
}
672+
666673
#[stable(feature = "futures_api", since = "1.36.0")]
667674
pub mod task {
668675
//! Types and Traits for working with asynchronous tasks.

0 commit comments

Comments
 (0)