Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,8 @@ impl AddressSpace {
/// The address space for workgroup memory on nvptx and amdgpu.
/// See e.g. the `gpu_launch_sized_workgroup_mem` intrinsic for details.
pub const GPU_WORKGROUP: Self = AddressSpace(3);
/// The address space LLVM's WebAssembly backend uses for `externref` values.
pub const WASM_EXTERNREF: Self = AddressSpace(10);
}

/// How many scalable vectors are in a `BackendRepr::ScalableVector`?
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_codegen_ssa/src/mir/debuginfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,15 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}
}

// wasm `externref` values cannot be stored to linear memory,
// so spilling one for debuginfo would be unselectable in the
// wasm backend. They exist only as wasm locals; emit no
// memory-based debug location (clang likewise emits no
// location for `__externref_t` variables).
if bx.tcx().is_externref(operand.layout.ty) {
return;
}

Self::spill_operand_to_stack(*operand, name, bx)
}

Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ language_item_table! {
PartialEq, sym::eq, eq_trait, Target::Trait, GenericRequirement::Exact(1);
PartialOrd, sym::partial_ord, partial_ord_trait, Target::Trait, GenericRequirement::Exact(1);
CVoid, sym::c_void, c_void, Target::Enum, GenericRequirement::None;
// An opaque handle to a WebAssembly `externref` value, lowered as a pointer
// in the wasm externref address space.
ExternRef, sym::externref, externref, Target::Struct, GenericRequirement::Exact(0);

Type, sym::type_info, type_struct, Target::Struct, GenericRequirement::None;
TypeId, sym::type_id, type_id, Target::Struct, GenericRequirement::None;
Expand Down
14 changes: 14 additions & 0 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,13 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
check_static_inhabited(tcx, def_id);
check_static_linkage(tcx, def_id);
let ty = tcx.type_of(def_id).instantiate_identity().skip_norm_wip();
if tcx.ty_mentions_externref_illegally(ty, /* allow_bare */ false) {
tcx.dcx().span_err(
tcx.ty_span(def_id),
"wasm `externref` cannot be used in a `static`: it may only appear as a \
bare function parameter, return value or local",
);
}
res = res.and(wfcheck::check_static_item(
tcx, def_id, ty, /* should_check_for_sync */ true,
));
Expand Down Expand Up @@ -947,6 +954,13 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
let ty_span = tcx.ty_span(def_id);
let ty = wfcx.deeply_normalize(ty_span, Some(WellFormedLoc::Ty(def_id)), ty);
wfcx.register_wf_obligation(ty_span, Some(WellFormedLoc::Ty(def_id)), ty.into());
if tcx.ty_mentions_externref_illegally(ty, /* allow_bare */ false) {
tcx.dcx().span_err(
ty_span,
"wasm `externref` cannot be used in a `const`: it may only appear as a \
bare function parameter, return value or local",
);
}
wfcx.register_bound(
traits::ObligationCause::new(
ty_span,
Expand Down
28 changes: 28 additions & 0 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,17 @@ pub(crate) fn check_type_defn<'tcx>(
);
wfcx.register_wf_obligation(span, Some(WellFormedLoc::Ty(field_id)), ty.into());

if tcx.ty_mentions_externref_illegally(ty, /* allow_bare */ false) {
tcx.dcx().span_err(
span,
format!(
"wasm `externref` cannot be used in a {} field: it may only appear \
as a bare function parameter, return value or local",
adt_def.variant_descr()
),
);
}

if matches!(ty.kind(), ty::Adt(def, _) if def.repr().scalable())
&& !matches!(adt_def.repr().scalable, Some(ScalableElt::Container))
{
Expand Down Expand Up @@ -1647,6 +1658,23 @@ fn check_fn_or_method<'tcx>(
);
}

// wasm `externref` may appear bare in parameter/return slots, but never
// inside another type. Impls of `externref` itself (core-only, by
// coherence) are exempt so `Clone` is expressible.
if !tcx.is_externref_impl_item(def_id.to_def_id()) {
for (idx, ty) in sig.inputs_and_output.iter().enumerate() {
if tcx.ty_mentions_externref_illegally(ty, /* allow_bare */ true) {
tcx.dcx().span_err(
arg_span(idx),
format!(
"wasm `externref` cannot be used inside `{ty}`: it may only appear \
as a bare function parameter, return value or local"
),
);
}
}
}

check_where_clauses(wfcx, def_id);

if sig.abi() == ExternAbi::RustCall {
Expand Down
44 changes: 44 additions & 0 deletions compiler/rustc_hir_typeck/src/writeback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ struct WritebackCx<'cx, 'tcx> {
body: &'tcx hir::Body<'tcx>,

rustc_dump_user_args: bool,

/// Whether to enforce wasm `externref` position rules in this body
/// (the lang item exists and this body is not part of `externref`'s own
/// core-only impls).
externref_checks: bool,

/// Spans already reported for externref position errors, to avoid
/// duplicate diagnostics for the same node.
reported_externref: FxHashSet<Span>,
}

impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
Expand All @@ -113,11 +122,16 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
) -> WritebackCx<'cx, 'tcx> {
let owner = body.id().hir_id.owner;

let externref_checks = fcx.tcx.lang_items().externref().is_some()
&& !fcx.tcx.is_externref_impl_item(owner.to_def_id());

let mut wbcx = WritebackCx {
fcx,
typeck_results: ty::TypeckResults::new(owner),
body,
rustc_dump_user_args,
externref_checks,
reported_externref: FxHashSet::default(),
};

// HACK: We specifically don't want the (opaque) error from tainting our
Expand All @@ -134,6 +148,29 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
self.fcx.tcx
}

/// Enforce wasm `externref` position rules: values may only occupy bare
/// value slots (function parameters, return values, locals and function
/// pointer signature slots), never appear inside another type or as a
/// generic argument.
fn check_externref_position(&mut self, span: Span, ty: Ty<'tcx>, allow_bare: bool) {
if !self.externref_checks {
return;
}
if self.tcx().ty_mentions_externref_illegally(ty, allow_bare)
&& self.reported_externref.insert(span)
{
let msg = if allow_bare {
format!(
"wasm `externref` cannot be used inside `{ty}`: it may only appear as a \
bare function parameter, return value or local"
)
} else {
"wasm `externref` cannot be used as a generic argument".to_string()
};
self.tcx().dcx().span_err(span, msg);
}
}

fn write_ty_to_typeck_results(&mut self, hir_id: HirId, ty: Ty<'tcx>) {
debug!("write_ty_to_typeck_results({:?}, {:?})", hir_id, ty);
assert!(
Expand Down Expand Up @@ -675,13 +712,17 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
let n_ty = self.fcx.node_ty(hir_id);
let n_ty = self.resolve(n_ty, &span);
self.write_ty_to_typeck_results(hir_id, n_ty);
self.check_externref_position(span, n_ty, /* allow_bare */ true);
debug!(?n_ty);

// Resolve any generic parameters
if let Some(args) = self.fcx.typeck_results.borrow().node_args_opt(hir_id) {
let args = self.resolve(args, &span);
debug!("write_args_to_tcx({:?}, {:?})", hir_id, args);
assert!(!args.has_infer() && !args.has_placeholders());
for ty in args.types() {
self.check_externref_position(span, ty, /* allow_bare */ false);
}
self.typeck_results.node_args_mut().insert(hir_id, args);
}
}
Expand All @@ -697,6 +738,9 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
Some(adjustment) => {
let resolved_adjustment = self.resolve(adjustment, &span);
debug!(?resolved_adjustment);
for adjust in &resolved_adjustment {
self.check_externref_position(span, adjust.target, /* allow_bare */ true);
}
self.typeck_results.adjustments_mut().insert(hir_id, resolved_adjustment);
}
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_lint/src/types/improper_ctypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,11 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
if def.is_phantom_data() {
return FfiPhantom(ty);
}
// wasm `externref` lowers to a wasm reference type, which is a
// first-class type in the wasm C ABI.
if tcx.is_lang_item(def.did(), hir::LangItem::ExternRef) {
return FfiSafe;
}
match def.adt_kind() {
AdtKind::Struct | AdtKind::Union => {
if let Some(sym::cstring_type | sym::cstr_type) =
Expand Down
76 changes: 75 additions & 1 deletion compiler/rustc_middle/src/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use crate::traits::ObligationCause;
use crate::ty::layout::{FloatExt, IntegerExt};
use crate::ty::{
self, Asyncness, FallibleTypeFolder, GenericArgKind, GenericArgsRef, Ty, TyCtxt, TypeFoldable,
TypeFolder, TypeSuperFoldable, TypeVisitableExt, Unnormalized, Upcast,
TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
TypeVisitor, Unnormalized, Upcast,
};

#[derive(Copy, Clone, Debug)]
Expand Down Expand Up @@ -143,6 +144,79 @@ impl<'tcx> TyCtxt<'tcx> {
})
}

/// Whether `ty` is the wasm `externref` lang type itself.
pub fn is_externref(self, ty: Ty<'tcx>) -> bool {
matches!(ty.kind(), ty::Adt(def, _) if self.is_lang_item(def.did(), hir::LangItem::ExternRef))
}

/// wasm `externref` values exist only in wasm value slots: function
/// parameters, return values and locals. Function pointer signature slots
/// are also value positions, so bare `externref` is permitted there.
///
/// This reports whether `ty` mentions `externref` in any other position,
/// e.g. behind a reference or pointer, as an aggregate member, or as a
/// generic argument. `allow_bare` permits `ty` itself being `externref`
/// (i.e. `ty` occupies a value slot).
pub fn ty_mentions_externref_illegally(self, ty: Ty<'tcx>, allow_bare: bool) -> bool {
use std::ops::ControlFlow;

if self.lang_items().externref().is_none() {
return false;
}
if allow_bare && self.is_externref(ty) {
return false;
}

struct MentionVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
}
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for MentionVisitor<'tcx> {
type Result = ControlFlow<()>;
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<()> {
match t.kind() {
ty::Adt(..) if self.tcx.is_externref(t) => ControlFlow::Break(()),
// Function pointer parameter/return slots are value
// positions: bare `externref` is allowed in them.
ty::FnPtr(sig_tys, _) => {
for io in sig_tys.skip_binder().inputs_and_output {
if !self.tcx.is_externref(io) {
io.visit_with(self)?;
}
}
ControlFlow::Continue(())
}
// Fn items are code, not storage; their signatures are
// value positions and their generic instantiations are
// checked separately as generic arguments.
ty::FnDef(..) => ControlFlow::Continue(()),
_ => t.super_visit_with(self),
}
}
}
ty.visit_with(&mut MentionVisitor { tcx: self }).is_break()
}

/// Whether `def_id` is (an item within) an `impl` of the wasm `externref`
/// lang type. By coherence such impls can only be written in the defining
/// crate (`core`); they are exempt from externref position checks so that
/// `Copy`/`Clone` impls are expressible.
pub fn is_externref_impl_item(self, mut def_id: DefId) -> bool {
if self.lang_items().externref().is_none() {
return false;
}
loop {
if let DefKind::Impl { .. } = self.def_kind(def_id) {
return self.is_externref(
self.type_of(def_id).instantiate_identity().skip_normalization(),
);
}
match self.opt_parent(def_id) {
Some(parent) => def_id = parent,
None => return false,
}
}
}

pub fn res_generics_def_id(self, res: Res) -> Option<DefId> {
match res {
Res::Def(DefKind::Ctor(CtorOf::Variant, _), def_id) => {
Expand Down
Loading
Loading