Skip to content

Commit 0665766

Browse files
committed
rustc: Add a new wasm ABI
This commit implements the idea of a new ABI for the WebAssembly target, one called `"wasm"`. This ABI is entirely of my own invention and has no current precedent, but I think that the addition of this ABI might help solve a number of issues with the WebAssembly targets. When `wasm32-unknown-unknown` was first added to Rust I naively "implemented an abi" for the target. I then went to write `wasm-bindgen` which accidentally relied on details of this ABI. Turns out the ABI definition didn't match C, which is causing issues for C/Rust interop. Currently the compiler has a "wasm32 bindgen compat" ABI which is the original implementation I added, and it's purely there for, well, `wasm-bindgen`. Another issue with the WebAssembly target is that it's not clear to me when and if the default C ABI will change to account for WebAssembly's multi-value feature (a feature that allows functions to return multiple values). Even if this does happen, though, it seems like the C ABI will be guided based on the performance of WebAssembly code and will likely not match even what the current wasm-bindgen-compat ABI is today. This leaves a hole in Rust's expressivity in binding WebAssembly where given a particular import type, Rust may not be able to import that signature with an updated C ABI for multi-value. To fix these issues I had the idea of a new ABI for WebAssembly, one called `wasm`. The definition of this ABI is "what you write maps straight to wasm". The goal here is that whatever you write down in the parameter list or in the return values goes straight into the function's signature in the WebAssembly file. This special ABI is for intentionally matching the ABI of an imported function from the environment or exporting a function with the right signature. With the addition of a new ABI, this enables rustc to: * Eventually remove the "wasm-bindgen compat hack". Once this ABI is stable wasm-bindgen can switch to using it everywhere. Afterwards the wasm32-unknown-unknown target can have its default ABI updated to match C. * Expose the ability to precisely match an ABI signature for a WebAssembly function, regardless of what the C ABI that clang chooses turns out to be. * Continue to evolve the definition of the default C ABI to match what clang does on all targets, since the purpose of that ABI will be explicitly matching C rather than generating particular function imports/exports. Naturally this is implemented as an unstable feature initially, but it would be nice for this to get stabilized (if it works) in the near-ish future to remove the wasm32-unknown-unknown incompatibility with the C ABI. Doing this, however, requires the feature to be on stable because wasm-bindgen works with stable Rust.
1 parent 803ddb8 commit 0665766

File tree

21 files changed

+388
-120
lines changed

21 files changed

+388
-120
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ impl<'a> PostExpansionVisitor<'a> {
196196
"thiscall-unwind ABI is experimental and subject to change"
197197
);
198198
}
199+
"wasm" => {
200+
gate_feature_post!(
201+
&self,
202+
wasm_abi,
203+
span,
204+
"wasm ABI is experimental and subject to change"
205+
);
206+
}
199207
abi => self
200208
.sess
201209
.parse_sess

compiler/rustc_codegen_llvm/src/attributes.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use rustc_middle::ty::query::Providers;
1313
use rustc_middle::ty::{self, TyCtxt};
1414
use rustc_session::config::{OptLevel, SanitizerSet};
1515
use rustc_session::Session;
16+
use rustc_target::spec::abi::Abi;
1617
use rustc_target::spec::StackProbeType;
1718

1819
use crate::attributes;
@@ -289,7 +290,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
289290
// The target doesn't care; the subtarget reads our attribute.
290291
apply_tune_cpu_attr(cx, llfn);
291292

292-
let function_features = codegen_fn_attrs
293+
let mut function_features = codegen_fn_attrs
293294
.target_features
294295
.iter()
295296
.map(|f| {
@@ -301,6 +302,18 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
301302
InstructionSetAttr::ArmT32 => "+thumb-mode".to_string(),
302303
}))
303304
.collect::<Vec<String>>();
305+
306+
// The `"wasm"` abi on wasm targets automatically enables the `+multivalue`
307+
// feature because the purpose of the wasm abi is to match the WebAssembly
308+
// specification, which has this feature. This won't be needed when LLVM
309+
// enables this `multivalue` feature by default.
310+
if cx.tcx.sess.target.arch == "wasm32" && !cx.tcx.is_closure(instance.def_id()) {
311+
let abi = cx.tcx.fn_sig(instance.def_id()).abi();
312+
if abi == Abi::Wasm {
313+
function_features.push("+multivalue".to_string());
314+
}
315+
}
316+
304317
if !function_features.is_empty() {
305318
let mut global_features = llvm_util::llvm_global_features(cx.tcx.sess);
306319
global_features.extend(function_features.into_iter());

compiler/rustc_feature/src/active.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,9 @@ declare_features! (
644644
/// Allows `extern "C-unwind" fn` to enable unwinding across ABI boundaries.
645645
(active, c_unwind, "1.52.0", Some(74990), None),
646646

647+
/// Allows `extern "wasm" fn`
648+
(active, wasm_abi, "1.53.0", Some(83788), None),
649+
647650
// -------------------------------------------------------------------------
648651
// feature-group-end: actual feature gates
649652
// -------------------------------------------------------------------------

compiler/rustc_middle/src/ty/layout.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,6 +2630,7 @@ fn fn_can_unwind(
26302630
| AvrInterrupt
26312631
| AvrNonBlockingInterrupt
26322632
| CCmseNonSecureCall
2633+
| Wasm
26332634
| RustIntrinsic
26342635
| PlatformIntrinsic
26352636
| Unadjusted => false,
@@ -2712,6 +2713,7 @@ where
27122713
AmdGpuKernel => Conv::AmdGpuKernel,
27132714
AvrInterrupt => Conv::AvrInterrupt,
27142715
AvrNonBlockingInterrupt => Conv::AvrNonBlockingInterrupt,
2716+
Wasm => Conv::C,
27152717

27162718
// These API constants ought to be more specific...
27172719
Cdecl => Conv::C,

compiler/rustc_mir_build/src/build/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ fn should_abort_on_panic(tcx: TyCtxt<'_>, fn_def_id: LocalDefId, abi: Abi) -> bo
609609
| AvrInterrupt
610610
| AvrNonBlockingInterrupt
611611
| CCmseNonSecureCall
612+
| Wasm
612613
| RustIntrinsic
613614
| PlatformIntrinsic
614615
| Unadjusted => true,

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ symbols! {
12921292
vreg,
12931293
vreg_low16,
12941294
warn,
1295+
wasm_abi,
12951296
wasm_import_module,
12961297
wasm_target_feature,
12971298
while_let,

compiler/rustc_target/src/abi/call/mod.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ mod s390x;
1919
mod sparc;
2020
mod sparc64;
2121
mod wasm32;
22-
mod wasm32_bindgen_compat;
2322
mod x86;
2423
mod x86_64;
2524
mod x86_win64;
@@ -647,11 +646,14 @@ impl<'a, Ty> FnAbi<'a, Ty> {
647646
"nvptx64" => nvptx64::compute_abi_info(self),
648647
"hexagon" => hexagon::compute_abi_info(self),
649648
"riscv32" | "riscv64" => riscv::compute_abi_info(cx, self),
650-
"wasm32" => match cx.target_spec().os.as_str() {
651-
"emscripten" | "wasi" => wasm32::compute_abi_info(cx, self),
652-
_ => wasm32_bindgen_compat::compute_abi_info(self),
653-
},
654-
"asmjs" => wasm32::compute_abi_info(cx, self),
649+
"wasm32" => {
650+
if cx.target_spec().adjust_abi(abi) == spec::abi::Abi::Wasm {
651+
wasm32::compute_wasm_abi_info(self)
652+
} else {
653+
wasm32::compute_c_abi_info(cx, self)
654+
}
655+
}
656+
"asmjs" => wasm32::compute_c_abi_info(cx, self),
655657
a => return Err(format!("unrecognized arch \"{}\" in target specification", a)),
656658
}
657659

compiler/rustc_target/src/abi/call/wasm32.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ where
4040
}
4141
}
4242

43-
pub fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
43+
/// The purpose of this ABI is to match the C ABI (aka clang) exactly.
44+
pub fn compute_c_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>)
4445
where
4546
Ty: TyAndLayoutMethods<'a, C> + Copy,
4647
C: LayoutOf<Ty = Ty, TyAndLayout = TyAndLayout<'a, Ty>> + HasDataLayout,
@@ -56,3 +57,27 @@ where
5657
classify_arg(cx, arg);
5758
}
5859
}
60+
61+
/// The purpose of this ABI is for matching the WebAssembly standard. This
62+
/// intentionally diverges from the C ABI and is specifically crafted to take
63+
/// advantage of LLVM's support of multiple returns in WebAssembly.
64+
pub fn compute_wasm_abi_info<Ty>(fn_abi: &mut FnAbi<'_, Ty>) {
65+
if !fn_abi.ret.is_ignore() {
66+
classify_ret(&mut fn_abi.ret);
67+
}
68+
69+
for arg in &mut fn_abi.args {
70+
if arg.is_ignore() {
71+
continue;
72+
}
73+
classify_arg(arg);
74+
}
75+
76+
fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {
77+
ret.extend_integer_width_to(32);
78+
}
79+
80+
fn classify_arg<Ty>(arg: &mut ArgAbi<'_, Ty>) {
81+
arg.extend_integer_width_to(32);
82+
}
83+
}

compiler/rustc_target/src/abi/call/wasm32_bindgen_compat.rs

Lines changed: 0 additions & 29 deletions
This file was deleted.

compiler/rustc_target/src/spec/abi.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub enum Abi {
3434
AvrInterrupt,
3535
AvrNonBlockingInterrupt,
3636
CCmseNonSecureCall,
37+
Wasm,
3738

3839
// Multiplatform / generic ABIs
3940
System { unwind: bool },
@@ -83,6 +84,7 @@ const AbiDatas: &[AbiData] = &[
8384
generic: false,
8485
},
8586
AbiData { abi: Abi::CCmseNonSecureCall, name: "C-cmse-nonsecure-call", generic: false },
87+
AbiData { abi: Abi::Wasm, name: "wasm", generic: false },
8688
// Cross-platform ABIs
8789
AbiData { abi: Abi::System { unwind: false }, name: "system", generic: true },
8890
AbiData { abi: Abi::System { unwind: true }, name: "system-unwind", generic: true },
@@ -131,13 +133,14 @@ impl Abi {
131133
AvrInterrupt => 18,
132134
AvrNonBlockingInterrupt => 19,
133135
CCmseNonSecureCall => 20,
136+
Wasm => 21,
134137
// Cross-platform ABIs
135-
System { unwind: false } => 21,
136-
System { unwind: true } => 22,
137-
RustIntrinsic => 23,
138-
RustCall => 24,
139-
PlatformIntrinsic => 25,
140-
Unadjusted => 26,
138+
System { unwind: false } => 22,
139+
System { unwind: true } => 23,
140+
RustIntrinsic => 24,
141+
RustCall => 25,
142+
PlatformIntrinsic => 26,
143+
Unadjusted => 27,
141144
};
142145
debug_assert!(
143146
AbiDatas

0 commit comments

Comments
 (0)