Skip to content

Commit 4f93105

Browse files
committed
[WIP] Implement wasm proc macro support
1 parent 84f1f1b commit 4f93105

6 files changed

Lines changed: 308 additions & 8 deletions

File tree

Cargo.lock

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4346,6 +4346,7 @@ dependencies = [
43464346
"rustc_target",
43474347
"tempfile",
43484348
"tracing",
4349+
"wasmi",
43494350
]
43504351

43514352
[[package]]
@@ -5341,6 +5342,12 @@ dependencies = [
53415342
"color-eyre",
53425343
]
53435344

5345+
[[package]]
5346+
name = "spin"
5347+
version = "0.9.8"
5348+
source = "registry+https://github.com/rust-lang/crates.io-index"
5349+
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
5350+
53445351
[[package]]
53455352
name = "stable_deref_trait"
53465353
version = "1.2.1"
@@ -5366,6 +5373,16 @@ version = "1.1.0"
53665373
source = "registry+https://github.com/rust-lang/crates.io-index"
53675374
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
53685375

5376+
[[package]]
5377+
name = "string-interner"
5378+
version = "0.19.0"
5379+
source = "registry+https://github.com/rust-lang/crates.io-index"
5380+
checksum = "23de088478b31c349c9ba67816fa55d9355232d63c3afea8bf513e31f0f1d2c0"
5381+
dependencies = [
5382+
"hashbrown 0.15.5",
5383+
"serde",
5384+
]
5385+
53695386
[[package]]
53705387
name = "string_cache"
53715388
version = "0.8.9"
@@ -6369,6 +6386,47 @@ dependencies = [
63696386
"wasmparser 0.246.2",
63706387
]
63716388

6389+
[[package]]
6390+
name = "wasmi"
6391+
version = "1.0.9"
6392+
source = "registry+https://github.com/rust-lang/crates.io-index"
6393+
checksum = "22bf475363d09d960b48275c4ea9403051add498a9d80c64dbc91edabab9d1d0"
6394+
dependencies = [
6395+
"spin",
6396+
"wasmi_collections",
6397+
"wasmi_core",
6398+
"wasmi_ir",
6399+
"wasmparser 0.228.0",
6400+
"wat",
6401+
]
6402+
6403+
[[package]]
6404+
name = "wasmi_collections"
6405+
version = "1.0.9"
6406+
source = "registry+https://github.com/rust-lang/crates.io-index"
6407+
checksum = "85851acbdffd675a9b699b3590406a1d37fc1e1fd073743c7c9cf47c59caacba"
6408+
dependencies = [
6409+
"string-interner",
6410+
]
6411+
6412+
[[package]]
6413+
name = "wasmi_core"
6414+
version = "1.0.9"
6415+
source = "registry+https://github.com/rust-lang/crates.io-index"
6416+
checksum = "ef64cf60195d1f937dbaed592a5afce3e6d86868fb8070c5255bc41539d68f9d"
6417+
dependencies = [
6418+
"libm",
6419+
]
6420+
6421+
[[package]]
6422+
name = "wasmi_ir"
6423+
version = "1.0.9"
6424+
source = "registry+https://github.com/rust-lang/crates.io-index"
6425+
checksum = "5dcb572ce4400e06b5475819f3d6b9048513efbca785f0b9ef3a41747f944fd8"
6426+
dependencies = [
6427+
"wasmi_core",
6428+
]
6429+
63726430
[[package]]
63736431
name = "wasmparser"
63746432
version = "0.219.2"
@@ -6379,6 +6437,16 @@ dependencies = [
63796437
"indexmap",
63806438
]
63816439

6440+
[[package]]
6441+
name = "wasmparser"
6442+
version = "0.228.0"
6443+
source = "registry+https://github.com/rust-lang/crates.io-index"
6444+
checksum = "4abf1132c1fdf747d56bbc1bb52152400c70f336870f968b85e89ea422198ae3"
6445+
dependencies = [
6446+
"bitflags",
6447+
"indexmap",
6448+
]
6449+
63826450
[[package]]
63836451
name = "wasmparser"
63846452
version = "0.236.1"

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
```
2+
./x.py build library --target wasm32-unknown-unknown,x86_64-unknown-linux-gnu --keep-stage-std 1
3+
echo 'extern crate proc_macro; #[proc_macro] pub fn foo(a: proc_macro::TokenStream) -> proc_macro::TokenStream { println!("foo"); (a.to_string() + ";println!(\"Hello from wasm proc macro!\");").parse().unwrap() }' | rustc +stage1 - --crate-type proc-macro --target wasm32-unknown-unknown -Zwasm-proc-macros
4+
echo 'fn main() { foo::foo!(println!("Hello World")); }' | rustc +stage1 --extern foo=rust_out.wasm - --edition 2024 && ./rust_out
5+
```
6+
7+
18
<div align="center">
29
<picture>
310
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/rust-lang/www.rust-lang.org/master/static/images/rust-social-wide-dark.svg">

compiler/rustc_codegen_ssa/src/back/linker.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1870,7 +1870,18 @@ fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec<(String, Symbol
18701870
let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE);
18711871
let proc_macro_decls_name = tcx.sess.generate_proc_macro_decls_symbol(stable_crate_id);
18721872

1873-
vec![(proc_macro_decls_name, SymbolExportKind::Data)]
1873+
if tcx.sess.target.arch == Arch::Wasm32 {
1874+
vec![
1875+
(proc_macro_decls_name, SymbolExportKind::Data),
1876+
("__rustc_proc_macro_alloc_buffer".to_owned(), SymbolExportKind::Text),
1877+
("__rustc_proc_macro_buffer_replace".to_owned(), SymbolExportKind::Text),
1878+
("__rustc_proc_macro_buffer_ptr".to_owned(), SymbolExportKind::Text),
1879+
("__rustc_proc_macro_buffer_len".to_owned(), SymbolExportKind::Text),
1880+
("__rustc_proc_macro_call_client".to_owned(), SymbolExportKind::Text),
1881+
]
1882+
} else {
1883+
vec![(proc_macro_decls_name, SymbolExportKind::Data)]
1884+
}
18741885
}
18751886

18761887
pub(crate) fn linked_symbols(

compiler/rustc_metadata/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ rustc_span = { path = "../rustc_span" }
3131
rustc_target = { path = "../rustc_target" }
3232
tempfile = "3.7.1"
3333
tracing = "0.1"
34+
wasmi = "1.0.9"
3435
# tidy-alphabetical-end
3536

3637
[target.'cfg(target_os = "aix")'.dependencies]

compiler/rustc_metadata/src/creader.rs

Lines changed: 167 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::collections::BTreeMap;
44
use std::error::Error;
55
use std::path::Path;
66
use std::str::FromStr;
7+
use std::sync::Arc;
78
use std::time::Duration;
89
use std::{cmp, env, iter};
910

@@ -996,18 +997,177 @@ impl CStore {
996997
.map(|proc_macro| IntoDynSyncSend(proc_macro.into_dyn_client()))
997998
.collect())
998999
}
999-
Err(err) => {
1000-
debug!(
1001-
"failed to dlsym proc_macros {} for symbol `{}`",
1002-
path.display(),
1003-
sym_name
1004-
);
1005-
Err(err.into())
1000+
Err(_err) => {
1001+
let engine = wasmi::Engine::default();
1002+
let module = wasmi::Module::new(&engine, std::fs::read(path).unwrap()).unwrap();
1003+
1004+
let mut store = wasmi::Store::new(&engine, ());
1005+
let mut linker = wasmi::Linker::new(&engine);
1006+
linker
1007+
.func_wrap("env", "__rustc_proc_macro_dispatch", |_: u32, _: u32| -> () {
1008+
unreachable!()
1009+
})
1010+
.unwrap();
1011+
let instance = linker.instantiate_and_start(&mut store, &module).unwrap();
1012+
1013+
let memory =
1014+
instance.get_export(&store, "memory").unwrap().into_memory().unwrap();
1015+
1016+
let sym = instance
1017+
.get_export(&store, &sym_name)
1018+
.unwrap()
1019+
.into_global()
1020+
.unwrap()
1021+
.get(&store)
1022+
.i32()
1023+
.unwrap();
1024+
1025+
let mut data = [0; 8];
1026+
memory.read(&store, sym as usize, &mut data).unwrap();
1027+
let ptr = u32::from_le_bytes(data[..4].try_into().unwrap());
1028+
let len = u32::from_le_bytes(data[4..8].try_into().unwrap());
1029+
1030+
Ok((0..len)
1031+
.map(|i| {
1032+
let mut data = [0; 4];
1033+
memory.read(&store, (ptr + 4 * i) as usize, &mut data).unwrap();
1034+
let func_ptr = u32::from_le_bytes(data);
1035+
1036+
IntoDynSyncSend(Self::wasm_macro_client(
1037+
engine.clone(),
1038+
module.clone(),
1039+
func_ptr,
1040+
))
1041+
})
1042+
.collect::<Vec<_>>())
10061043
}
10071044
}
10081045
}
10091046
}
10101047

1048+
fn wasm_macro_client(engine: wasmi::Engine, module: wasmi::Module, func_ptr: u32) -> DynClient {
1049+
DynClient {
1050+
run: Arc::new(move |config| {
1051+
struct Ctx<'a> {
1052+
dispatch: rustc_proc_macro::bridge::Closure<'a>,
1053+
client_refs: Option<ClientRefs>,
1054+
}
1055+
1056+
#[derive(Clone)]
1057+
struct ClientRefs {
1058+
memory: wasmi::Memory,
1059+
buffer_replace: wasmi::TypedFunc<(u32, u32), ()>,
1060+
buffer_ptr: wasmi::TypedFunc<(u32,), (u32,)>,
1061+
buffer_len: wasmi::TypedFunc<(u32,), (u32,)>,
1062+
}
1063+
1064+
impl ClientRefs {
1065+
fn read(
1066+
&self,
1067+
store: &mut impl wasmi::AsContextMut,
1068+
buffer: u32,
1069+
) -> Result<Vec<u8>, wasmi::Error> {
1070+
let (buffer_ptr,) = self.buffer_ptr.call(&mut *store, (buffer,))?;
1071+
let (buffer_len,) = self.buffer_len.call(&mut *store, (buffer,))?;
1072+
let mut data = vec![0; buffer_len as usize];
1073+
self.memory.read(store, buffer_ptr as usize, &mut data)?;
1074+
Ok(data)
1075+
}
1076+
}
1077+
1078+
let mut store = wasmi::Store::new(
1079+
&engine,
1080+
Ctx { dispatch: config.dispatch, client_refs: None },
1081+
);
1082+
let mut linker: wasmi::Linker<Ctx<'_>> = wasmi::Linker::new(&engine);
1083+
linker
1084+
.func_new(
1085+
"env",
1086+
"__rustc_proc_macro_dispatch",
1087+
wasmi::FuncType::new([wasmi::ValType::I32, wasmi::ValType::I32], []),
1088+
|mut caller, inputs, _outputs| {
1089+
let client_refs = caller.data().client_refs.clone().unwrap();
1090+
let input_buffer = inputs[0].i32().unwrap().cast_unsigned();
1091+
let output_buffer = inputs[1].i32().unwrap().cast_unsigned();
1092+
1093+
let input_buffer_data = client_refs.read(&mut caller, input_buffer)?;
1094+
1095+
let output_buffer_data =
1096+
caller.data_mut().dispatch.call(input_buffer_data.into());
1097+
1098+
client_refs.buffer_replace.call(
1099+
&mut caller,
1100+
(output_buffer, output_buffer_data.len().try_into().unwrap()),
1101+
)?;
1102+
let (output_buffer_ptr,) =
1103+
client_refs.buffer_ptr.call(&mut caller, (output_buffer,))?;
1104+
client_refs.memory.write(
1105+
&mut caller,
1106+
output_buffer_ptr as usize,
1107+
&output_buffer_data,
1108+
)?;
1109+
1110+
Ok(())
1111+
},
1112+
)
1113+
.unwrap();
1114+
let instance = linker.instantiate_and_start(&mut store, &module).unwrap();
1115+
1116+
fn get_func<T: wasmi::WasmParams, U: wasmi::WasmResults>(
1117+
instance: &wasmi::Instance,
1118+
store: &impl wasmi::AsContext,
1119+
name: &str,
1120+
) -> wasmi::TypedFunc<T, U> {
1121+
instance
1122+
.get_export(store, name)
1123+
.unwrap()
1124+
.into_func()
1125+
.unwrap()
1126+
.typed(store)
1127+
.unwrap()
1128+
}
1129+
1130+
let client_refs = ClientRefs {
1131+
memory: instance.get_export(&store, "memory").unwrap().into_memory().unwrap(),
1132+
buffer_replace: get_func(
1133+
&instance,
1134+
&store,
1135+
"__rustc_proc_macro_buffer_replace",
1136+
),
1137+
buffer_ptr: get_func(&instance, &store, "__rustc_proc_macro_buffer_ptr"),
1138+
buffer_len: get_func(&instance, &store, "__rustc_proc_macro_buffer_len"),
1139+
};
1140+
store.data_mut().client_refs = Some(client_refs.clone());
1141+
1142+
let alloc_buffer: wasmi::TypedFunc<(u32,), (u32,)> =
1143+
get_func(&instance, &store, "__rustc_proc_macro_alloc_buffer");
1144+
1145+
let call_client: wasmi::TypedFunc<(u32, u32), (u32,)> =
1146+
get_func(&instance, &store, "__rustc_proc_macro_call_client");
1147+
1148+
let (input_buffer,) = alloc_buffer
1149+
.call(&mut store, (config.input.len().try_into().unwrap(),))
1150+
.unwrap();
1151+
let input_buffer_ptr =
1152+
client_refs.buffer_ptr.call(&mut store, (input_buffer,)).unwrap().0;
1153+
instance
1154+
.get_export(&store, "memory")
1155+
.unwrap()
1156+
.into_memory()
1157+
.unwrap()
1158+
.write(&mut store, input_buffer_ptr as usize, &config.input)
1159+
.unwrap();
1160+
1161+
let (output_buffer,) =
1162+
call_client.call(&mut store, (input_buffer, func_ptr)).unwrap();
1163+
1164+
let output_buffer_data = client_refs.read(&mut store, output_buffer).unwrap();
1165+
1166+
output_buffer_data.into()
1167+
}),
1168+
}
1169+
}
1170+
10111171
fn inject_panic_runtime(&mut self, tcx: TyCtxt<'_>, krate: &ast::Crate) {
10121172
// If we're only compiling an rlib, then there's no need to select a
10131173
// panic runtime, so we just skip this section entirely.

library/proc_macro/src/bridge/client.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,56 @@ impl Client {
291291
}
292292
}
293293
}
294+
295+
#[cfg(target_family = "wasm")]
296+
#[allow(unsafe_code)]
297+
#[unsafe(no_mangle)]
298+
fn __rustc_proc_macro_alloc_buffer(len: usize) -> *mut Buffer {
299+
Box::into_raw(Box::new(Buffer::from(vec![0; len])))
300+
}
301+
302+
#[cfg(target_family = "wasm")]
303+
#[allow(unsafe_code)]
304+
#[unsafe(no_mangle)]
305+
unsafe fn __rustc_proc_macro_buffer_replace(buffer: *mut Buffer, len: usize) {
306+
unsafe { (*buffer) = Buffer::from(vec![0; len]) }
307+
}
308+
309+
#[cfg(target_family = "wasm")]
310+
#[allow(unsafe_code)]
311+
#[unsafe(no_mangle)]
312+
unsafe fn __rustc_proc_macro_buffer_ptr(buffer: *mut Buffer) -> *mut u8 {
313+
unsafe { (*buffer).as_mut_ptr() }
314+
}
315+
316+
#[cfg(target_family = "wasm")]
317+
#[allow(unsafe_code)]
318+
#[unsafe(no_mangle)]
319+
unsafe fn __rustc_proc_macro_buffer_len(buffer: *mut Buffer) -> usize {
320+
unsafe { (&*buffer).len() }
321+
}
322+
323+
#[cfg(target_family = "wasm")]
324+
#[allow(unsafe_code)]
325+
#[unsafe(no_mangle)]
326+
fn __rustc_proc_macro_call_client(
327+
input: &mut Buffer,
328+
client: extern "C" fn(BridgeConfig<'_>) -> Buffer,
329+
) -> *mut Buffer {
330+
#[link(wasm_import_module = "env")]
331+
unsafe extern "C" {
332+
fn __rustc_proc_macro_dispatch(input: &Buffer, output: &mut Buffer);
333+
}
334+
335+
let dispatch = closure::Closure::from(Box::leak(Box::new(|input| {
336+
let mut output = Buffer::new();
337+
unsafe { __rustc_proc_macro_dispatch(&input, &mut output) }
338+
output
339+
})));
340+
341+
Box::into_raw(Box::new(client(BridgeConfig {
342+
input: input.take(),
343+
dispatch,
344+
force_show_panics: false,
345+
})))
346+
}

0 commit comments

Comments
 (0)