Skip to main content

inline_blob/
lib.rs

1//! Inline gigabytes of data into a binary or library crate by emitting a
2//! `static` placed in `.lrodata.*` so the linker can use large-code-model
3//! relocations on 64-bit ELF targets.
4
5use proc_macro::{TokenStream, TokenTree};
6use std::str::FromStr;
7
8/// `blob!(<vis> static NAME, <path-expr>);`
9///
10/// `<path-expr>` is forwarded verbatim to `include_bytes!`, so any expression
11/// that the latter accepts works — a string literal, or e.g.
12/// `concat!(env!("OUT_DIR"), "/blob.bin")` for a build-script-generated file.
13///
14/// Expands to:
15///
16/// ```ignore
17/// #[used]
18/// #[unsafe(link_section = ".lrodata.<lowercased name>")]
19/// <vis> static NAME: [u8; const { include_bytes!("path/to/data").len() }]
20///     = *include_bytes!("path/to/data");
21/// ```
22///
23/// (`[u8; _]` is not yet allowed in `static` item signatures on stable, so the
24/// length is computed via a `const` block. `include_bytes!` is evaluated at
25/// compile time and the resulting array is deduplicated, so the binary only
26/// carries one copy of the data.)
27#[proc_macro]
28pub fn blob(input: TokenStream) -> TokenStream {
29    let tokens: Vec<TokenTree> = input.into_iter().collect();
30
31    let static_idx = tokens
32        .iter()
33        .position(|t| matches!(t, TokenTree::Ident(i) if i.to_string() == "static"))
34        .expect("inline-blob: expected `static` keyword (e.g. `pub static NAME, \"path\"`)");
35
36    let vis_stream: TokenStream = tokens[..static_idx].iter().cloned().collect();
37    let vis_str = vis_stream.to_string();
38
39    let after = &tokens[static_idx + 1..];
40
41    let name = match after.first() {
42        Some(TokenTree::Ident(i)) => i.to_string(),
43        _ => panic!("inline-blob: expected identifier after `static`"),
44    };
45
46    match after.get(1) {
47        Some(TokenTree::Punct(p)) if p.as_char() == ',' => {}
48        _ => panic!("inline-blob: expected `,` after identifier"),
49    }
50
51    let path_tokens: TokenStream = after[2..].iter().cloned().collect();
52    if path_tokens.is_empty() {
53        panic!("inline-blob: expected path expression after `,`");
54    }
55    let path_expr = path_tokens.to_string();
56
57    let anchor_section = format!(".lbss.{}", name.to_lowercase());
58    let section = format!(".lrodata.{}", name.to_lowercase());
59
60    let generated = format!(
61        "#[used] \
62         #[unsafe(link_section = \"{anchor_section}\")] \
63         static __ANCHOR_{name}: [u8; 1] = [0];
64         #[used] \
65         #[unsafe(link_section = \"{section}\")] \
66         {vis_str} static {name}: [u8; const {{ ::core::include_bytes!({path_expr}).len() }}] \
67         = *::core::include_bytes!({path_expr});"
68    );
69
70    TokenStream::from_str(&generated)
71        .expect("inline-blob: failed to construct output token stream")
72}