Skip to content

Commit 1255053

Browse files
committed
Auto merge of #83763 - alexcrichton:wasm-multivalue-abi, r=nagisa
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 multivalue 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.
2 parents 010c236 + 482a3d0 commit 1255053

File tree

23 files changed

+405
-198
lines changed

23 files changed

+405
-198
lines changed

compiler/rustc_ast_passes/src/feature_gate.rs

+8
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

+28-16
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;
1515
use rustc_session::Session;
16+
use rustc_target::spec::abi::Abi;
1617
use rustc_target::spec::{SanitizerSet, StackProbeType};
1718

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

296-
let function_features = codegen_fn_attrs
297+
let mut function_features = codegen_fn_attrs
297298
.target_features
298299
.iter()
299300
.map(|f| {
@@ -305,23 +306,10 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
305306
InstructionSetAttr::ArmT32 => "+thumb-mode".to_string(),
306307
}))
307308
.collect::<Vec<String>>();
308-
if !function_features.is_empty() {
309-
let mut global_features = llvm_util::llvm_global_features(cx.tcx.sess);
310-
global_features.extend(function_features.into_iter());
311-
let features = global_features.join(",");
312-
let val = CString::new(features).unwrap();
313-
llvm::AddFunctionAttrStringValue(
314-
llfn,
315-
llvm::AttributePlace::Function,
316-
cstr!("target-features"),
317-
&val,
318-
);
319-
}
320309

321-
// Note that currently the `wasm-import-module` doesn't do anything, but
322-
// eventually LLVM 7 should read this and ferry the appropriate import
323-
// module to the output file.
324310
if cx.tcx.sess.target.is_like_wasm {
311+
// If this function is an import from the environment but the wasm
312+
// import has a specific module/name, apply them here.
325313
if let Some(module) = wasm_import_module(cx.tcx, instance.def_id()) {
326314
llvm::AddFunctionAttrStringValue(
327315
llfn,
@@ -340,6 +328,30 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
340328
&name,
341329
);
342330
}
331+
332+
// The `"wasm"` abi on wasm targets automatically enables the
333+
// `+multivalue` feature because the purpose of the wasm abi is to match
334+
// the WebAssembly specification, which has this feature. This won't be
335+
// needed when LLVM enables this `multivalue` feature by default.
336+
if !cx.tcx.is_closure(instance.def_id()) {
337+
let abi = cx.tcx.fn_sig(instance.def_id()).abi();
338+
if abi == Abi::Wasm {
339+
function_features.push("+multivalue".to_string());
340+
}
341+
}
342+
}
343+
344+
if !function_features.is_empty() {
345+
let mut global_features = llvm_util::llvm_global_features(cx.tcx.sess);
346+
global_features.extend(function_features.into_iter());
347+
let features = global_features.join(",");
348+
let val = CString::new(features).unwrap();
349+
llvm::AddFunctionAttrStringValue(
350+
llfn,
351+
llvm::AttributePlace::Function,
352+
cstr!("target-features"),
353+
&val,
354+
);
343355
}
344356
}
345357

compiler/rustc_feature/src/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,9 @@ declare_features! (
645645
/// Allows using `#[repr(align(...))]` on function items
646646
(active, fn_align, "1.53.0", Some(82232), None),
647647

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

compiler/rustc_middle/src/ty/layout.rs

+2
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

+1
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

+1
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,7 @@ symbols! {
12951295
vreg,
12961296
vreg_low16,
12971297
warn,
1298+
wasm_abi,
12981299
wasm_import_module,
12991300
wasm_target_feature,
13001301
while_let,

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ mod riscv;
1818
mod s390x;
1919
mod sparc;
2020
mod sparc64;
21-
mod wasm32;
22-
mod wasm32_bindgen_compat;
23-
mod wasm64;
21+
mod wasm;
2422
mod x86;
2523
mod x86_64;
2624
mod x86_win64;
@@ -648,12 +646,14 @@ impl<'a, Ty> FnAbi<'a, Ty> {
648646
"nvptx64" => nvptx64::compute_abi_info(self),
649647
"hexagon" => hexagon::compute_abi_info(self),
650648
"riscv32" | "riscv64" => riscv::compute_abi_info(cx, self),
651-
"wasm32" => match cx.target_spec().os.as_str() {
652-
"emscripten" | "wasi" => wasm32::compute_abi_info(cx, self),
653-
_ => wasm32_bindgen_compat::compute_abi_info(self),
654-
},
655-
"asmjs" => wasm32::compute_abi_info(cx, self),
656-
"wasm64" => wasm64::compute_abi_info(cx, self),
649+
"wasm32" | "wasm64" => {
650+
if cx.target_spec().adjust_abi(abi) == spec::abi::Abi::Wasm {
651+
wasm::compute_wasm_abi_info(self)
652+
} else {
653+
wasm::compute_c_abi_info(cx, self)
654+
}
655+
}
656+
"asmjs" => wasm::compute_c_abi_info(cx, self),
657657
a => return Err(format!("unrecognized arch \"{}\" in target specification", a)),
658658
}
659659

compiler/rustc_target/src/abi/call/wasm32.rs renamed to compiler/rustc_target/src/abi/call/wasm.rs

+26-1
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

-29
This file was deleted.

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

-58
This file was deleted.

compiler/rustc_target/src/spec/abi.rs

+9-6
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

compiler/rustc_target/src/spec/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,9 @@ pub struct TargetOptions {
12541254
/// enabled can generated on this target, but the necessary supporting libraries are not
12551255
/// distributed with the target, the sanitizer should still appear in this list for the target.
12561256
pub supported_sanitizers: SanitizerSet,
1257+
1258+
/// If present it's a default value to use for adjusting the C ABI.
1259+
pub default_adjusted_cabi: Option<Abi>,
12571260
}
12581261

12591262
impl Default for TargetOptions {
@@ -1357,6 +1360,7 @@ impl Default for TargetOptions {
13571360
has_thumb_interworking: false,
13581361
split_debuginfo: SplitDebuginfo::Off,
13591362
supported_sanitizers: SanitizerSet::empty(),
1363+
default_adjusted_cabi: None,
13601364
}
13611365
}
13621366
}
@@ -1408,6 +1412,9 @@ impl Target {
14081412
Abi::C { unwind: false }
14091413
}
14101414
}
1415+
1416+
Abi::C { unwind } => self.default_adjusted_cabi.unwrap_or(Abi::C { unwind }),
1417+
14111418
abi => abi,
14121419
}
14131420
}
@@ -1742,6 +1749,16 @@ impl Target {
17421749
}
17431750
}
17441751
} );
1752+
($key_name:ident, Option<Abi>) => ( {
1753+
let name = (stringify!($key_name)).replace("_", "-");
1754+
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
1755+
match lookup_abi(s) {
1756+
Some(abi) => base.$key_name = Some(abi),
1757+
_ => return Some(Err(format!("'{}' is not a valid value for abi", s))),
1758+
}
1759+
Some(Ok(()))
1760+
})).unwrap_or(Ok(()))
1761+
} );
17451762
}
17461763

17471764
if let Some(s) = obj.find("target-endian").and_then(Json::as_string) {
@@ -1841,6 +1858,7 @@ impl Target {
18411858
key!(has_thumb_interworking, bool);
18421859
key!(split_debuginfo, SplitDebuginfo)?;
18431860
key!(supported_sanitizers, SanitizerSet)?;
1861+
key!(default_adjusted_cabi, Option<Abi>)?;
18441862

18451863
// NB: The old name is deprecated, but support for it is retained for
18461864
// compatibility.
@@ -2081,6 +2099,10 @@ impl ToJson for Target {
20812099
target_option_val!(split_debuginfo);
20822100
target_option_val!(supported_sanitizers);
20832101

2102+
if let Some(abi) = self.default_adjusted_cabi {
2103+
d.insert("default-adjusted-cabi".to_string(), Abi::name(abi).to_json());
2104+
}
2105+
20842106
if default.unsupported_abis != self.unsupported_abis {
20852107
d.insert(
20862108
"unsupported-abis".to_string(),

0 commit comments

Comments
 (0)