Auto merge of #57760 - dlrobertson:varargs1, r=alexreg
Support defining C compatible variadic functions ## Summary Add support for defining C compatible variadic functions in unsafe rust with `extern "C"` according to [RFC 2137]. ## Details ### Parsing When parsing a user defined function that is `unsafe` and `extern "C"` allow variadic signatures and inject a "spoofed" `VaList` in the new functions signature. This allows the user to interact with the variadic arguments via a `VaList` instead of manually using `va_start` and `va_end` (See [RFC 2137] for details). ### Codegen When running codegen for a variadic function, remove the "spoofed" `VaList` from the function signature and inject `va_start` when the arg local references are created for the function and `va_end` on return. ## TODO - [x] Get feedback on injecting `va_start/va_end` in MIR vs codegen - [x] Properly inject `va_end` - It seems like it should be possible to inject `va_end` on the `TerminatorKind::Return`. I just need to figure out how to get the `LocalRef` here. - [x] Properly call Rust defined C variadic functions in Rust - The spoofed `VaList` causes problems here. Related to: #44930 r? @ghost [RFC 2137]: https://github.com/rust-lang/rfcs/blob/master/text/2137-variadic.md
This commit is contained in:
commit
1999a22881
78 changed files with 1797 additions and 921 deletions
24
src/doc/unstable-book/src/language-features/c-variadic.md
Normal file
24
src/doc/unstable-book/src/language-features/c-variadic.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# `c_variadic`
|
||||
|
||||
The tracking issue for this feature is: [#44930]
|
||||
|
||||
[#44930]: https://github.com/rust-lang/rust/issues/44930
|
||||
|
||||
------------------------
|
||||
|
||||
The `c_variadic` language feature enables C-variadic functions to be
|
||||
defined in Rust. The may be called both from within Rust and via FFI.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
#![feature(c_variadic)]
|
||||
|
||||
pub unsafe extern "C" fn add(n: usize, mut args: ...) -> usize {
|
||||
let mut sum = 0;
|
||||
for _ in 0..n {
|
||||
sum += args.arg::<usize>();
|
||||
}
|
||||
sum
|
||||
}
|
||||
```
|
26
src/doc/unstable-book/src/library-features/c-variadic.md
Normal file
26
src/doc/unstable-book/src/library-features/c-variadic.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# `c_variadic`
|
||||
|
||||
The tracking issue for this feature is: [#44930]
|
||||
|
||||
[#44930]: https://github.com/rust-lang/rust/issues/44930
|
||||
|
||||
------------------------
|
||||
|
||||
The `c_variadic` library feature exposes the `VaList` structure,
|
||||
Rust's analogue of C's `va_list` type.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
#![feature(c_variadic)]
|
||||
|
||||
use std::ffi::VaList;
|
||||
|
||||
pub unsafe extern "C" fn vadd(n: usize, mut args: VaList) -> usize {
|
||||
let mut sum = 0;
|
||||
for _ in 0..n {
|
||||
sum += args.arg::<usize>();
|
||||
}
|
||||
sum
|
||||
}
|
||||
```
|
|
@ -617,6 +617,9 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty) {
|
|||
TyKind::Typeof(ref expression) => {
|
||||
visitor.visit_anon_const(expression)
|
||||
}
|
||||
TyKind::CVarArgs(ref lt) => {
|
||||
visitor.visit_lifetime(lt)
|
||||
}
|
||||
TyKind::Infer | TyKind::Err => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ const HIR_ID_COUNTER_LOCKED: u32 = 0xFFFFFFFF;
|
|||
pub struct LoweringContext<'a> {
|
||||
crate_root: Option<&'static str>,
|
||||
|
||||
// Used to assign ids to HIR nodes that do not directly correspond to an AST node.
|
||||
/// Used to assign ids to HIR nodes that do not directly correspond to an AST node.
|
||||
sess: &'a Session,
|
||||
|
||||
cstore: &'a dyn CrateStore,
|
||||
|
@ -107,25 +107,25 @@ pub struct LoweringContext<'a> {
|
|||
/// written at all (e.g., `&T` or `std::cell::Ref<T>`).
|
||||
anonymous_lifetime_mode: AnonymousLifetimeMode,
|
||||
|
||||
// Used to create lifetime definitions from in-band lifetime usages.
|
||||
// e.g., `fn foo(x: &'x u8) -> &'x u8` to `fn foo<'x>(x: &'x u8) -> &'x u8`
|
||||
// When a named lifetime is encountered in a function or impl header and
|
||||
// has not been defined
|
||||
// (i.e., it doesn't appear in the in_scope_lifetimes list), it is added
|
||||
// to this list. The results of this list are then added to the list of
|
||||
// lifetime definitions in the corresponding impl or function generics.
|
||||
/// Used to create lifetime definitions from in-band lifetime usages.
|
||||
/// e.g., `fn foo(x: &'x u8) -> &'x u8` to `fn foo<'x>(x: &'x u8) -> &'x u8`
|
||||
/// When a named lifetime is encountered in a function or impl header and
|
||||
/// has not been defined
|
||||
/// (i.e., it doesn't appear in the in_scope_lifetimes list), it is added
|
||||
/// to this list. The results of this list are then added to the list of
|
||||
/// lifetime definitions in the corresponding impl or function generics.
|
||||
lifetimes_to_define: Vec<(Span, ParamName)>,
|
||||
|
||||
// Whether or not in-band lifetimes are being collected. This is used to
|
||||
// indicate whether or not we're in a place where new lifetimes will result
|
||||
// in in-band lifetime definitions, such a function or an impl header,
|
||||
// including implicit lifetimes from `impl_header_lifetime_elision`.
|
||||
/// Whether or not in-band lifetimes are being collected. This is used to
|
||||
/// indicate whether or not we're in a place where new lifetimes will result
|
||||
/// in in-band lifetime definitions, such a function or an impl header,
|
||||
/// including implicit lifetimes from `impl_header_lifetime_elision`.
|
||||
is_collecting_in_band_lifetimes: bool,
|
||||
|
||||
// Currently in-scope lifetimes defined in impl headers, fn headers, or HRTB.
|
||||
// When `is_collectin_in_band_lifetimes` is true, each lifetime is checked
|
||||
// against this list to see if it is already in-scope, or if a definition
|
||||
// needs to be created for it.
|
||||
/// Currently in-scope lifetimes defined in impl headers, fn headers, or HRTB.
|
||||
/// When `is_collectin_in_band_lifetimes` is true, each lifetime is checked
|
||||
/// against this list to see if it is already in-scope, or if a definition
|
||||
/// needs to be created for it.
|
||||
in_scope_lifetimes: Vec<Ident>,
|
||||
|
||||
current_module: NodeId,
|
||||
|
@ -954,7 +954,7 @@ impl<'a> LoweringContext<'a> {
|
|||
let decl = FnDecl {
|
||||
inputs: vec![],
|
||||
output,
|
||||
variadic: false
|
||||
c_variadic: false
|
||||
};
|
||||
let body_id = self.record_body(body_expr, Some(&decl));
|
||||
self.is_generator = prev_is_generator;
|
||||
|
@ -1345,6 +1345,12 @@ impl<'a> LoweringContext<'a> {
|
|||
}
|
||||
}
|
||||
TyKind::Mac(_) => panic!("TyMac should have been expanded by now."),
|
||||
TyKind::CVarArgs => {
|
||||
// Create the implicit lifetime of the "spoofed" `VaList`.
|
||||
let span = self.sess.source_map().next_point(t.span.shrink_to_lo());
|
||||
let lt = self.new_implicit_lifetime(span);
|
||||
hir::TyKind::CVarArgs(lt)
|
||||
},
|
||||
};
|
||||
|
||||
let LoweredNodeId { node_id: _, hir_id } = self.lower_node_id(t.id);
|
||||
|
@ -2112,7 +2118,7 @@ impl<'a> LoweringContext<'a> {
|
|||
P(hir::FnDecl {
|
||||
inputs,
|
||||
output,
|
||||
variadic: decl.variadic,
|
||||
c_variadic: decl.c_variadic,
|
||||
implicit_self: decl.inputs.get(0).map_or(
|
||||
hir::ImplicitSelfKind::None,
|
||||
|arg| {
|
||||
|
@ -3967,7 +3973,7 @@ impl<'a> LoweringContext<'a> {
|
|||
let outer_decl = FnDecl {
|
||||
inputs: decl.inputs.clone(),
|
||||
output: FunctionRetTy::Default(fn_decl_span),
|
||||
variadic: false,
|
||||
c_variadic: false,
|
||||
};
|
||||
// We need to lower the declaration outside the new scope, because we
|
||||
// have to conserve the state of being inside a loop condition for the
|
||||
|
|
|
@ -1829,6 +1829,9 @@ pub enum TyKind {
|
|||
Infer,
|
||||
/// Placeholder for a type that has failed to be defined.
|
||||
Err,
|
||||
/// Placeholder for C-variadic arguments. We "spoof" the `VaList` created
|
||||
/// from the variadic arguments. This type is only valid up to typeck.
|
||||
CVarArgs(Lifetime),
|
||||
}
|
||||
|
||||
#[derive(Clone, RustcEncodable, RustcDecodable, Debug)]
|
||||
|
@ -1865,7 +1868,7 @@ pub struct Arg {
|
|||
pub struct FnDecl {
|
||||
pub inputs: HirVec<Ty>,
|
||||
pub output: FunctionRetTy,
|
||||
pub variadic: bool,
|
||||
pub c_variadic: bool,
|
||||
/// Does the function have an implicit self?
|
||||
pub implicit_self: ImplicitSelfKind,
|
||||
}
|
||||
|
|
|
@ -434,6 +434,9 @@ impl<'a> State<'a> {
|
|||
self.s.word("/*ERROR*/")?;
|
||||
self.pclose()?;
|
||||
}
|
||||
hir::TyKind::CVarArgs(_) => {
|
||||
self.s.word("...")?;
|
||||
}
|
||||
}
|
||||
self.end()
|
||||
}
|
||||
|
@ -2004,7 +2007,7 @@ impl<'a> State<'a> {
|
|||
s.print_type(ty)?;
|
||||
s.end()
|
||||
})?;
|
||||
if decl.variadic {
|
||||
if decl.c_variadic {
|
||||
self.s.word(", ...")?;
|
||||
}
|
||||
self.pclose()?;
|
||||
|
|
|
@ -361,13 +361,14 @@ impl_stable_hash_for!(enum hir::TyKind {
|
|||
TraitObject(trait_refs, lifetime),
|
||||
Typeof(body_id),
|
||||
Err,
|
||||
Infer
|
||||
Infer,
|
||||
CVarArgs(lt),
|
||||
});
|
||||
|
||||
impl_stable_hash_for!(struct hir::FnDecl {
|
||||
inputs,
|
||||
output,
|
||||
variadic,
|
||||
c_variadic,
|
||||
implicit_self
|
||||
});
|
||||
|
||||
|
|
|
@ -232,7 +232,7 @@ impl_stable_hash_for!(struct ty::GenSig<'tcx> {
|
|||
|
||||
impl_stable_hash_for!(struct ty::FnSig<'tcx> {
|
||||
inputs_and_output,
|
||||
variadic,
|
||||
c_variadic,
|
||||
unsafety,
|
||||
abi
|
||||
});
|
||||
|
|
|
@ -764,6 +764,13 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
|
|||
});
|
||||
}
|
||||
}
|
||||
hir::TyKind::CVarArgs(ref lt) => {
|
||||
// Resolve the generated lifetime for the C-variadic arguments.
|
||||
// The lifetime is generated in AST -> HIR lowering.
|
||||
if lt.name.is_elided() {
|
||||
self.resolve_elided_lifetimes(vec![lt])
|
||||
}
|
||||
}
|
||||
_ => intravisit::walk_ty(self, ty),
|
||||
}
|
||||
}
|
||||
|
@ -2225,7 +2232,8 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
|
|||
if let hir::TyKind::BareFn(_) = ty.node {
|
||||
self.outer_index.shift_in(1);
|
||||
}
|
||||
if let hir::TyKind::TraitObject(ref bounds, ref lifetime) = ty.node {
|
||||
match ty.node {
|
||||
hir::TyKind::TraitObject(ref bounds, ref lifetime) => {
|
||||
for bound in bounds {
|
||||
self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None);
|
||||
}
|
||||
|
@ -2235,9 +2243,12 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
|
|||
if !lifetime.is_elided() {
|
||||
self.visit_lifetime(lifetime);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
hir::TyKind::CVarArgs(_) => {}
|
||||
_ => {
|
||||
intravisit::walk_ty(self, ty);
|
||||
}
|
||||
}
|
||||
if let hir::TyKind::BareFn(_) = ty.node {
|
||||
self.outer_index.shift_out(1);
|
||||
}
|
||||
|
|
|
@ -1944,7 +1944,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
|||
if let ty::FnSig {
|
||||
unsafety: hir::Unsafety::Normal,
|
||||
abi: Abi::Rust,
|
||||
variadic: false,
|
||||
c_variadic: false,
|
||||
..
|
||||
} = self_ty.fn_sig(self.tcx()).skip_binder()
|
||||
{
|
||||
|
|
|
@ -2453,7 +2453,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
|
|||
self.mk_fn_sig(
|
||||
params_iter,
|
||||
s.output(),
|
||||
s.variadic,
|
||||
s.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
abi::Abi::Rust,
|
||||
)
|
||||
|
@ -2779,7 +2779,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
|
|||
pub fn mk_fn_sig<I>(self,
|
||||
inputs: I,
|
||||
output: I::Item,
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
unsafety: hir::Unsafety,
|
||||
abi: abi::Abi)
|
||||
-> <I::Item as InternIteratorElement<Ty<'tcx>, ty::FnSig<'tcx>>>::Output
|
||||
|
@ -2788,7 +2788,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
|
|||
{
|
||||
inputs.chain(iter::once(output)).intern_with(|xs| ty::FnSig {
|
||||
inputs_and_output: self.intern_type_list(xs),
|
||||
variadic, unsafety, abi
|
||||
c_variadic, unsafety, abi
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ impl<'a, 'tcx> Instance<'tcx> {
|
|||
sig.map_bound(|sig| tcx.mk_fn_sig(
|
||||
iter::once(*env_ty.skip_binder()).chain(sig.inputs().iter().cloned()),
|
||||
sig.output(),
|
||||
sig.variadic,
|
||||
sig.c_variadic,
|
||||
sig.unsafety,
|
||||
sig.abi
|
||||
))
|
||||
|
|
|
@ -147,9 +147,9 @@ impl<'tcx> Relate<'tcx> for ty::FnSig<'tcx> {
|
|||
{
|
||||
let tcx = relation.tcx();
|
||||
|
||||
if a.variadic != b.variadic {
|
||||
if a.c_variadic != b.c_variadic {
|
||||
return Err(TypeError::VariadicMismatch(
|
||||
expected_found(relation, &a.variadic, &b.variadic)));
|
||||
expected_found(relation, &a.c_variadic, &b.c_variadic)));
|
||||
}
|
||||
let unsafety = relation.relate(&a.unsafety, &b.unsafety)?;
|
||||
let abi = relation.relate(&a.abi, &b.abi)?;
|
||||
|
@ -171,7 +171,7 @@ impl<'tcx> Relate<'tcx> for ty::FnSig<'tcx> {
|
|||
});
|
||||
Ok(ty::FnSig {
|
||||
inputs_and_output: tcx.mk_type_list(inputs_and_output)?,
|
||||
variadic: a.variadic,
|
||||
c_variadic: a.c_variadic,
|
||||
unsafety,
|
||||
abi,
|
||||
})
|
||||
|
|
|
@ -396,7 +396,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::FnSig<'a> {
|
|||
tcx.lift(&self.inputs_and_output).map(|x| {
|
||||
ty::FnSig {
|
||||
inputs_and_output: x,
|
||||
variadic: self.variadic,
|
||||
c_variadic: self.c_variadic,
|
||||
unsafety: self.unsafety,
|
||||
abi: self.abi,
|
||||
}
|
||||
|
@ -832,7 +832,7 @@ BraceStructTypeFoldableImpl! {
|
|||
|
||||
BraceStructTypeFoldableImpl! {
|
||||
impl<'tcx> TypeFoldable<'tcx> for ty::FnSig<'tcx> {
|
||||
inputs_and_output, variadic, unsafety, abi
|
||||
inputs_and_output, c_variadic, unsafety, abi
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -977,13 +977,13 @@ impl<'tcx> PolyGenSig<'tcx> {
|
|||
/// Signature of a function type, which I have arbitrarily
|
||||
/// decided to use to refer to the input/output types.
|
||||
///
|
||||
/// - `inputs` is the list of arguments and their modes.
|
||||
/// - `output` is the return type.
|
||||
/// - `variadic` indicates whether this is a variadic function. (only true for foreign fns)
|
||||
/// - `inputs`: is the list of arguments and their modes.
|
||||
/// - `output`: is the return type.
|
||||
/// - `c_variadic`: indicates whether this is a C-variadic function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
|
||||
pub struct FnSig<'tcx> {
|
||||
pub inputs_and_output: &'tcx List<Ty<'tcx>>,
|
||||
pub variadic: bool,
|
||||
pub c_variadic: bool,
|
||||
pub unsafety: hir::Unsafety,
|
||||
pub abi: abi::Abi,
|
||||
}
|
||||
|
@ -1016,8 +1016,8 @@ impl<'tcx> PolyFnSig<'tcx> {
|
|||
pub fn output(&self) -> ty::Binder<Ty<'tcx>> {
|
||||
self.map_bound_ref(|fn_sig| fn_sig.output())
|
||||
}
|
||||
pub fn variadic(&self) -> bool {
|
||||
self.skip_binder().variadic
|
||||
pub fn c_variadic(&self) -> bool {
|
||||
self.skip_binder().c_variadic
|
||||
}
|
||||
pub fn unsafety(&self) -> hir::Unsafety {
|
||||
self.skip_binder().unsafety
|
||||
|
|
|
@ -360,7 +360,7 @@ impl PrintContext {
|
|||
fn fn_sig<F: fmt::Write>(&mut self,
|
||||
f: &mut F,
|
||||
inputs: &[Ty<'_>],
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
output: Ty<'_>)
|
||||
-> fmt::Result {
|
||||
write!(f, "(")?;
|
||||
|
@ -370,7 +370,7 @@ impl PrintContext {
|
|||
for &ty in inputs {
|
||||
print!(f, self, write(", "), print_display(ty))?;
|
||||
}
|
||||
if variadic {
|
||||
if c_variadic {
|
||||
write!(f, ", ...")?;
|
||||
}
|
||||
}
|
||||
|
@ -1074,10 +1074,10 @@ define_print! {
|
|||
}
|
||||
|
||||
write!(f, "fn")?;
|
||||
cx.fn_sig(f, self.inputs(), self.variadic, self.output())
|
||||
cx.fn_sig(f, self.inputs(), self.c_variadic, self.output())
|
||||
}
|
||||
debug {
|
||||
write!(f, "({:?}; variadic: {})->{:?}", self.inputs(), self.variadic, self.output())
|
||||
write!(f, "({:?}; c_variadic: {})->{:?}", self.inputs(), self.c_variadic, self.output())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,7 +258,7 @@ impl ArgTypeExt<'ll, 'tcx> for ArgType<'tcx, Ty<'tcx>> {
|
|||
val
|
||||
};
|
||||
match self.mode {
|
||||
PassMode::Ignore => {},
|
||||
PassMode::Ignore(_) => {}
|
||||
PassMode::Pair(..) => {
|
||||
OperandValue::Pair(next(), next()).store(bx, dst);
|
||||
}
|
||||
|
@ -422,7 +422,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
|
||||
let mut inputs = sig.inputs();
|
||||
let extra_args = if sig.abi == RustCall {
|
||||
assert!(!sig.variadic && extra_args.is_empty());
|
||||
assert!(!sig.c_variadic && extra_args.is_empty());
|
||||
|
||||
match sig.inputs().last().unwrap().sty {
|
||||
ty::Tuple(ref tupled_arguments) => {
|
||||
|
@ -435,7 +435,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
assert!(sig.variadic || extra_args.is_empty());
|
||||
assert!(sig.c_variadic || extra_args.is_empty());
|
||||
extra_args
|
||||
};
|
||||
|
||||
|
@ -507,6 +507,14 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
}
|
||||
};
|
||||
|
||||
// Store the index of the last argument. This is useful for working with
|
||||
// C-compatible variadic arguments.
|
||||
let last_arg_idx = if sig.inputs().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(sig.inputs().len() - 1)
|
||||
};
|
||||
|
||||
let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| {
|
||||
let is_return = arg_idx.is_none();
|
||||
let mut arg = mk_arg_type(ty, arg_idx);
|
||||
|
@ -516,7 +524,30 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
// The same is true for s390x-unknown-linux-gnu
|
||||
// and sparc64-unknown-linux-gnu.
|
||||
if is_return || rust_abi || (!win_x64_gnu && !linux_s390x && !linux_sparc64) {
|
||||
arg.mode = PassMode::Ignore;
|
||||
arg.mode = PassMode::Ignore(IgnoreMode::Zst);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a C-variadic function, this is not the return value,
|
||||
// and there is one or more fixed arguments; ensure that the `VaList`
|
||||
// is ignored as an argument.
|
||||
if sig.c_variadic {
|
||||
match (last_arg_idx, arg_idx) {
|
||||
(Some(last_idx), Some(cur_idx)) if last_idx == cur_idx => {
|
||||
let va_list_did = match cx.tcx.lang_items().va_list() {
|
||||
Some(did) => did,
|
||||
None => bug!("`va_list` lang item required for C-variadic functions"),
|
||||
};
|
||||
match ty.sty {
|
||||
ty::Adt(def, _) if def.did == va_list_did => {
|
||||
// This is the "spoofed" `VaList`. Set the arguments mode
|
||||
// so that it will be ignored.
|
||||
arg.mode = PassMode::Ignore(IgnoreMode::CVarArgs);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -558,7 +589,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
args: inputs.iter().chain(extra_args).enumerate().map(|(i, ty)| {
|
||||
arg_of(ty, Some(i))
|
||||
}).collect(),
|
||||
variadic: sig.variadic,
|
||||
c_variadic: sig.c_variadic,
|
||||
conv,
|
||||
};
|
||||
fn_ty.adjust_for_abi(cx, sig.abi);
|
||||
|
@ -646,7 +677,9 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
);
|
||||
|
||||
let llreturn_ty = match self.ret.mode {
|
||||
PassMode::Ignore => cx.type_void(),
|
||||
PassMode::Ignore(IgnoreMode::Zst) => cx.type_void(),
|
||||
PassMode::Ignore(IgnoreMode::CVarArgs) =>
|
||||
bug!("`va_list` should never be a return type"),
|
||||
PassMode::Direct(_) | PassMode::Pair(..) => {
|
||||
self.ret.layout.immediate_llvm_type(cx)
|
||||
}
|
||||
|
@ -664,7 +697,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
}
|
||||
|
||||
let llarg_ty = match arg.mode {
|
||||
PassMode::Ignore => continue,
|
||||
PassMode::Ignore(_) => continue,
|
||||
PassMode::Direct(_) => arg.layout.immediate_llvm_type(cx),
|
||||
PassMode::Pair(..) => {
|
||||
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
|
||||
|
@ -684,7 +717,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
llargument_tys.push(llarg_ty);
|
||||
}
|
||||
|
||||
if self.variadic {
|
||||
if self.c_variadic {
|
||||
cx.type_variadic_func(&llargument_tys, llreturn_ty)
|
||||
} else {
|
||||
cx.type_func(&llargument_tys, llreturn_ty)
|
||||
|
@ -733,7 +766,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
apply(&ArgAttributes::new());
|
||||
}
|
||||
match arg.mode {
|
||||
PassMode::Ignore => {}
|
||||
PassMode::Ignore(_) => {}
|
||||
PassMode::Direct(ref attrs) |
|
||||
PassMode::Indirect(ref attrs, None) => apply(attrs),
|
||||
PassMode::Indirect(ref attrs, Some(ref extra_attrs)) => {
|
||||
|
@ -780,7 +813,7 @@ impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
|
|||
apply(&ArgAttributes::new());
|
||||
}
|
||||
match arg.mode {
|
||||
PassMode::Ignore => {}
|
||||
PassMode::Ignore(_) => {}
|
||||
PassMode::Direct(ref attrs) |
|
||||
PassMode::Indirect(ref attrs, None) => apply(attrs),
|
||||
PassMode::Indirect(ref attrs, Some(ref extra_attrs)) => {
|
||||
|
|
|
@ -143,7 +143,7 @@ pub fn push_debuginfo_type_name<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>,
|
|||
output.pop();
|
||||
}
|
||||
|
||||
if sig.variadic {
|
||||
if sig.c_variadic {
|
||||
if !sig.inputs().is_empty() {
|
||||
output.push_str(", ...");
|
||||
} else {
|
||||
|
|
|
@ -136,22 +136,18 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
let tp_ty = substs.type_at(0);
|
||||
self.const_usize(self.size_of(tp_ty).bytes())
|
||||
}
|
||||
func @ "va_start" | func @ "va_end" => {
|
||||
let va_list = match (tcx.lang_items().va_list(), &result.layout.ty.sty) {
|
||||
(Some(did), ty::Adt(def, _)) if def.did == did => args[0].immediate(),
|
||||
(Some(_), _) => self.load(args[0].immediate(),
|
||||
tcx.data_layout.pointer_align.abi),
|
||||
(None, _) => bug!("va_list language item must be defined")
|
||||
};
|
||||
let intrinsic = self.cx().get_intrinsic(&format!("llvm.{}", func));
|
||||
self.call(intrinsic, &[va_list], None)
|
||||
"va_start" => {
|
||||
self.va_start(args[0].immediate())
|
||||
}
|
||||
"va_end" => {
|
||||
self.va_end(args[0].immediate())
|
||||
}
|
||||
"va_copy" => {
|
||||
let va_list = match (tcx.lang_items().va_list(), &result.layout.ty.sty) {
|
||||
(Some(did), ty::Adt(def, _)) if def.did == did => args[0].immediate(),
|
||||
(Some(_), _) => self.load(args[0].immediate(),
|
||||
tcx.data_layout.pointer_align.abi),
|
||||
(None, _) => bug!("va_list language item must be defined")
|
||||
(None, _) => bug!("`va_list` language item must be defined")
|
||||
};
|
||||
let intrinsic = self.cx().get_intrinsic(&("llvm.va_copy"));
|
||||
self.call(intrinsic, &[llresult, va_list], None);
|
||||
|
@ -722,6 +718,41 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
let expect = self.get_intrinsic(&"llvm.expect.i1");
|
||||
self.call(expect, &[cond, self.const_bool(expected)], None)
|
||||
}
|
||||
|
||||
fn va_start(&mut self, list: &'ll Value) -> &'ll Value {
|
||||
let target = &self.cx.tcx.sess.target.target;
|
||||
let arch = &target.arch;
|
||||
// A pointer to the architecture specific structure is passed to this
|
||||
// function. For pointer variants (i686, RISC-V, Windows, etc), we
|
||||
// should do do nothing, as the address to the pointer is needed. For
|
||||
// architectures with a architecture specific structure (`Aarch64`,
|
||||
// `X86_64`, etc), this function should load the structure from the
|
||||
// address provided.
|
||||
let va_list = match &**arch {
|
||||
_ if target.options.is_like_windows => list,
|
||||
"aarch64" if target.target_os == "ios" => list,
|
||||
"aarch64" | "x86_64" | "powerpc" =>
|
||||
self.load(list, self.tcx().data_layout.pointer_align.abi),
|
||||
_ => list,
|
||||
};
|
||||
let intrinsic = self.cx().get_intrinsic("llvm.va_start");
|
||||
self.call(intrinsic, &[va_list], None)
|
||||
}
|
||||
|
||||
fn va_end(&mut self, list: &'ll Value) -> &'ll Value {
|
||||
let target = &self.cx.tcx.sess.target.target;
|
||||
let arch = &target.arch;
|
||||
// See the comment in `va_start` for the purpose of the following.
|
||||
let va_list = match &**arch {
|
||||
_ if target.options.is_like_windows => list,
|
||||
"aarch64" if target.target_os == "ios" => list,
|
||||
"aarch64" | "x86_64" | "powerpc" =>
|
||||
self.load(list, self.tcx().data_layout.pointer_align.abi),
|
||||
_ => list,
|
||||
};
|
||||
let intrinsic = self.cx().get_intrinsic("llvm.va_end");
|
||||
self.call(intrinsic, &[va_list], None)
|
||||
}
|
||||
}
|
||||
|
||||
fn copy_intrinsic(
|
||||
|
|
|
@ -109,12 +109,12 @@ pub(super) fn emit_va_arg(
|
|||
Align::from_bytes(4).unwrap(), true)
|
||||
}
|
||||
// Windows Aarch64
|
||||
("aarch4", true) => {
|
||||
("aarch64", true) => {
|
||||
emit_ptr_va_arg(bx, addr, target_ty, false,
|
||||
Align::from_bytes(8).unwrap(), false)
|
||||
}
|
||||
// iOS Aarch64
|
||||
("aarch4", _) if target.target_os == "ios" => {
|
||||
("aarch64", _) if target.target_os == "ios" => {
|
||||
emit_ptr_va_arg(bx, addr, target_ty, false,
|
||||
Align::from_bytes(8).unwrap(), true)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use rustc::ty::{self, Ty, TypeFoldable};
|
|||
use rustc::ty::layout::{self, LayoutOf, HasTyCtxt};
|
||||
use rustc::mir;
|
||||
use rustc::mir::interpret::EvalErrorKind;
|
||||
use rustc_target::abi::call::{ArgType, FnType, PassMode};
|
||||
use rustc_target::abi::call::{ArgType, FnType, PassMode, IgnoreMode};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use rustc_mir::monomorphize;
|
||||
use crate::base;
|
||||
|
@ -13,136 +13,127 @@ use crate::meth;
|
|||
|
||||
use crate::traits::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use syntax::symbol::Symbol;
|
||||
use syntax_pos::Pos;
|
||||
|
||||
use super::{FunctionCx, LocalRef};
|
||||
use super::place::PlaceRef;
|
||||
use super::operand::OperandRef;
|
||||
use super::operand::{OperandValue, OperandRef};
|
||||
use super::operand::OperandValue::{Pair, Ref, Immediate};
|
||||
|
||||
impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
pub fn codegen_block(
|
||||
&mut self,
|
||||
bb: mir::BasicBlock,
|
||||
) {
|
||||
let mut bx = self.build_block(bb);
|
||||
let data = &self.mir[bb];
|
||||
|
||||
debug!("codegen_block({:?}={:?})", bb, data);
|
||||
|
||||
for statement in &data.statements {
|
||||
bx = self.codegen_statement(bx, statement);
|
||||
/// Used by `FunctionCx::codegen_terminator` for emitting common patterns
|
||||
/// e.g., creating a basic block, calling a function, etc.
|
||||
struct TerminatorCodegenHelper<'a, 'tcx> {
|
||||
bb: &'a mir::BasicBlock,
|
||||
terminator: &'a mir::Terminator<'tcx>,
|
||||
funclet_bb: Option<mir::BasicBlock>,
|
||||
}
|
||||
|
||||
self.codegen_terminator(bx, bb, data.terminator());
|
||||
}
|
||||
|
||||
fn codegen_terminator(
|
||||
&mut self,
|
||||
mut bx: Bx,
|
||||
bb: mir::BasicBlock,
|
||||
terminator: &mir::Terminator<'tcx>
|
||||
) {
|
||||
debug!("codegen_terminator: {:?}", terminator);
|
||||
|
||||
// Create the cleanup bundle, if needed.
|
||||
let tcx = self.cx.tcx();
|
||||
let span = terminator.source_info.span;
|
||||
let funclet_bb = self.cleanup_kinds[bb].funclet_bb(bb);
|
||||
|
||||
// HACK(eddyb) force the right lifetimes, NLL can't figure them out.
|
||||
fn funclet_closure_factory<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
||||
funclet_bb: Option<mir::BasicBlock>
|
||||
) -> impl for<'b> Fn(
|
||||
&'b FunctionCx<'a, 'tcx, Bx>,
|
||||
) -> Option<&'b Bx::Funclet> {
|
||||
move |this| {
|
||||
match funclet_bb {
|
||||
Some(funclet_bb) => this.funclets[funclet_bb].as_ref(),
|
||||
impl<'a, 'tcx> TerminatorCodegenHelper<'a, 'tcx> {
|
||||
/// Returns the associated funclet from `FunctionCx::funclets` for the
|
||||
/// `funclet_bb` member if it is not `None`.
|
||||
fn funclet<'c, 'b, Bx: BuilderMethods<'b, 'tcx>>(
|
||||
&self,
|
||||
fx: &'c mut FunctionCx<'b, 'tcx, Bx>,
|
||||
) -> Option<&'c Bx::Funclet> {
|
||||
match self.funclet_bb {
|
||||
Some(funcl) => fx.funclets[funcl].as_ref(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
let funclet = funclet_closure_factory(funclet_bb);
|
||||
|
||||
let lltarget = |this: &mut Self, target: mir::BasicBlock| {
|
||||
let lltarget = this.blocks[target];
|
||||
let target_funclet = this.cleanup_kinds[target].funclet_bb(target);
|
||||
match (funclet_bb, target_funclet) {
|
||||
fn lltarget<'b, 'c, Bx: BuilderMethods<'b, 'tcx>>(
|
||||
&self,
|
||||
fx: &'c mut FunctionCx<'b, 'tcx, Bx>,
|
||||
target: mir::BasicBlock,
|
||||
) -> (Bx::BasicBlock, bool) {
|
||||
let span = self.terminator.source_info.span;
|
||||
let lltarget = fx.blocks[target];
|
||||
let target_funclet = fx.cleanup_kinds[target].funclet_bb(target);
|
||||
match (self.funclet_bb, target_funclet) {
|
||||
(None, None) => (lltarget, false),
|
||||
(Some(f), Some(t_f))
|
||||
if f == t_f || !base::wants_msvc_seh(tcx.sess)
|
||||
=> (lltarget, false),
|
||||
(None, Some(_)) => {
|
||||
(Some(f), Some(t_f)) if f == t_f || !base::wants_msvc_seh(fx.cx.tcx().sess) =>
|
||||
(lltarget, false),
|
||||
// jump *into* cleanup - need a landing pad if GNU
|
||||
(this.landing_pad_to(target), false)
|
||||
}
|
||||
(Some(_), None) => span_bug!(span, "{:?} - jump out of cleanup?", terminator),
|
||||
(Some(_), Some(_)) => {
|
||||
(this.landing_pad_to(target), true)
|
||||
(None, Some(_)) => (fx.landing_pad_to(target), false),
|
||||
(Some(_), None) => span_bug!(span, "{:?} - jump out of cleanup?", self.terminator),
|
||||
(Some(_), Some(_)) => (fx.landing_pad_to(target), true),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let llblock = |this: &mut Self, target: mir::BasicBlock| {
|
||||
let (lltarget, is_cleanupret) = lltarget(this, target);
|
||||
/// Create a basic block.
|
||||
fn llblock<'c, 'b, Bx: BuilderMethods<'b, 'tcx>>(
|
||||
&self,
|
||||
fx: &'c mut FunctionCx<'b, 'tcx, Bx>,
|
||||
target: mir::BasicBlock,
|
||||
) -> Bx::BasicBlock {
|
||||
let (lltarget, is_cleanupret) = self.lltarget(fx, target);
|
||||
if is_cleanupret {
|
||||
// MSVC cross-funclet jump - need a trampoline
|
||||
|
||||
debug!("llblock: creating cleanup trampoline for {:?}", target);
|
||||
let name = &format!("{:?}_cleanup_trampoline_{:?}", bb, target);
|
||||
let mut trampoline = this.new_block(name);
|
||||
trampoline.cleanup_ret(funclet(this).unwrap(), Some(lltarget));
|
||||
let name = &format!("{:?}_cleanup_trampoline_{:?}", self.bb, target);
|
||||
let mut trampoline = fx.new_block(name);
|
||||
trampoline.cleanup_ret(self.funclet(fx).unwrap(),
|
||||
Some(lltarget));
|
||||
trampoline.llbb()
|
||||
} else {
|
||||
lltarget
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let funclet_br =
|
||||
|this: &mut Self, bx: &mut Bx, target: mir::BasicBlock| {
|
||||
let (lltarget, is_cleanupret) = lltarget(this, target);
|
||||
fn funclet_br<'c, 'b, Bx: BuilderMethods<'b, 'tcx>>(
|
||||
&self,
|
||||
fx: &'c mut FunctionCx<'b, 'tcx, Bx>,
|
||||
bx: &mut Bx,
|
||||
target: mir::BasicBlock,
|
||||
) {
|
||||
let (lltarget, is_cleanupret) = self.lltarget(fx, target);
|
||||
if is_cleanupret {
|
||||
// micro-optimization: generate a `ret` rather than a jump
|
||||
// to a trampoline.
|
||||
bx.cleanup_ret(funclet(this).unwrap(), Some(lltarget));
|
||||
bx.cleanup_ret(self.funclet(fx).unwrap(), Some(lltarget));
|
||||
} else {
|
||||
bx.br(lltarget);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let do_call = |
|
||||
this: &mut Self,
|
||||
/// Call `fn_ptr` of `fn_ty` with the arguments `llargs`, the optional
|
||||
/// return destination `destination` and the cleanup function `cleanup`.
|
||||
fn do_call<'c, 'b, Bx: BuilderMethods<'b, 'tcx>>(
|
||||
&self,
|
||||
fx: &'c mut FunctionCx<'b, 'tcx, Bx>,
|
||||
bx: &mut Bx,
|
||||
fn_ty: FnType<'tcx, Ty<'tcx>>,
|
||||
fn_ptr: Bx::Value,
|
||||
llargs: &[Bx::Value],
|
||||
destination: Option<(ReturnDest<'tcx, Bx::Value>, mir::BasicBlock)>,
|
||||
cleanup: Option<mir::BasicBlock>
|
||||
| {
|
||||
cleanup: Option<mir::BasicBlock>,
|
||||
) {
|
||||
if let Some(cleanup) = cleanup {
|
||||
let ret_bx = if let Some((_, target)) = destination {
|
||||
this.blocks[target]
|
||||
fx.blocks[target]
|
||||
} else {
|
||||
this.unreachable_block()
|
||||
fx.unreachable_block()
|
||||
};
|
||||
let invokeret = bx.invoke(fn_ptr,
|
||||
&llargs,
|
||||
ret_bx,
|
||||
llblock(this, cleanup),
|
||||
funclet(this));
|
||||
self.llblock(fx, cleanup),
|
||||
self.funclet(fx));
|
||||
bx.apply_attrs_callsite(&fn_ty, invokeret);
|
||||
|
||||
if let Some((ret_dest, target)) = destination {
|
||||
let mut ret_bx = this.build_block(target);
|
||||
this.set_debug_loc(&mut ret_bx, terminator.source_info);
|
||||
this.store_return(&mut ret_bx, ret_dest, &fn_ty.ret, invokeret);
|
||||
let mut ret_bx = fx.build_block(target);
|
||||
fx.set_debug_loc(&mut ret_bx, self.terminator.source_info);
|
||||
fx.store_return(&mut ret_bx, ret_dest, &fn_ty.ret, invokeret);
|
||||
}
|
||||
} else {
|
||||
let llret = bx.call(fn_ptr, &llargs, funclet(this));
|
||||
let llret = bx.call(fn_ptr, &llargs, self.funclet(fx));
|
||||
bx.apply_attrs_callsite(&fn_ty, llret);
|
||||
if this.mir[bb].is_cleanup {
|
||||
if fx.mir[*self.bb].is_cleanup {
|
||||
// Cleanup is always the cold path. Don't inline
|
||||
// drop glue. Also, when there is a deeply-nested
|
||||
// struct, there are "symmetry" issues that cause
|
||||
|
@ -151,18 +142,24 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
}
|
||||
|
||||
if let Some((ret_dest, target)) = destination {
|
||||
this.store_return(bx, ret_dest, &fn_ty.ret, llret);
|
||||
funclet_br(this, bx, target);
|
||||
fx.store_return(bx, ret_dest, &fn_ty.ret, llret);
|
||||
self.funclet_br(fx, bx, target);
|
||||
} else {
|
||||
bx.unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
self.set_debug_loc(&mut bx, terminator.source_info);
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Resume => {
|
||||
if let Some(funclet) = funclet(self) {
|
||||
/// Codegen implementations for some terminator variants.
|
||||
impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
/// Generates code for a `Resume` terminator.
|
||||
fn codegen_resume_terminator<'b>(
|
||||
&mut self,
|
||||
helper: TerminatorCodegenHelper<'b, 'tcx>,
|
||||
mut bx: Bx,
|
||||
) {
|
||||
if let Some(funclet) = helper.funclet(self) {
|
||||
bx.cleanup_ret(funclet, None);
|
||||
} else {
|
||||
let slot = self.get_personality_slot(&mut bx);
|
||||
|
@ -178,27 +175,27 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
lp = bx.insert_value(lp, lp1, 1);
|
||||
bx.resume(lp);
|
||||
} else {
|
||||
bx.call(bx.eh_unwind_resume(), &[lp0], funclet(self));
|
||||
bx.call(bx.eh_unwind_resume(), &[lp0],
|
||||
helper.funclet(self));
|
||||
bx.unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Abort => {
|
||||
bx.abort();
|
||||
bx.unreachable();
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Goto { target } => {
|
||||
funclet_br(self, &mut bx, target);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::SwitchInt { ref discr, switch_ty, ref values, ref targets } => {
|
||||
let discr = self.codegen_operand(&mut bx, discr);
|
||||
fn codegen_switchint_terminator<'b>(
|
||||
&mut self,
|
||||
helper: TerminatorCodegenHelper<'b, 'tcx>,
|
||||
mut bx: Bx,
|
||||
discr: &mir::Operand<'tcx>,
|
||||
switch_ty: Ty<'tcx>,
|
||||
values: &Cow<'tcx, [u128]>,
|
||||
targets: &Vec<mir::BasicBlock>,
|
||||
) {
|
||||
let discr = self.codegen_operand(&mut bx, &discr);
|
||||
if targets.len() == 2 {
|
||||
// If there are two targets, emit br instead of switch
|
||||
let lltrue = llblock(self, targets[0]);
|
||||
let llfalse = llblock(self, targets[1]);
|
||||
let lltrue = helper.llblock(self, targets[0]);
|
||||
let llfalse = helper.llblock(self, targets[1]);
|
||||
if switch_ty == bx.tcx().types.bool {
|
||||
// Don't generate trivial icmps when switching on bool
|
||||
if let [0] = values[..] {
|
||||
|
@ -218,26 +215,38 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
} else {
|
||||
let (otherwise, targets) = targets.split_last().unwrap();
|
||||
let switch = bx.switch(discr.immediate(),
|
||||
llblock(self, *otherwise),
|
||||
helper.llblock(self, *otherwise),
|
||||
values.len());
|
||||
let switch_llty = bx.immediate_backend_type(
|
||||
bx.layout_of(switch_ty)
|
||||
);
|
||||
for (&value, target) in values.iter().zip(targets) {
|
||||
let llval = bx.const_uint_big(switch_llty, value);
|
||||
let llbb = llblock(self, *target);
|
||||
let llbb = helper.llblock(self, *target);
|
||||
bx.add_case(switch, llval, llbb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Return => {
|
||||
fn codegen_return_terminator<'b>(
|
||||
&mut self,
|
||||
mut bx: Bx,
|
||||
) {
|
||||
if self.fn_ty.c_variadic {
|
||||
if let Some(va_list) = self.va_list_ref {
|
||||
bx.va_end(va_list.llval);
|
||||
}
|
||||
}
|
||||
let llval = match self.fn_ty.ret.mode {
|
||||
PassMode::Ignore | PassMode::Indirect(..) => {
|
||||
PassMode::Ignore(IgnoreMode::Zst) | PassMode::Indirect(..) => {
|
||||
bx.ret_void();
|
||||
return;
|
||||
}
|
||||
|
||||
PassMode::Ignore(IgnoreMode::CVarArgs) => {
|
||||
bug!("C-variadic arguments should never be the return type");
|
||||
}
|
||||
|
||||
PassMode::Direct(_) | PassMode::Pair(..) => {
|
||||
let op =
|
||||
self.codegen_consume(&mut bx, &mir::Place::Local(mir::RETURN_PLACE));
|
||||
|
@ -282,18 +291,22 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
bx.ret(llval);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Unreachable => {
|
||||
bx.unreachable();
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Drop { ref location, target, unwind } => {
|
||||
fn codegen_drop_terminator<'b>(
|
||||
&mut self,
|
||||
helper: TerminatorCodegenHelper<'b, 'tcx>,
|
||||
mut bx: Bx,
|
||||
location: &mir::Place<'tcx>,
|
||||
target: mir::BasicBlock,
|
||||
unwind: Option<mir::BasicBlock>,
|
||||
) {
|
||||
let ty = location.ty(self.mir, bx.tcx()).to_ty(bx.tcx());
|
||||
let ty = self.monomorphize(&ty);
|
||||
let drop_fn = monomorphize::resolve_drop_in_place(bx.tcx(), ty);
|
||||
|
||||
if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def {
|
||||
// we don't actually need to drop anything.
|
||||
funclet_br(self, &mut bx, target);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -308,8 +321,8 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
};
|
||||
let (drop_fn, fn_ty) = match ty.sty {
|
||||
ty::Dynamic(..) => {
|
||||
let sig = drop_fn.fn_sig(tcx);
|
||||
let sig = tcx.normalize_erasing_late_bound_regions(
|
||||
let sig = drop_fn.fn_sig(self.cx.tcx());
|
||||
let sig = self.cx.tcx().normalize_erasing_late_bound_regions(
|
||||
ty::ParamEnv::reveal_all(),
|
||||
&sig,
|
||||
);
|
||||
|
@ -323,12 +336,23 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
bx.fn_type_of_instance(&drop_fn))
|
||||
}
|
||||
};
|
||||
do_call(self, &mut bx, fn_ty, drop_fn, args,
|
||||
helper.do_call(self, &mut bx, fn_ty, drop_fn, args,
|
||||
Some((ReturnDest::Nothing, target)),
|
||||
unwind);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, cleanup } => {
|
||||
fn codegen_assert_terminator<'b>(
|
||||
&mut self,
|
||||
helper: TerminatorCodegenHelper<'b, 'tcx>,
|
||||
mut bx: Bx,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
cond: &mir::Operand<'tcx>,
|
||||
expected: bool,
|
||||
msg: &mir::AssertMessage<'tcx>,
|
||||
target: mir::BasicBlock,
|
||||
cleanup: Option<mir::BasicBlock>,
|
||||
) {
|
||||
let span = terminator.source_info.span;
|
||||
let cond = self.codegen_operand(&mut bx, cond).immediate();
|
||||
let mut const_cond = bx.const_to_opt_u128(cond, false).map(|c| c == 1);
|
||||
|
||||
|
@ -347,7 +371,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
|
||||
// Don't codegen the panic block if success if known.
|
||||
if const_cond == Some(expected) {
|
||||
funclet_br(self, &mut bx, target);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -355,7 +379,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let cond = bx.expect(cond, expected);
|
||||
|
||||
// Create the failure block and the conditional branch to it.
|
||||
let lltarget = llblock(self, target);
|
||||
let lltarget = helper.llblock(self, target);
|
||||
let panic_block = self.new_block("panic");
|
||||
if expected {
|
||||
bx.cond_br(cond, lltarget, panic_block.llbb());
|
||||
|
@ -373,9 +397,9 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let filename = bx.const_str_slice(filename);
|
||||
let line = bx.const_u32(loc.line as u32);
|
||||
let col = bx.const_u32(loc.col.to_usize() as u32 + 1);
|
||||
let align = tcx.data_layout.aggregate_align.abi
|
||||
.max(tcx.data_layout.i32_align.abi)
|
||||
.max(tcx.data_layout.pointer_align.abi);
|
||||
let align = self.cx.tcx().data_layout.aggregate_align.abi
|
||||
.max(self.cx.tcx().data_layout.i32_align.abi)
|
||||
.max(self.cx.tcx().data_layout.pointer_align.abi);
|
||||
|
||||
// Put together the arguments to the panic entry point.
|
||||
let (lang_item, args) = match *msg {
|
||||
|
@ -417,20 +441,20 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let llfn = bx.get_fn(instance);
|
||||
|
||||
// Codegen the actual panic invoke/call.
|
||||
do_call(self, &mut bx, fn_ty, llfn, &args, None, cleanup);
|
||||
helper.do_call(self, &mut bx, fn_ty, llfn, &args, None, cleanup);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::DropAndReplace { .. } => {
|
||||
bug!("undesugared DropAndReplace in codegen: {:?}", terminator);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Call {
|
||||
ref func,
|
||||
ref args,
|
||||
ref destination,
|
||||
cleanup,
|
||||
from_hir_call: _
|
||||
} => {
|
||||
fn codegen_call_terminator<'b>(
|
||||
&mut self,
|
||||
helper: TerminatorCodegenHelper<'b, 'tcx>,
|
||||
mut bx: Bx,
|
||||
terminator: &mir::Terminator<'tcx>,
|
||||
func: &mir::Operand<'tcx>,
|
||||
args: &Vec<mir::Operand<'tcx>>,
|
||||
destination: &Option<(mir::Place<'tcx>, mir::BasicBlock)>,
|
||||
cleanup: Option<mir::BasicBlock>,
|
||||
) {
|
||||
let span = terminator.source_info.span;
|
||||
// Create the callee. This is a fn ptr or zero-sized and hence a kind of scalar.
|
||||
let callee = self.codegen_operand(&mut bx, func);
|
||||
|
||||
|
@ -445,7 +469,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
ty::FnPtr(_) => {
|
||||
(None, Some(callee.immediate()))
|
||||
}
|
||||
_ => bug!("{} is not callable", callee.layout.ty)
|
||||
_ => bug!("{} is not callable", callee.layout.ty),
|
||||
};
|
||||
let def = instance.map(|i| i.def);
|
||||
let sig = callee.layout.ty.fn_sig(bx.tcx());
|
||||
|
@ -457,8 +481,8 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
|
||||
// Handle intrinsics old codegen wants Expr's for, ourselves.
|
||||
let intrinsic = match def {
|
||||
Some(ty::InstanceDef::Intrinsic(def_id))
|
||||
=> Some(bx.tcx().item_name(def_id).as_str()),
|
||||
Some(ty::InstanceDef::Intrinsic(def_id)) =>
|
||||
Some(bx.tcx().item_name(def_id).as_str()),
|
||||
_ => None
|
||||
};
|
||||
let intrinsic = intrinsic.as_ref().map(|s| &s[..]);
|
||||
|
@ -467,7 +491,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
if let Some(destination_ref) = destination.as_ref() {
|
||||
let &(ref dest, target) = destination_ref;
|
||||
self.codegen_transmute(&mut bx, &args[0], dest);
|
||||
funclet_br(self, &mut bx, target);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
} else {
|
||||
// If we are trying to transmute to an uninhabited type,
|
||||
// it is likely there is no allotted destination. In fact,
|
||||
|
@ -481,7 +505,10 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
return;
|
||||
}
|
||||
|
||||
let extra_args = &args[sig.inputs().len()..];
|
||||
// The "spoofed" `VaList` added to a C-variadic functions signature
|
||||
// should not be included in the `extra_args` calculation.
|
||||
let extra_args_start_idx = sig.inputs().len() - if sig.c_variadic { 1 } else { 0 };
|
||||
let extra_args = &args[extra_args_start_idx..];
|
||||
let extra_args = extra_args.iter().map(|op_arg| {
|
||||
let op_ty = op_arg.ty(self.mir, bx.tcx());
|
||||
self.monomorphize(&op_ty)
|
||||
|
@ -492,15 +519,15 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
bx.new_vtable(sig, &extra_args)
|
||||
}
|
||||
Some(ty::InstanceDef::DropGlue(_, None)) => {
|
||||
// empty drop glue - a nop.
|
||||
// Empty drop glue; a no-op.
|
||||
let &(_, target) = destination.as_ref().unwrap();
|
||||
funclet_br(self, &mut bx, target);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return;
|
||||
}
|
||||
_ => bx.new_fn_type(sig, &extra_args)
|
||||
};
|
||||
|
||||
// emit a panic or a NOP for `panic_if_uninhabited`
|
||||
// Emit a panic or a no-op for `panic_if_uninhabited`.
|
||||
if intrinsic == Some("panic_if_uninhabited") {
|
||||
let ty = instance.unwrap().substs.type_at(0);
|
||||
let layout = bx.layout_of(ty);
|
||||
|
@ -510,9 +537,9 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let filename = bx.const_str_slice(filename);
|
||||
let line = bx.const_u32(loc.line as u32);
|
||||
let col = bx.const_u32(loc.col.to_usize() as u32 + 1);
|
||||
let align = tcx.data_layout.aggregate_align.abi
|
||||
.max(tcx.data_layout.i32_align.abi)
|
||||
.max(tcx.data_layout.pointer_align.abi);
|
||||
let align = self.cx.tcx().data_layout.aggregate_align.abi
|
||||
.max(self.cx.tcx().data_layout.i32_align.abi)
|
||||
.max(self.cx.tcx().data_layout.pointer_align.abi);
|
||||
|
||||
let str = format!(
|
||||
"Attempted to instantiate uninhabited type {}",
|
||||
|
@ -538,7 +565,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let llfn = bx.get_fn(instance);
|
||||
|
||||
// Codegen the actual panic invoke/call.
|
||||
do_call(
|
||||
helper.do_call(
|
||||
self,
|
||||
&mut bx,
|
||||
fn_ty,
|
||||
|
@ -549,7 +576,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
);
|
||||
} else {
|
||||
// a NOP
|
||||
funclet_br(self, &mut bx, destination.as_ref().unwrap().1);
|
||||
helper.funclet_br(self, &mut bx, destination.as_ref().unwrap().1)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -570,13 +597,12 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
if intrinsic.is_some() && intrinsic != Some("drop_in_place") {
|
||||
let dest = match ret_dest {
|
||||
_ if fn_ty.ret.is_indirect() => llargs[0],
|
||||
ReturnDest::Nothing => {
|
||||
bx.const_undef(bx.type_ptr_to(bx.memory_ty(&fn_ty.ret)))
|
||||
}
|
||||
ReturnDest::IndirectOperand(dst, _) |
|
||||
ReturnDest::Store(dst) => dst.llval,
|
||||
ReturnDest::Nothing =>
|
||||
bx.const_undef(bx.type_ptr_to(bx.memory_ty(&fn_ty.ret))),
|
||||
ReturnDest::IndirectOperand(dst, _) | ReturnDest::Store(dst) =>
|
||||
dst.llval,
|
||||
ReturnDest::DirectOperand(_) =>
|
||||
bug!("Cannot use direct operand with an intrinsic call")
|
||||
bug!("Cannot use direct operand with an intrinsic call"),
|
||||
};
|
||||
|
||||
let args: Vec<_> = args.iter().enumerate().map(|(i, arg)| {
|
||||
|
@ -608,7 +634,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
layout: bx.layout_of(ty),
|
||||
};
|
||||
|
||||
},
|
||||
}
|
||||
mir::Operand::Copy(_) |
|
||||
mir::Operand::Move(_) => {
|
||||
span_bug!(span, "shuffle indices must be constant");
|
||||
|
@ -642,7 +668,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
}
|
||||
|
||||
if let Some((_, target)) = *destination {
|
||||
funclet_br(self, &mut bx, target);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
} else {
|
||||
bx.unreachable();
|
||||
}
|
||||
|
@ -658,7 +684,37 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
(&args[..], None)
|
||||
};
|
||||
|
||||
// Useful determining if the current argument is the "spoofed" `VaList`
|
||||
let last_arg_idx = if sig.inputs().is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(sig.inputs().len() - 1)
|
||||
};
|
||||
'make_args: for (i, arg) in first_args.iter().enumerate() {
|
||||
// If this is a C-variadic function the function signature contains
|
||||
// an "spoofed" `VaList`. This argument is ignored, but we need to
|
||||
// populate it with a dummy operand so that the users real arguments
|
||||
// are not overwritten.
|
||||
let i = if sig.c_variadic && last_arg_idx.map(|x| x == i).unwrap_or(false) {
|
||||
let layout = match self.cx.tcx().lang_items().va_list() {
|
||||
Some(did) => bx.cx().layout_of(bx.tcx().type_of(did)),
|
||||
None => bug!("`va_list` language item required for C-variadics"),
|
||||
};
|
||||
let op = OperandRef {
|
||||
val: OperandValue::Immediate(
|
||||
bx.cx().const_undef(bx.cx().immediate_backend_type(layout)
|
||||
)),
|
||||
layout: layout,
|
||||
};
|
||||
self.codegen_argument(&mut bx, op, &mut llargs, &fn_ty.args[i]);
|
||||
if i + 1 < fn_ty.args.len() {
|
||||
i + 1
|
||||
} else {
|
||||
break 'make_args
|
||||
}
|
||||
} else {
|
||||
i
|
||||
};
|
||||
let mut op = self.codegen_operand(&mut bx, arg);
|
||||
|
||||
if let (0, Some(ty::InstanceDef::Virtual(_, idx))) = (i, def) {
|
||||
|
@ -696,7 +752,7 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
llargs.push(data_ptr);
|
||||
continue 'make_args
|
||||
}
|
||||
other => bug!("expected a Pair, got {:?}", other)
|
||||
other => bug!("expected a Pair, got {:?}", other),
|
||||
}
|
||||
} else if let Ref(data_ptr, Some(meta), _) = op.val {
|
||||
// by-value dynamic dispatch
|
||||
|
@ -734,10 +790,96 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
_ => span_bug!(span, "no llfn for call"),
|
||||
};
|
||||
|
||||
do_call(self, &mut bx, fn_ty, fn_ptr, &llargs,
|
||||
helper.do_call(self, &mut bx, fn_ty, fn_ptr, &llargs,
|
||||
destination.as_ref().map(|&(_, target)| (ret_dest, target)),
|
||||
cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
pub fn codegen_block(
|
||||
&mut self,
|
||||
bb: mir::BasicBlock,
|
||||
) {
|
||||
let mut bx = self.build_block(bb);
|
||||
let data = &self.mir[bb];
|
||||
|
||||
debug!("codegen_block({:?}={:?})", bb, data);
|
||||
|
||||
for statement in &data.statements {
|
||||
bx = self.codegen_statement(bx, statement);
|
||||
}
|
||||
|
||||
self.codegen_terminator(bx, bb, data.terminator());
|
||||
}
|
||||
|
||||
fn codegen_terminator(
|
||||
&mut self,
|
||||
mut bx: Bx,
|
||||
bb: mir::BasicBlock,
|
||||
terminator: &mir::Terminator<'tcx>
|
||||
) {
|
||||
debug!("codegen_terminator: {:?}", terminator);
|
||||
|
||||
// Create the cleanup bundle, if needed.
|
||||
let funclet_bb = self.cleanup_kinds[bb].funclet_bb(bb);
|
||||
let helper = TerminatorCodegenHelper {
|
||||
bb: &bb, terminator, funclet_bb
|
||||
};
|
||||
|
||||
self.set_debug_loc(&mut bx, terminator.source_info);
|
||||
match terminator.kind {
|
||||
mir::TerminatorKind::Resume => {
|
||||
self.codegen_resume_terminator(helper, bx)
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Abort => {
|
||||
bx.abort();
|
||||
bx.unreachable();
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Goto { target } => {
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::SwitchInt {
|
||||
ref discr, switch_ty, ref values, ref targets
|
||||
} => {
|
||||
self.codegen_switchint_terminator(helper, bx, discr, switch_ty,
|
||||
values, targets);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Return => {
|
||||
self.codegen_return_terminator(bx);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Unreachable => {
|
||||
bx.unreachable();
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Drop { ref location, target, unwind } => {
|
||||
self.codegen_drop_terminator(helper, bx, location, target, unwind);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Assert { ref cond, expected, ref msg, target, cleanup } => {
|
||||
self.codegen_assert_terminator(helper, bx, terminator, cond,
|
||||
expected, msg, target, cleanup);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::DropAndReplace { .. } => {
|
||||
bug!("undesugared DropAndReplace in codegen: {:?}", terminator);
|
||||
}
|
||||
|
||||
mir::TerminatorKind::Call {
|
||||
ref func,
|
||||
ref args,
|
||||
ref destination,
|
||||
cleanup,
|
||||
from_hir_call: _
|
||||
} => {
|
||||
self.codegen_call_terminator(helper, bx, terminator, func,
|
||||
args, destination, cleanup);
|
||||
}
|
||||
mir::TerminatorKind::GeneratorDrop |
|
||||
mir::TerminatorKind::Yield { .. } => bug!("generator ops in codegen"),
|
||||
mir::TerminatorKind::FalseEdges { .. } |
|
||||
|
|
|
@ -5,7 +5,7 @@ use rustc::mir::{self, Mir};
|
|||
use rustc::ty::subst::SubstsRef;
|
||||
use rustc::session::config::DebugInfo;
|
||||
use rustc_mir::monomorphize::Instance;
|
||||
use rustc_target::abi::call::{FnType, PassMode};
|
||||
use rustc_target::abi::call::{FnType, PassMode, IgnoreMode};
|
||||
use crate::base;
|
||||
use crate::debuginfo::{self, VariableAccess, VariableKind, FunctionDebugContext};
|
||||
use crate::traits::*;
|
||||
|
@ -86,6 +86,10 @@ pub struct FunctionCx<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> {
|
|||
|
||||
/// If this function is being monomorphized, this contains the type substitutions used.
|
||||
param_substs: SubstsRef<'tcx>,
|
||||
|
||||
/// If this function is a C-variadic function, this contains the `PlaceRef` of the
|
||||
/// "spoofed" `VaList`.
|
||||
va_list_ref: Option<PlaceRef<'tcx, Bx::Value>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||
|
@ -246,13 +250,18 @@ pub fn codegen_mir<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
assert!(!instance.substs.needs_infer());
|
||||
instance.substs
|
||||
},
|
||||
va_list_ref: None,
|
||||
};
|
||||
|
||||
let memory_locals = analyze::non_ssa_locals(&fx);
|
||||
|
||||
// Allocate variable and temp allocas
|
||||
fx.locals = {
|
||||
let args = arg_local_refs(&mut bx, &fx, &fx.scopes, &memory_locals);
|
||||
// FIXME(dlrobertson): This is ugly. Find a better way of getting the `PlaceRef` or
|
||||
// `LocalRef` from `arg_local_refs`
|
||||
let mut va_list_ref = None;
|
||||
let args = arg_local_refs(&mut bx, &fx, &fx.scopes, &memory_locals, &mut va_list_ref);
|
||||
fx.va_list_ref = va_list_ref;
|
||||
|
||||
let mut allocate_local = |local| {
|
||||
let decl = &mir.local_decls[local];
|
||||
|
@ -433,6 +442,7 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
debuginfo::MirDebugScope<Bx::DIScope>
|
||||
>,
|
||||
memory_locals: &BitSet<mir::Local>,
|
||||
va_list_ref: &mut Option<PlaceRef<'tcx, Bx::Value>>,
|
||||
) -> Vec<LocalRef<'tcx, Bx::Value>> {
|
||||
let mir = fx.mir;
|
||||
let tcx = fx.cx.tcx();
|
||||
|
@ -447,6 +457,15 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
None
|
||||
};
|
||||
|
||||
// Store the index of the last argument. This is used to
|
||||
// call va_start on the va_list instead of attempting
|
||||
// to store_fn_arg.
|
||||
let last_arg_idx = if fx.fn_ty.args.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(fx.fn_ty.args.len() - 1)
|
||||
};
|
||||
|
||||
mir.args_iter().enumerate().map(|(arg_index, local)| {
|
||||
let arg_decl = &mir.local_decls[local];
|
||||
|
||||
|
@ -510,9 +529,16 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
// of putting everything in allocas just so we can use llvm.dbg.declare.
|
||||
let local = |op| LocalRef::Operand(Some(op));
|
||||
match arg.mode {
|
||||
PassMode::Ignore => {
|
||||
PassMode::Ignore(IgnoreMode::Zst) => {
|
||||
return local(OperandRef::new_zst(bx.cx(), arg.layout));
|
||||
}
|
||||
PassMode::Ignore(IgnoreMode::CVarArgs) => {
|
||||
let backend_type = bx.cx().immediate_backend_type(arg.layout);
|
||||
return local(OperandRef {
|
||||
val: OperandValue::Immediate(bx.cx().const_undef(backend_type)),
|
||||
layout: arg.layout,
|
||||
});
|
||||
}
|
||||
PassMode::Direct(_) => {
|
||||
let llarg = bx.get_param(bx.llfn(), llarg_idx as c_uint);
|
||||
bx.set_value_name(llarg, &name);
|
||||
|
@ -558,10 +584,36 @@ fn arg_local_refs<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
let tmp = PlaceRef::alloca_unsized_indirect(bx, arg.layout, &name);
|
||||
indirect_operand.store(bx, tmp);
|
||||
tmp
|
||||
} else {
|
||||
if fx.fn_ty.c_variadic && last_arg_idx.map(|idx| arg_index == idx).unwrap_or(false) {
|
||||
let va_list_impl = match arg_decl.ty.ty_adt_def() {
|
||||
Some(adt) => adt.non_enum_variant(),
|
||||
None => bug!("`va_list` language item improperly constructed")
|
||||
};
|
||||
match tcx.type_of(va_list_impl.fields[0].did).sty {
|
||||
ty::Ref(_, ty, _) => {
|
||||
// If the underlying structure the `VaList` contains is a structure,
|
||||
// we need to allocate it (e.g., X86_64 on Linux).
|
||||
let tmp = PlaceRef::alloca(bx, arg.layout, &name);
|
||||
if let ty::Adt(..) = ty.sty {
|
||||
let layout = bx.layout_of(ty);
|
||||
// Create an unnamed allocation for the backing structure
|
||||
// and store it in the the spoofed `VaList`.
|
||||
let backing = PlaceRef::alloca(bx, layout, "");
|
||||
bx.store(backing.llval, tmp.llval, layout.align.abi);
|
||||
}
|
||||
// Call `va_start` on the spoofed `VaList`.
|
||||
bx.va_start(tmp.llval);
|
||||
*va_list_ref = Some(tmp);
|
||||
tmp
|
||||
}
|
||||
_ => bug!("improperly constructed `va_list` lang item"),
|
||||
}
|
||||
} else {
|
||||
let tmp = PlaceRef::alloca(bx, arg.layout, &name);
|
||||
bx.store_fn_arg(arg, &mut llarg_idx, tmp);
|
||||
tmp
|
||||
}
|
||||
};
|
||||
arg_scope.map(|scope| {
|
||||
// Is this a regular argument?
|
||||
|
|
|
@ -20,4 +20,10 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
|
|||
fn abort(&mut self);
|
||||
fn assume(&mut self, val: Self::Value);
|
||||
fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value;
|
||||
/// Trait method used to inject `va_start` on the "spoofed" `VaList` in
|
||||
/// Rust defined C-variadic functions.
|
||||
fn va_start(&mut self, val: Self::Value) -> Self::Value;
|
||||
/// Trait method used to inject `va_end` on the "spoofed" `VaList` before
|
||||
/// Rust defined C-variadic functions return.
|
||||
fn va_end(&mut self, val: Self::Value) -> Self::Value;
|
||||
}
|
||||
|
|
|
@ -766,8 +766,15 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
|
|||
let def_id = self.cx.tcx.hir().local_def_id(id);
|
||||
let sig = self.cx.tcx.fn_sig(def_id);
|
||||
let sig = self.cx.tcx.erase_late_bound_regions(&sig);
|
||||
let inputs = if sig.c_variadic {
|
||||
// Don't include the spoofed `VaList` in the functions list
|
||||
// of inputs.
|
||||
&sig.inputs()[..sig.inputs().len() - 1]
|
||||
} else {
|
||||
&sig.inputs()[..]
|
||||
};
|
||||
|
||||
for (input_ty, input_hir) in sig.inputs().iter().zip(&decl.inputs) {
|
||||
for (input_ty, input_hir) in inputs.iter().zip(&decl.inputs) {
|
||||
self.check_type_for_ffi_and_report_errors(input_hir.span, input_ty);
|
||||
}
|
||||
|
||||
|
|
|
@ -1602,10 +1602,17 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
|
|||
from_hir_call: bool,
|
||||
) {
|
||||
debug!("check_call_inputs({:?}, {:?})", sig, args);
|
||||
if args.len() < sig.inputs().len() || (args.len() > sig.inputs().len() && !sig.variadic) {
|
||||
// Do not count the `VaList` argument as a "true" argument to
|
||||
// a C-variadic function.
|
||||
let inputs = if sig.c_variadic {
|
||||
&sig.inputs()[..sig.inputs().len() - 1]
|
||||
} else {
|
||||
&sig.inputs()[..]
|
||||
};
|
||||
if args.len() < inputs.len() || (args.len() > inputs.len() && !sig.c_variadic) {
|
||||
span_mirbug!(self, term, "call to {:?} with wrong # of args", sig);
|
||||
}
|
||||
for (n, (fn_arg, op_arg)) in sig.inputs().iter().zip(args).enumerate() {
|
||||
for (n, (fn_arg, op_arg)) in inputs.iter().zip(args).enumerate() {
|
||||
let op_arg_ty = op_arg.ty(mir, self.tcx());
|
||||
let category = if from_hir_call {
|
||||
ConstraintCategory::CallArgument
|
||||
|
|
|
@ -353,7 +353,7 @@ impl<'a, 'tcx> DefPathBasedNames<'a, 'tcx> {
|
|||
output.pop();
|
||||
}
|
||||
|
||||
if sig.variadic {
|
||||
if sig.c_variadic {
|
||||
if !sig.inputs().is_empty() {
|
||||
output.push_str(", ...");
|
||||
} else {
|
||||
|
|
|
@ -190,6 +190,7 @@ impl Sig for ast::Ty {
|
|||
Ok(replace_text(nested, text))
|
||||
}
|
||||
ast::TyKind::Never => Ok(text_sig("!".to_owned())),
|
||||
ast::TyKind::CVarArgs => Ok(text_sig("...".to_owned())),
|
||||
ast::TyKind::Tup(ref ts) => {
|
||||
let mut text = "(".to_owned();
|
||||
let mut defs = vec![];
|
||||
|
|
|
@ -99,7 +99,7 @@ pub fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnType<'a, Ty>)
|
|||
// `extern "aapcs"`, then we must use the VFP registers for homogeneous aggregates.
|
||||
let vfp = cx.target_spec().llvm_target.ends_with("hf")
|
||||
&& fty.conv != Conv::ArmAapcs
|
||||
&& !fty.variadic;
|
||||
&& !fty.c_variadic;
|
||||
|
||||
if !fty.ret.is_ignore() {
|
||||
classify_ret_ty(cx, &mut fty.ret, vfp);
|
||||
|
|
|
@ -23,10 +23,18 @@ mod x86_64;
|
|||
mod x86_win64;
|
||||
mod wasm32;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum IgnoreMode {
|
||||
/// C-variadic arguments.
|
||||
CVarArgs,
|
||||
/// A zero-sized type.
|
||||
Zst,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum PassMode {
|
||||
/// Ignore the argument (useful for empty struct).
|
||||
Ignore,
|
||||
/// Ignore the argument (useful for empty structs and C-variadic args).
|
||||
Ignore(IgnoreMode),
|
||||
/// Pass the argument directly.
|
||||
Direct(ArgAttributes),
|
||||
/// Pass a pair's elements directly in two arguments.
|
||||
|
@ -481,7 +489,10 @@ impl<'a, Ty> ArgType<'a, Ty> {
|
|||
}
|
||||
|
||||
pub fn is_ignore(&self) -> bool {
|
||||
self.mode == PassMode::Ignore
|
||||
match self.mode {
|
||||
PassMode::Ignore(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -520,7 +531,7 @@ pub struct FnType<'a, Ty> {
|
|||
/// LLVM return type.
|
||||
pub ret: ArgType<'a, Ty>,
|
||||
|
||||
pub variadic: bool,
|
||||
pub c_variadic: bool,
|
||||
|
||||
pub conv: Conv,
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ pub fn compute_abi_info<'a, Ty, C>(cx: &C, fty: &mut FnType<'a, Ty>, flavor: Fla
|
|||
|
||||
for arg in &mut fty.args {
|
||||
let attrs = match arg.mode {
|
||||
PassMode::Ignore |
|
||||
PassMode::Ignore(_) |
|
||||
PassMode::Indirect(_, None) => continue,
|
||||
PassMode::Direct(ref mut attrs) => attrs,
|
||||
PassMode::Pair(..) |
|
||||
|
|
|
@ -105,7 +105,7 @@ fn assemble_builtin_sized_impls<'tcx>(
|
|||
let fn_ptr = generic_types::fn_ptr(
|
||||
tcx,
|
||||
fn_ptr.inputs_and_output.len(),
|
||||
fn_ptr.variadic,
|
||||
fn_ptr.c_variadic,
|
||||
fn_ptr.unsafety,
|
||||
fn_ptr.abi
|
||||
);
|
||||
|
@ -190,11 +190,11 @@ fn wf_clause_for_raw_ptr<'tcx>(
|
|||
fn wf_clause_for_fn_ptr<'tcx>(
|
||||
tcx: ty::TyCtxt<'_, '_, 'tcx>,
|
||||
arity_and_output: usize,
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
unsafety: hir::Unsafety,
|
||||
abi: abi::Abi
|
||||
) -> Clauses<'tcx> {
|
||||
let fn_ptr = generic_types::fn_ptr(tcx, arity_and_output, variadic, unsafety, abi);
|
||||
let fn_ptr = generic_types::fn_ptr(tcx, arity_and_output, c_variadic, unsafety, abi);
|
||||
|
||||
let wf_clause = ProgramClause {
|
||||
goal: DomainGoal::WellFormed(WellFormed::Ty(fn_ptr)),
|
||||
|
@ -503,7 +503,7 @@ impl ChalkInferenceContext<'cx, 'gcx, 'tcx> {
|
|||
wf_clause_for_fn_ptr(
|
||||
self.infcx.tcx,
|
||||
fn_ptr.inputs_and_output.len(),
|
||||
fn_ptr.variadic,
|
||||
fn_ptr.c_variadic,
|
||||
fn_ptr.unsafety,
|
||||
fn_ptr.abi
|
||||
)
|
||||
|
|
|
@ -24,7 +24,7 @@ crate fn raw_ptr(tcx: TyCtxt<'_, '_, 'tcx>, mutbl: hir::Mutability) -> Ty<'tcx>
|
|||
crate fn fn_ptr(
|
||||
tcx: ty::TyCtxt<'_, '_, 'tcx>,
|
||||
arity_and_output: usize,
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
unsafety: hir::Unsafety,
|
||||
abi: abi::Abi
|
||||
) -> Ty<'tcx> {
|
||||
|
@ -37,7 +37,7 @@ crate fn fn_ptr(
|
|||
|
||||
let fn_sig = ty::Binder::bind(ty::FnSig {
|
||||
inputs_and_output,
|
||||
variadic,
|
||||
c_variadic,
|
||||
unsafety,
|
||||
abi,
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ use rustc::ty::subst::{Kind, Subst, InternalSubsts, SubstsRef};
|
|||
use rustc::ty::wf::object_region_bounds;
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_target::spec::abi;
|
||||
use crate::require_c_abi_if_variadic;
|
||||
use crate::require_c_abi_if_c_variadic;
|
||||
use smallvec::SmallVec;
|
||||
use syntax::ast;
|
||||
use syntax::feature_gate::{GateIssue, emit_feature_err};
|
||||
|
@ -1769,7 +1769,7 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
|
|||
tcx.mk_tup(fields.iter().map(|t| self.ast_ty_to_ty(&t)))
|
||||
}
|
||||
hir::TyKind::BareFn(ref bf) => {
|
||||
require_c_abi_if_variadic(tcx, &bf.decl, bf.abi, ast_ty.span);
|
||||
require_c_abi_if_c_variadic(tcx, &bf.decl, bf.abi, ast_ty.span);
|
||||
tcx.mk_fn_ptr(self.ty_of_fn(bf.unsafety, bf.abi, &bf.decl))
|
||||
}
|
||||
hir::TyKind::TraitObject(ref bounds, ref lifetime) => {
|
||||
|
@ -1823,6 +1823,15 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
|
|||
hir::TyKind::Err => {
|
||||
tcx.types.err
|
||||
}
|
||||
hir::TyKind::CVarArgs(lt) => {
|
||||
let va_list_did = match tcx.lang_items().va_list() {
|
||||
Some(did) => did,
|
||||
None => span_bug!(ast_ty.span,
|
||||
"`va_list` lang item required for variadics"),
|
||||
};
|
||||
let region = self.ast_region_to_region(<, None);
|
||||
tcx.type_of(va_list_did).subst(tcx, &[region.into()])
|
||||
}
|
||||
};
|
||||
|
||||
self.record_ty(ast_ty.hir_id, result_ty, ast_ty.span);
|
||||
|
@ -1905,7 +1914,7 @@ impl<'o, 'gcx: 'tcx, 'tcx> dyn AstConv<'gcx, 'tcx> + 'o {
|
|||
let bare_fn_ty = ty::Binder::bind(tcx.mk_fn_sig(
|
||||
input_tys,
|
||||
output_ty,
|
||||
decl.variadic,
|
||||
decl.c_variadic,
|
||||
unsafety,
|
||||
abi
|
||||
));
|
||||
|
|
|
@ -368,20 +368,30 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
.0;
|
||||
let fn_sig = self.normalize_associated_types_in(call_expr.span, &fn_sig);
|
||||
|
||||
let inputs = if fn_sig.c_variadic {
|
||||
if fn_sig.inputs().len() > 1 {
|
||||
&fn_sig.inputs()[..fn_sig.inputs().len() - 1]
|
||||
} else {
|
||||
span_bug!(call_expr.span,
|
||||
"C-variadic functions are only valid with one or more fixed arguments");
|
||||
}
|
||||
} else {
|
||||
&fn_sig.inputs()[..]
|
||||
};
|
||||
// Call the generic checker.
|
||||
let expected_arg_tys = self.expected_inputs_for_expected_output(
|
||||
call_expr.span,
|
||||
expected,
|
||||
fn_sig.output(),
|
||||
fn_sig.inputs(),
|
||||
inputs,
|
||||
);
|
||||
self.check_argument_types(
|
||||
call_expr.span,
|
||||
call_expr.span,
|
||||
fn_sig.inputs(),
|
||||
inputs,
|
||||
&expected_arg_tys[..],
|
||||
arg_exprs,
|
||||
fn_sig.variadic,
|
||||
fn_sig.c_variadic,
|
||||
TupleArgumentsFlag::DontTupleArguments,
|
||||
def_span,
|
||||
);
|
||||
|
@ -414,7 +424,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
fn_sig.inputs(),
|
||||
&expected_arg_tys,
|
||||
arg_exprs,
|
||||
fn_sig.variadic,
|
||||
fn_sig.c_variadic,
|
||||
TupleArgumentsFlag::TupleArguments,
|
||||
None,
|
||||
);
|
||||
|
|
|
@ -141,7 +141,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
self.tcx.mk_fn_sig(
|
||||
iter::once(self.tcx.intern_tup(sig.inputs())),
|
||||
sig.output(),
|
||||
sig.variadic,
|
||||
sig.c_variadic,
|
||||
sig.unsafety,
|
||||
sig.abi,
|
||||
)
|
||||
|
@ -386,7 +386,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
// Watch out for some surprises and just ignore the
|
||||
// expectation if things don't see to match up with what we
|
||||
// expect.
|
||||
if expected_sig.sig.variadic != decl.variadic {
|
||||
if expected_sig.sig.c_variadic != decl.c_variadic {
|
||||
return self.sig_of_closure_no_expectation(expr_def_id, decl, body);
|
||||
} else if expected_sig.sig.inputs_and_output.len() != decl.inputs.len() + 1 {
|
||||
return self.sig_of_closure_with_mismatched_number_of_arguments(
|
||||
|
@ -404,7 +404,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
let bound_sig = ty::Binder::bind(self.tcx.mk_fn_sig(
|
||||
expected_sig.sig.inputs().iter().cloned(),
|
||||
expected_sig.sig.output(),
|
||||
decl.variadic,
|
||||
decl.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
));
|
||||
|
@ -586,7 +586,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
let result = ty::Binder::bind(self.tcx.mk_fn_sig(
|
||||
supplied_arguments,
|
||||
supplied_return,
|
||||
decl.variadic,
|
||||
decl.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
));
|
||||
|
@ -621,7 +621,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
let result = ty::Binder::bind(self.tcx.mk_fn_sig(
|
||||
supplied_arguments,
|
||||
self.tcx.types.err,
|
||||
decl.variadic,
|
||||
decl.c_variadic,
|
||||
hir::Unsafety::Normal,
|
||||
Abi::RustCall,
|
||||
));
|
||||
|
|
|
@ -337,7 +337,7 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|||
"va_start" | "va_end" => {
|
||||
match mk_va_list_ty() {
|
||||
Some(va_list_ty) => (0, vec![va_list_ty], tcx.mk_unit()),
|
||||
None => bug!("va_list lang_item must be defined to use va_list intrinsics")
|
||||
None => bug!("`va_list` language item needed for C-variadic intrinsics")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,14 +364,14 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
|||
};
|
||||
(0, vec![tcx.mk_imm_ref(tcx.mk_region(env_region), va_list_ty)], ret_ty)
|
||||
}
|
||||
None => bug!("va_list lang_item must be defined to use va_list intrinsics")
|
||||
None => bug!("`va_list` language item needed for C-variadic intrinsics")
|
||||
}
|
||||
}
|
||||
|
||||
"va_arg" => {
|
||||
match mk_va_list_ty() {
|
||||
Some(va_list_ty) => (1, vec![va_list_ty], param(0)),
|
||||
None => bug!("va_list lang_item must be defined to use va_list intrinsics")
|
||||
None => bug!("`va_list` language item needed for C-variadic intrinsics")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ use std::mem::replace;
|
|||
use std::ops::{self, Deref};
|
||||
use std::slice;
|
||||
|
||||
use crate::require_c_abi_if_variadic;
|
||||
use crate::require_c_abi_if_c_variadic;
|
||||
use crate::session::{CompileIncomplete, Session};
|
||||
use crate::session::config::EntryFnType;
|
||||
use crate::TypeAndSubsts;
|
||||
|
@ -1072,7 +1072,7 @@ fn check_fn<'a, 'gcx, 'tcx>(inherited: &'a Inherited<'a, 'gcx, 'tcx>,
|
|||
fn_sig = fcx.tcx.mk_fn_sig(
|
||||
fn_sig.inputs().iter().cloned(),
|
||||
revealed_ret_ty,
|
||||
fn_sig.variadic,
|
||||
fn_sig.c_variadic,
|
||||
fn_sig.unsafety,
|
||||
fn_sig.abi
|
||||
);
|
||||
|
@ -1426,7 +1426,7 @@ pub fn check_item_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, it: &'tcx hir::Ite
|
|||
}
|
||||
|
||||
if let hir::ForeignItemKind::Fn(ref fn_decl, _, _) = item.node {
|
||||
require_c_abi_if_variadic(tcx, fn_decl, m.abi, item.span);
|
||||
require_c_abi_if_c_variadic(tcx, fn_decl, m.abi, item.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2783,7 +2783,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
&method.sig.inputs()[1..]
|
||||
);
|
||||
self.check_argument_types(sp, expr_sp, &method.sig.inputs()[1..], &expected_arg_tys[..],
|
||||
args_no_rcvr, method.sig.variadic, tuple_arguments,
|
||||
args_no_rcvr, method.sig.c_variadic, tuple_arguments,
|
||||
self.tcx.hir().span_if_local(method.def_id));
|
||||
method.sig.output()
|
||||
}
|
||||
|
@ -2862,7 +2862,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
fn_inputs: &[Ty<'tcx>],
|
||||
mut expected_arg_tys: &[Ty<'tcx>],
|
||||
args: &'gcx [hir::Expr],
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
tuple_arguments: TupleArgumentsFlag,
|
||||
def_span: Option<Span>) {
|
||||
let tcx = self.tcx;
|
||||
|
@ -2886,11 +2886,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
let param_count_error = |expected_count: usize,
|
||||
arg_count: usize,
|
||||
error_code: &str,
|
||||
variadic: bool,
|
||||
c_variadic: bool,
|
||||
sugg_unit: bool| {
|
||||
let mut err = tcx.sess.struct_span_err_with_code(sp,
|
||||
&format!("this function takes {}{} but {} {} supplied",
|
||||
if variadic {"at least "} else {""},
|
||||
if c_variadic { "at least " } else { "" },
|
||||
potentially_plural_count(expected_count, "parameter"),
|
||||
potentially_plural_count(arg_count, "parameter"),
|
||||
if arg_count == 1 {"was"} else {"were"}),
|
||||
|
@ -2910,7 +2910,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
Applicability::MachineApplicable);
|
||||
} else {
|
||||
err.span_label(sp, format!("expected {}{}",
|
||||
if variadic {"at least "} else {""},
|
||||
if c_variadic { "at least " } else { "" },
|
||||
potentially_plural_count(expected_count, "parameter")));
|
||||
}
|
||||
err.emit();
|
||||
|
@ -2944,7 +2944,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
}
|
||||
} else if expected_arg_count == supplied_arg_count {
|
||||
fn_inputs.to_vec()
|
||||
} else if variadic {
|
||||
} else if c_variadic {
|
||||
if supplied_arg_count >= expected_arg_count {
|
||||
fn_inputs.to_vec()
|
||||
} else {
|
||||
|
@ -2991,10 +2991,10 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
self.select_obligations_where_possible(false);
|
||||
}
|
||||
|
||||
// For variadic functions, we don't have a declared type for all of
|
||||
// For C-variadic functions, we don't have a declared type for all of
|
||||
// the arguments hence we only do our usual type checking with
|
||||
// the arguments who's types we do know.
|
||||
let t = if variadic {
|
||||
let t = if c_variadic {
|
||||
expected_arg_count
|
||||
} else if tuple_arguments == TupleArguments {
|
||||
args.len()
|
||||
|
@ -3043,7 +3043,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
|
|||
|
||||
// We also need to make sure we at least write the ty of the other
|
||||
// arguments which we skipped above.
|
||||
if variadic {
|
||||
if c_variadic {
|
||||
fn variadic_error<'tcx>(s: &Session, span: Span, t: Ty<'tcx>, cast_ty: &str) {
|
||||
use crate::structured_errors::{VariadicError, StructuredDiagnostic};
|
||||
VariadicError::new(s, span, t, cast_ty).diagnostic().emit();
|
||||
|
|
|
@ -136,14 +136,14 @@ fn check_type_alias_enum_variants_enabled<'a, 'gcx, 'tcx>(tcx: TyCtxt<'a, 'gcx,
|
|||
}
|
||||
}
|
||||
|
||||
fn require_c_abi_if_variadic(tcx: TyCtxt<'_, '_, '_>,
|
||||
fn require_c_abi_if_c_variadic(tcx: TyCtxt<'_, '_, '_>,
|
||||
decl: &hir::FnDecl,
|
||||
abi: Abi,
|
||||
span: Span) {
|
||||
if decl.variadic && !(abi == Abi::C || abi == Abi::Cdecl) {
|
||||
if decl.c_variadic && !(abi == Abi::C || abi == Abi::Cdecl) {
|
||||
let mut err = struct_span_err!(tcx.sess, span, E0045,
|
||||
"variadic function must have C or cdecl calling convention");
|
||||
err.span_label(span, "variadics require C or cdecl calling convention").emit();
|
||||
"C-variadic function must have C or cdecl calling convention");
|
||||
err.span_label(span, "C-variadics require C or cdecl calling convention").emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1752,7 +1752,6 @@ impl Clean<Item> for doctree::Function {
|
|||
pub struct FnDecl {
|
||||
pub inputs: Arguments,
|
||||
pub output: FunctionRetTy,
|
||||
pub variadic: bool,
|
||||
pub attrs: Attributes,
|
||||
}
|
||||
|
||||
|
@ -1831,7 +1830,6 @@ impl<'a, A: Copy> Clean<FnDecl> for (&'a hir::FnDecl, A)
|
|||
FnDecl {
|
||||
inputs: (&self.0.inputs[..], self.1).clean(cx),
|
||||
output: self.0.output.clean(cx),
|
||||
variadic: self.0.variadic,
|
||||
attrs: Attributes::default()
|
||||
}
|
||||
}
|
||||
|
@ -1849,7 +1847,6 @@ impl<'a, 'tcx> Clean<FnDecl> for (DefId, ty::PolyFnSig<'tcx>) {
|
|||
FnDecl {
|
||||
output: Return(sig.skip_binder().output().clean(cx)),
|
||||
attrs: Attributes::default(),
|
||||
variadic: sig.skip_binder().variadic,
|
||||
inputs: Arguments {
|
||||
values: sig.skip_binder().inputs().iter().map(|t| {
|
||||
Argument {
|
||||
|
@ -2252,6 +2249,7 @@ pub enum Type {
|
|||
Slice(Box<Type>),
|
||||
Array(Box<Type>, String),
|
||||
Never,
|
||||
CVarArgs,
|
||||
Unique(Box<Type>),
|
||||
RawPointer(Mutability, Box<Type>),
|
||||
BorrowedRef {
|
||||
|
@ -2290,6 +2288,7 @@ pub enum PrimitiveType {
|
|||
Reference,
|
||||
Fn,
|
||||
Never,
|
||||
CVarArgs,
|
||||
}
|
||||
|
||||
#[derive(Clone, RustcEncodable, RustcDecodable, Copy, Debug)]
|
||||
|
@ -2469,6 +2468,7 @@ impl PrimitiveType {
|
|||
Reference => "reference",
|
||||
Fn => "fn",
|
||||
Never => "never",
|
||||
CVarArgs => "...",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2518,6 +2518,7 @@ impl Clean<Type> for hir::Ty {
|
|||
|
||||
match self.node {
|
||||
TyKind::Never => Never,
|
||||
TyKind::CVarArgs(_) => CVarArgs,
|
||||
TyKind::Ptr(ref m) => RawPointer(m.mutbl.clean(cx), box m.ty.clean(cx)),
|
||||
TyKind::Rptr(ref l, ref m) => {
|
||||
let lifetime = if l.is_elided() {
|
||||
|
@ -3654,6 +3655,7 @@ fn build_deref_target_impls(cx: &DocContext<'_, '_, '_>,
|
|||
Reference => None,
|
||||
Fn => None,
|
||||
Never => None,
|
||||
CVarArgs => tcx.lang_items().va_list(),
|
||||
};
|
||||
if let Some(did) = did {
|
||||
if !did.is_local() {
|
||||
|
|
|
@ -609,6 +609,7 @@ fn fmt_type(t: &clean::Type, f: &mut fmt::Formatter<'_>, use_absolute: bool) ->
|
|||
primitive_link(f, PrimitiveType::Array, &format!("; {}]", n))
|
||||
}
|
||||
clean::Never => primitive_link(f, PrimitiveType::Never, "!"),
|
||||
clean::CVarArgs => primitive_link(f, PrimitiveType::CVarArgs, "..."),
|
||||
clean::RawPointer(m, ref t) => {
|
||||
match **t {
|
||||
clean::Generic(_) | clean::ResolvedPath {is_generic: true, ..} => {
|
||||
|
@ -834,13 +835,6 @@ impl fmt::Display for clean::FunctionRetTy {
|
|||
|
||||
impl fmt::Display for clean::FnDecl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.variadic {
|
||||
if f.alternate() {
|
||||
write!(f, "({args:#}, ...){arrow:#}", args = self.inputs, arrow = self.output)
|
||||
} else {
|
||||
write!(f, "({args}, ...){arrow}", args = self.inputs, arrow = self.output)
|
||||
}
|
||||
} else {
|
||||
if f.alternate() {
|
||||
write!(f, "({args:#}){arrow:#}", args = self.inputs, arrow = self.output)
|
||||
} else {
|
||||
|
@ -848,7 +842,6 @@ impl fmt::Display for clean::FnDecl {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for Function<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -907,12 +900,7 @@ impl<'a> fmt::Display for Function<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut args_plain = format!("({})", args_plain);
|
||||
|
||||
if decl.variadic {
|
||||
args.push_str(",<br> ...");
|
||||
args_plain.push_str(", ...");
|
||||
}
|
||||
let args_plain = format!("({})", args_plain);
|
||||
|
||||
let output = if let hir::IsAsync::Async = asyncness {
|
||||
Cow::Owned(decl.sugared_async_return_type())
|
||||
|
|
|
@ -1643,6 +1643,8 @@ pub enum TyKind {
|
|||
Mac(Mac),
|
||||
/// Placeholder for a kind that has failed to be defined.
|
||||
Err,
|
||||
/// Placeholder for a `va_list`.
|
||||
CVarArgs,
|
||||
}
|
||||
|
||||
impl TyKind {
|
||||
|
@ -1802,7 +1804,7 @@ impl Arg {
|
|||
pub struct FnDecl {
|
||||
pub inputs: Vec<Arg>,
|
||||
pub output: FunctionRetTy,
|
||||
pub variadic: bool,
|
||||
pub c_variadic: bool,
|
||||
}
|
||||
|
||||
impl FnDecl {
|
||||
|
|
|
@ -985,7 +985,7 @@ impl<'a> AstBuilder for ExtCtxt<'a> {
|
|||
P(ast::FnDecl {
|
||||
inputs,
|
||||
output,
|
||||
variadic: false
|
||||
c_variadic: false
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -471,6 +471,9 @@ declare_features! (
|
|||
|
||||
// #[repr(align(X))] on enums
|
||||
(active, repr_align_enum, "1.34.0", Some(57996), None),
|
||||
|
||||
// Allows the use of C-variadics
|
||||
(active, c_variadic, "1.34.0", Some(44930), None),
|
||||
);
|
||||
|
||||
declare_features! (
|
||||
|
@ -1901,6 +1904,11 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
|
|||
if header.asyncness.node.is_async() {
|
||||
gate_feature_post!(&self, async_await, span, "async fn is unstable");
|
||||
}
|
||||
|
||||
if fn_decl.c_variadic {
|
||||
gate_feature_post!(&self, c_variadic, span,
|
||||
"C-varaidic functions are unstable");
|
||||
}
|
||||
// Stability of const fn methods are covered in
|
||||
// `visit_trait_item` and `visit_impl_item` below; this is
|
||||
// because default methods don't pass through this point.
|
||||
|
@ -1929,6 +1937,10 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
|
|||
if block.is_none() {
|
||||
self.check_abi(sig.header.abi, ti.span);
|
||||
}
|
||||
if sig.decl.c_variadic {
|
||||
gate_feature_post!(&self, c_variadic, ti.span,
|
||||
"C-varaidic functions are unstable");
|
||||
}
|
||||
if sig.header.constness.node == ast::Constness::Const {
|
||||
gate_feature_post!(&self, const_fn, ti.span, "const fn is unstable");
|
||||
}
|
||||
|
|
|
@ -409,7 +409,8 @@ pub fn noop_visit_ty<T: MutVisitor>(ty: &mut P<Ty>, vis: &mut T) {
|
|||
let Ty { id, node, span } = ty.deref_mut();
|
||||
vis.visit_id(id);
|
||||
match node {
|
||||
TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err | TyKind::Never => {}
|
||||
TyKind::Infer | TyKind::ImplicitSelf | TyKind::Err |
|
||||
TyKind::Never | TyKind::CVarArgs => {}
|
||||
TyKind::Slice(ty) => vis.visit_ty(ty),
|
||||
TyKind::Ptr(mt) => vis.visit_mt(mt),
|
||||
TyKind::Rptr(lt, mt) => {
|
||||
|
@ -680,7 +681,7 @@ pub fn noop_visit_asyncness<T: MutVisitor>(asyncness: &mut IsAsync, vis: &mut T)
|
|||
}
|
||||
|
||||
pub fn noop_visit_fn_decl<T: MutVisitor>(decl: &mut P<FnDecl>, vis: &mut T) {
|
||||
let FnDecl { inputs, output, variadic: _ } = decl.deref_mut();
|
||||
let FnDecl { inputs, output, c_variadic: _ } = decl.deref_mut();
|
||||
visit_vec(inputs, |input| vis.visit_arg(input));
|
||||
match output {
|
||||
FunctionRetTy::Default(span) => vis.visit_span(span),
|
||||
|
|
|
@ -1457,12 +1457,12 @@ impl<'a> Parser<'a> {
|
|||
};
|
||||
|
||||
self.expect_keyword(keywords::Fn)?;
|
||||
let (inputs, variadic) = self.parse_fn_args(false, true)?;
|
||||
let (inputs, c_variadic) = self.parse_fn_args(false, true)?;
|
||||
let ret_ty = self.parse_ret_ty(false)?;
|
||||
let decl = P(FnDecl {
|
||||
inputs,
|
||||
output: ret_ty,
|
||||
variadic,
|
||||
c_variadic,
|
||||
});
|
||||
Ok(TyKind::BareFn(P(BareFnTy {
|
||||
abi,
|
||||
|
@ -1543,7 +1543,7 @@ impl<'a> Parser<'a> {
|
|||
// definition...
|
||||
|
||||
// We don't allow argument names to be left off in edition 2018.
|
||||
p.parse_arg_general(p.span.rust_2018(), true)
|
||||
p.parse_arg_general(p.span.rust_2018(), true, false)
|
||||
})?;
|
||||
generics.where_clause = self.parse_where_clause()?;
|
||||
|
||||
|
@ -1613,7 +1613,7 @@ impl<'a> Parser<'a> {
|
|||
/// Parses an optional return type `[ -> TY ]` in a function declaration.
|
||||
fn parse_ret_ty(&mut self, allow_plus: bool) -> PResult<'a, FunctionRetTy> {
|
||||
if self.eat(&token::RArrow) {
|
||||
Ok(FunctionRetTy::Ty(self.parse_ty_common(allow_plus, true)?))
|
||||
Ok(FunctionRetTy::Ty(self.parse_ty_common(allow_plus, true, false)?))
|
||||
} else {
|
||||
Ok(FunctionRetTy::Default(self.span.shrink_to_lo()))
|
||||
}
|
||||
|
@ -1621,7 +1621,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
/// Parses a type.
|
||||
pub fn parse_ty(&mut self) -> PResult<'a, P<Ty>> {
|
||||
self.parse_ty_common(true, true)
|
||||
self.parse_ty_common(true, true, false)
|
||||
}
|
||||
|
||||
/// Parses a type in restricted contexts where `+` is not permitted.
|
||||
|
@ -1631,11 +1631,11 @@ impl<'a> Parser<'a> {
|
|||
/// Example 2: `value1 as TYPE + value2`
|
||||
/// `+` is prohibited to avoid interactions with expression grammar.
|
||||
fn parse_ty_no_plus(&mut self) -> PResult<'a, P<Ty>> {
|
||||
self.parse_ty_common(false, true)
|
||||
self.parse_ty_common(false, true, false)
|
||||
}
|
||||
|
||||
fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool)
|
||||
-> PResult<'a, P<Ty>> {
|
||||
fn parse_ty_common(&mut self, allow_plus: bool, allow_qpath_recovery: bool,
|
||||
allow_c_variadic: bool) -> PResult<'a, P<Ty>> {
|
||||
maybe_whole!(self, NtTy, |x| x);
|
||||
|
||||
let lo = self.span;
|
||||
|
@ -1772,6 +1772,15 @@ impl<'a> Parser<'a> {
|
|||
TyKind::Path(None, path)
|
||||
}
|
||||
}
|
||||
} else if self.check(&token::DotDotDot) {
|
||||
if allow_c_variadic {
|
||||
self.eat(&token::DotDotDot);
|
||||
TyKind::CVarArgs
|
||||
} else {
|
||||
return Err(self.fatal(
|
||||
"only foreign functions are allowed to be C-variadic"
|
||||
));
|
||||
}
|
||||
} else {
|
||||
let msg = format!("expected type, found {}", self.this_token_descr());
|
||||
return Err(self.fatal(&msg));
|
||||
|
@ -1959,7 +1968,8 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
/// This version of parse arg doesn't necessarily require identifier names.
|
||||
fn parse_arg_general(&mut self, require_name: bool, is_trait_item: bool) -> PResult<'a, Arg> {
|
||||
fn parse_arg_general(&mut self, require_name: bool, is_trait_item: bool,
|
||||
allow_c_variadic: bool) -> PResult<'a, Arg> {
|
||||
maybe_whole!(self, NtArg, |x| x);
|
||||
|
||||
if let Ok(Some(_)) = self.parse_self_arg() {
|
||||
|
@ -2008,12 +2018,12 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
|
||||
self.eat_incorrect_doc_comment("a method argument's type");
|
||||
(pat, self.parse_ty()?)
|
||||
(pat, self.parse_ty_common(true, true, allow_c_variadic)?)
|
||||
} else {
|
||||
debug!("parse_arg_general ident_to_pat");
|
||||
let parser_snapshot_before_ty = self.clone();
|
||||
self.eat_incorrect_doc_comment("a method argument's type");
|
||||
let mut ty = self.parse_ty();
|
||||
let mut ty = self.parse_ty_common(true, true, allow_c_variadic);
|
||||
if ty.is_ok() && self.token != token::Comma &&
|
||||
self.token != token::CloseDelim(token::Paren) {
|
||||
// This wasn't actually a type, but a pattern looking like a type,
|
||||
|
@ -2032,6 +2042,11 @@ impl<'a> Parser<'a> {
|
|||
(pat, ty)
|
||||
}
|
||||
Err(mut err) => {
|
||||
// If this is a C-variadic argument and we hit an error, return the
|
||||
// error.
|
||||
if self.token == token::DotDotDot {
|
||||
return Err(err);
|
||||
}
|
||||
// Recover from attempting to parse the argument as a type without pattern.
|
||||
err.cancel();
|
||||
mem::replace(self, parser_snapshot_before_ty);
|
||||
|
@ -2068,7 +2083,7 @@ impl<'a> Parser<'a> {
|
|||
|
||||
/// Parses a single function argument.
|
||||
crate fn parse_arg(&mut self) -> PResult<'a, Arg> {
|
||||
self.parse_arg_general(true, false)
|
||||
self.parse_arg_general(true, false, false)
|
||||
}
|
||||
|
||||
/// Parses an argument in a lambda header (e.g., `|arg, arg|`).
|
||||
|
@ -2406,7 +2421,7 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
let span = lo.to(self.prev_span);
|
||||
let output = if self.eat(&token::RArrow) {
|
||||
Some(self.parse_ty_common(false, false)?)
|
||||
Some(self.parse_ty_common(false, false, false)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -6113,58 +6128,52 @@ impl<'a> Parser<'a> {
|
|||
Ok(where_clause)
|
||||
}
|
||||
|
||||
fn parse_fn_args(&mut self, named_args: bool, allow_variadic: bool)
|
||||
fn parse_fn_args(&mut self, named_args: bool, allow_c_variadic: bool)
|
||||
-> PResult<'a, (Vec<Arg> , bool)> {
|
||||
self.expect(&token::OpenDelim(token::Paren))?;
|
||||
|
||||
let sp = self.span;
|
||||
let mut variadic = false;
|
||||
let mut c_variadic = false;
|
||||
let (args, recovered): (Vec<Option<Arg>>, bool) =
|
||||
self.parse_seq_to_before_end(
|
||||
&token::CloseDelim(token::Paren),
|
||||
SeqSep::trailing_allowed(token::Comma),
|
||||
|p| {
|
||||
if p.token == token::DotDotDot {
|
||||
p.bump();
|
||||
variadic = true;
|
||||
if allow_variadic {
|
||||
// If the argument is a C-variadic argument we should not
|
||||
// enforce named arguments.
|
||||
let enforce_named_args = if p.token == token::DotDotDot {
|
||||
false
|
||||
} else {
|
||||
named_args
|
||||
};
|
||||
match p.parse_arg_general(enforce_named_args, false,
|
||||
allow_c_variadic) {
|
||||
Ok(arg) => {
|
||||
if let TyKind::CVarArgs = arg.ty.node {
|
||||
c_variadic = true;
|
||||
if p.token != token::CloseDelim(token::Paren) {
|
||||
let span = p.span;
|
||||
p.span_err(span,
|
||||
"`...` must be last in argument list for variadic function");
|
||||
}
|
||||
"`...` must be the last argument of a C-variadic function");
|
||||
Ok(None)
|
||||
} else {
|
||||
let span = p.prev_span;
|
||||
if p.token == token::CloseDelim(token::Paren) {
|
||||
// continue parsing to present any further errors
|
||||
p.struct_span_err(
|
||||
span,
|
||||
"only foreign functions are allowed to be variadic"
|
||||
).emit();
|
||||
Ok(Some(dummy_arg(span)))
|
||||
} else {
|
||||
// this function definition looks beyond recovery, stop parsing
|
||||
p.span_err(span,
|
||||
"only foreign functions are allowed to be variadic");
|
||||
Ok(None)
|
||||
}
|
||||
Ok(Some(arg))
|
||||
}
|
||||
} else {
|
||||
match p.parse_arg_general(named_args, false) {
|
||||
Ok(arg) => Ok(Some(arg)),
|
||||
Ok(Some(arg))
|
||||
}
|
||||
},
|
||||
Err(mut e) => {
|
||||
e.emit();
|
||||
let lo = p.prev_span;
|
||||
// Skip every token until next possible arg or end.
|
||||
p.eat_to_tokens(&[&token::Comma, &token::CloseDelim(token::Paren)]);
|
||||
// Create a placeholder argument for proper arg count (#34264).
|
||||
// Create a placeholder argument for proper arg count (issue #34264).
|
||||
let span = lo.to(p.prev_span);
|
||||
Ok(Some(dummy_arg(span)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
if !recovered {
|
||||
|
@ -6173,24 +6182,24 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let args: Vec<_> = args.into_iter().filter_map(|x| x).collect();
|
||||
|
||||
if variadic && args.is_empty() {
|
||||
if c_variadic && args.is_empty() {
|
||||
self.span_err(sp,
|
||||
"variadic function must be declared with at least one named argument");
|
||||
"C-variadic function must be declared with at least one named argument");
|
||||
}
|
||||
|
||||
Ok((args, variadic))
|
||||
Ok((args, c_variadic))
|
||||
}
|
||||
|
||||
/// Parses the argument list and result type of a function declaration.
|
||||
fn parse_fn_decl(&mut self, allow_variadic: bool) -> PResult<'a, P<FnDecl>> {
|
||||
fn parse_fn_decl(&mut self, allow_c_variadic: bool) -> PResult<'a, P<FnDecl>> {
|
||||
|
||||
let (args, variadic) = self.parse_fn_args(true, allow_variadic)?;
|
||||
let (args, c_variadic) = self.parse_fn_args(true, allow_c_variadic)?;
|
||||
let ret_ty = self.parse_ret_ty(true)?;
|
||||
|
||||
Ok(P(FnDecl {
|
||||
inputs: args,
|
||||
output: ret_ty,
|
||||
variadic,
|
||||
c_variadic,
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -6337,7 +6346,7 @@ impl<'a> Parser<'a> {
|
|||
Ok(P(FnDecl {
|
||||
inputs: fn_inputs,
|
||||
output: self.parse_ret_ty(true)?,
|
||||
variadic: false
|
||||
c_variadic: false
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -6363,7 +6372,7 @@ impl<'a> Parser<'a> {
|
|||
Ok(P(FnDecl {
|
||||
inputs: inputs_captures,
|
||||
output,
|
||||
variadic: false
|
||||
c_variadic: false
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -6395,7 +6404,8 @@ impl<'a> Parser<'a> {
|
|||
abi: Abi)
|
||||
-> PResult<'a, ItemInfo> {
|
||||
let (ident, mut generics) = self.parse_fn_header()?;
|
||||
let decl = self.parse_fn_decl(false)?;
|
||||
let allow_c_variadic = abi == Abi::C && unsafety == Unsafety::Unsafe;
|
||||
let decl = self.parse_fn_decl(allow_c_variadic)?;
|
||||
generics.where_clause = self.parse_where_clause()?;
|
||||
let (inner_attrs, body) = self.parse_inner_attrs_and_block()?;
|
||||
let header = FnHeader { unsafety, asyncness, constness, abi };
|
||||
|
|
|
@ -1118,6 +1118,9 @@ impl<'a> State<'a> {
|
|||
ast::TyKind::Mac(ref m) => {
|
||||
self.print_mac(m)?;
|
||||
}
|
||||
ast::TyKind::CVarArgs => {
|
||||
self.s.word("...")?;
|
||||
}
|
||||
}
|
||||
self.end()
|
||||
}
|
||||
|
@ -2811,7 +2814,7 @@ impl<'a> State<'a> {
|
|||
-> io::Result<()> {
|
||||
self.popen()?;
|
||||
self.commasep(Inconsistent, &decl.inputs, |s, arg| s.print_arg(arg, false))?;
|
||||
if decl.variadic {
|
||||
if decl.c_variadic {
|
||||
self.s.word(", ...")?;
|
||||
}
|
||||
self.pclose()?;
|
||||
|
@ -3238,7 +3241,7 @@ mod tests {
|
|||
let decl = ast::FnDecl {
|
||||
inputs: Vec::new(),
|
||||
output: ast::FunctionRetTy::Default(syntax_pos::DUMMY_SP),
|
||||
variadic: false
|
||||
c_variadic: false
|
||||
};
|
||||
let generics = ast::Generics::default();
|
||||
assert_eq!(
|
||||
|
|
|
@ -319,7 +319,7 @@ pub fn walk_ty<'a, V: Visitor<'a>>(visitor: &mut V, typ: &'a Ty) {
|
|||
walk_list!(visitor, visit_lifetime, opt_lifetime);
|
||||
visitor.visit_ty(&mutable_type.ty)
|
||||
}
|
||||
TyKind::Never => {},
|
||||
TyKind::Never | TyKind::CVarArgs => {}
|
||||
TyKind::Tup(ref tuple_element_types) => {
|
||||
walk_list!(visitor, visit_ty, tuple_element_types);
|
||||
}
|
||||
|
|
69
src/test/codegen/c-variadic.rs
Normal file
69
src/test/codegen/c-variadic.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
// compile-flags: -C no-prepopulate-passes
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![feature(c_variadic)]
|
||||
#![no_std]
|
||||
use core::ffi::VaList;
|
||||
|
||||
extern "C" {
|
||||
fn foreign_c_variadic_0(_: i32, ...);
|
||||
fn foreign_c_variadic_1(_: VaList, ...);
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn use_foreign_c_variadic_0() {
|
||||
// Ensure that we correctly call foreign C-variadic functions.
|
||||
// CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0)
|
||||
foreign_c_variadic_0(0);
|
||||
// CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42)
|
||||
foreign_c_variadic_0(0, 42i32);
|
||||
// CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42, i32 1024)
|
||||
foreign_c_variadic_0(0, 42i32, 1024i32);
|
||||
// CHECK: invoke void (i32, ...) @foreign_c_variadic_0(i32 0, i32 42, i32 1024, i32 0)
|
||||
foreign_c_variadic_0(0, 42i32, 1024i32, 0i32);
|
||||
}
|
||||
|
||||
// Ensure that we do not remove the `va_list` passed to the foreign function when
|
||||
// removing the "spoofed" `VaList` that is used by Rust defined C-variadics.
|
||||
pub unsafe extern "C" fn use_foreign_c_variadic_1_0(ap: VaList) {
|
||||
// CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap)
|
||||
foreign_c_variadic_1(ap);
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn use_foreign_c_variadic_1_1(ap: VaList) {
|
||||
// CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 42)
|
||||
foreign_c_variadic_1(ap, 42i32);
|
||||
}
|
||||
pub unsafe extern "C" fn use_foreign_c_variadic_1_2(ap: VaList) {
|
||||
// CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 2, i32 42)
|
||||
foreign_c_variadic_1(ap, 2i32, 42i32);
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn use_foreign_c_variadic_1_3(ap: VaList) {
|
||||
// CHECK: invoke void ({{.*}}*, ...) @foreign_c_variadic_1({{.*}} %ap, i32 2, i32 42, i32 0)
|
||||
foreign_c_variadic_1(ap, 2i32, 42i32, 0i32);
|
||||
}
|
||||
|
||||
// Ensure that `va_start` and `va_end` are properly injected.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn c_variadic(n: i32, mut ap: ...) -> i32 {
|
||||
// CHECK: call void @llvm.va_start
|
||||
let mut sum = 0;
|
||||
for _ in 0..n {
|
||||
sum += ap.arg::<i32>();
|
||||
}
|
||||
sum
|
||||
// CHECK: call void @llvm.va_end
|
||||
}
|
||||
|
||||
// Ensure that we generate the correct `call` signature when calling a Rust
|
||||
// defined C-variadic.
|
||||
pub unsafe fn test_c_variadic_call() {
|
||||
// CHECK: call i32 (i32, ...) @c_variadic(i32 0)
|
||||
c_variadic(0);
|
||||
// CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42)
|
||||
c_variadic(0, 42i32);
|
||||
// CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42, i32 1024)
|
||||
c_variadic(0, 42i32, 1024i32);
|
||||
// CHECK: call i32 (i32, ...) @c_variadic(i32 0, i32 42, i32 1024, i32 0)
|
||||
c_variadic(0, 42i32, 1024i32, 0i32);
|
||||
}
|
|
@ -18,8 +18,10 @@ macro_rules! continue_if {
|
|||
|
||||
unsafe fn compare_c_str(ptr: *const c_char, val: &str) -> bool {
|
||||
let cstr0 = CStr::from_ptr(ptr);
|
||||
let cstr1 = CString::new(val).unwrap();
|
||||
&*cstr1 == cstr0
|
||||
match CString::new(val) {
|
||||
Ok(cstr1) => &*cstr1 == cstr0,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
@ -68,3 +70,24 @@ pub unsafe extern "C" fn check_list_copy_0(mut ap: VaList) -> usize {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn check_varargs_0(_: c_int, mut ap: ...) -> usize {
|
||||
continue_if!(ap.arg::<c_int>() == 42);
|
||||
continue_if!(compare_c_str(ap.arg::<*const c_char>(), "Hello, World!"));
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn check_varargs_1(_: c_int, mut ap: ...) -> usize {
|
||||
continue_if!(ap.arg::<c_double>().floor() == 3.14f64.floor());
|
||||
continue_if!(ap.arg::<c_long>() == 12);
|
||||
continue_if!(ap.arg::<c_char>() == 'A' as c_char);
|
||||
continue_if!(ap.arg::<c_longlong>() == 1);
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn check_varargs_2(_: c_int, mut ap: ...) -> usize {
|
||||
0
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ extern size_t check_list_0(va_list ap);
|
|||
extern size_t check_list_1(va_list ap);
|
||||
extern size_t check_list_2(va_list ap);
|
||||
extern size_t check_list_copy_0(va_list ap);
|
||||
extern size_t check_varargs_0(int fixed, ...);
|
||||
extern size_t check_varargs_1(int fixed, ...);
|
||||
extern size_t check_varargs_2(int fixed, ...);
|
||||
|
||||
int test_rust(size_t (*fn)(va_list), ...) {
|
||||
size_t ret = 0;
|
||||
|
@ -26,5 +29,12 @@ int main(int argc, char* argv[]) {
|
|||
assert(test_rust(check_list_2, 3.14, 12l, 'a', 6.28, "Hello", 42, "World") == 0);
|
||||
|
||||
assert(test_rust(check_list_copy_0, 6.28, 16, 'A', "Skip Me!", "Correct") == 0);
|
||||
|
||||
assert(check_varargs_0(0, 42, "Hello, World!") == 0);
|
||||
|
||||
assert(check_varargs_1(0, 3.14, 12l, 'A', 0x1LL) == 0);
|
||||
|
||||
assert(check_varargs_2(0, "All", "of", "these", "are", "ignored", ".") == 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ fn iter_exprs(depth: usize, f: &mut FnMut(P<Expr>)) {
|
|||
let decl = P(FnDecl {
|
||||
inputs: vec![],
|
||||
output: FunctionRetTy::Default(DUMMY_SP),
|
||||
variadic: false,
|
||||
c_variadic: false,
|
||||
});
|
||||
iter_exprs(depth - 1, &mut |e| g(
|
||||
ExprKind::Closure(CaptureBy::Value,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
extern "C" {
|
||||
// @has variadic/fn.foo.html //pre 'pub unsafe extern "C" fn foo(x: i32, ...)'
|
||||
// @has variadic/fn.foo.html //pre 'pub unsafe extern "C" fn foo(x: i32, _: ...)'
|
||||
pub fn foo(x: i32, ...);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
error[E0045]: variadic function must have C or cdecl calling convention
|
||||
--> $DIR/variadic-ffi.rs:5:5
|
||||
error[E0045]: C-variadic function must have C or cdecl calling convention
|
||||
--> $DIR/variadic-ffi-1.rs:5:5
|
||||
|
|
||||
LL | fn printf(_: *const u8, ...); //~ ERROR: variadic function must have C or cdecl calling
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variadics require C or cdecl calling convention
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C-variadics require C or cdecl calling convention
|
||||
|
||||
error: aborting due to previous error
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
error[E0045]: variadic function must have C or cdecl calling convention
|
||||
error[E0045]: C-variadic function must have C or cdecl calling convention
|
||||
--> $DIR/variadic-ffi-2.rs:3:11
|
||||
|
|
||||
LL | fn baz(f: extern "stdcall" fn(usize, ...)) {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variadics require C or cdecl calling convention
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C-variadics require C or cdecl calling convention
|
||||
|
||||
error: aborting due to previous error
|
||||
|
|
@ -14,12 +14,10 @@ fn main() {
|
|||
let x: unsafe extern "C" fn(f: isize, x: u8) = foo;
|
||||
//~^ ERROR: mismatched types
|
||||
//~| expected type `unsafe extern "C" fn(isize, u8)`
|
||||
//~| found type `unsafe extern "C" fn(isize, u8, ...) {foo}`
|
||||
|
||||
let y: extern "C" fn(f: isize, x: u8, ...) = bar;
|
||||
//~^ ERROR: mismatched types
|
||||
//~| expected type `extern "C" fn(isize, u8, ...)`
|
||||
//~| found type `extern "C" fn(isize, u8) {bar}`
|
||||
//~| expected type `for<'r> extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...)`
|
||||
|
||||
foo(1, 2, 3f32); //~ ERROR can't pass `f32` to variadic function
|
||||
foo(1, 2, true); //~ ERROR can't pass `bool` to variadic function
|
|
@ -23,49 +23,49 @@ LL | let x: unsafe extern "C" fn(f: isize, x: u8) = foo;
|
|||
| ^^^ expected non-variadic fn, found variadic function
|
||||
|
|
||||
= note: expected type `unsafe extern "C" fn(isize, u8)`
|
||||
found type `unsafe extern "C" fn(isize, u8, ...) {foo}`
|
||||
found type `for<'r> unsafe extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...) {foo}`
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/variadic-ffi-3.rs:19:54
|
||||
--> $DIR/variadic-ffi-3.rs:18:54
|
||||
|
|
||||
LL | let y: extern "C" fn(f: isize, x: u8, ...) = bar;
|
||||
| ^^^ expected variadic fn, found non-variadic function
|
||||
|
|
||||
= note: expected type `extern "C" fn(isize, u8, ...)`
|
||||
= note: expected type `for<'r> extern "C" fn(isize, u8, std::ffi::VaList<'r>, ...)`
|
||||
found type `extern "C" fn(isize, u8) {bar}`
|
||||
|
||||
error[E0617]: can't pass `f32` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:24:19
|
||||
--> $DIR/variadic-ffi-3.rs:22:19
|
||||
|
|
||||
LL | foo(1, 2, 3f32); //~ ERROR can't pass `f32` to variadic function
|
||||
| ^^^^ help: cast the value to `c_double`: `3f32 as c_double`
|
||||
|
||||
error[E0617]: can't pass `bool` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:25:19
|
||||
--> $DIR/variadic-ffi-3.rs:23:19
|
||||
|
|
||||
LL | foo(1, 2, true); //~ ERROR can't pass `bool` to variadic function
|
||||
| ^^^^ help: cast the value to `c_int`: `true as c_int`
|
||||
|
||||
error[E0617]: can't pass `i8` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:26:19
|
||||
--> $DIR/variadic-ffi-3.rs:24:19
|
||||
|
|
||||
LL | foo(1, 2, 1i8); //~ ERROR can't pass `i8` to variadic function
|
||||
| ^^^ help: cast the value to `c_int`: `1i8 as c_int`
|
||||
|
||||
error[E0617]: can't pass `u8` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:27:19
|
||||
--> $DIR/variadic-ffi-3.rs:25:19
|
||||
|
|
||||
LL | foo(1, 2, 1u8); //~ ERROR can't pass `u8` to variadic function
|
||||
| ^^^ help: cast the value to `c_uint`: `1u8 as c_uint`
|
||||
|
||||
error[E0617]: can't pass `i16` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:28:19
|
||||
--> $DIR/variadic-ffi-3.rs:26:19
|
||||
|
|
||||
LL | foo(1, 2, 1i16); //~ ERROR can't pass `i16` to variadic function
|
||||
| ^^^^ help: cast the value to `c_int`: `1i16 as c_int`
|
||||
|
||||
error[E0617]: can't pass `u16` to variadic function
|
||||
--> $DIR/variadic-ffi-3.rs:29:19
|
||||
--> $DIR/variadic-ffi-3.rs:27:19
|
||||
|
|
||||
LL | foo(1, 2, 1u16); //~ ERROR can't pass `u16` to variadic function
|
||||
| ^^^^ help: cast the value to `c_uint`: `1u16 as c_uint`
|
29
src/test/ui/c-variadic/variadic-ffi-4.rs
Normal file
29
src/test/ui/c-variadic/variadic-ffi-4.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
#![crate_type="lib"]
|
||||
#![no_std]
|
||||
#![feature(c_variadic)]
|
||||
|
||||
use core::ffi::VaList;
|
||||
|
||||
pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
|
||||
ap //~ ERROR: explicit lifetime required
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
|
||||
ap //~ ERROR: explicit lifetime required
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) {
|
||||
let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
*ap0 = ap1; //~ ERROR: mismatched types
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
ap0 = &mut ap1;
|
||||
//~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
//~^^ ERROR: mismatched types
|
||||
//~^^^ ERROR: mismatched types
|
||||
//~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
}
|
198
src/test/ui/c-variadic/variadic-ffi-4.stderr
Normal file
198
src/test/ui/c-variadic/variadic-ffi-4.stderr
Normal file
|
@ -0,0 +1,198 @@
|
|||
error[E0621]: explicit lifetime required in the type of `ap`
|
||||
--> $DIR/variadic-ffi-4.rs:8:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
|
||||
| --- help: add explicit lifetime `'a` to the type of `ap`: `core::ffi::VaList<'a>`
|
||||
LL | ap //~ ERROR: explicit lifetime required
|
||||
| ^^ lifetime `'a` required
|
||||
|
||||
error[E0621]: explicit lifetime required in the type of `ap`
|
||||
--> $DIR/variadic-ffi-4.rs:12:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
|
||||
| --- help: add explicit lifetime `'static` to the type of `ap`: `core::ffi::VaList<'static>`
|
||||
LL | ap //~ ERROR: explicit lifetime required
|
||||
| ^^ lifetime `'static` required
|
||||
|
||||
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
|
||||
--> $DIR/variadic-ffi-4.rs:16:28
|
||||
|
|
||||
LL | let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
|
||||
| ^^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 16:21...
|
||||
--> $DIR/variadic-ffi-4.rs:16:21
|
||||
|
|
||||
LL | let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
|
||||
| ^^^^^^^^^^^
|
||||
= note: ...so that the expression is assignable:
|
||||
expected core::ffi::VaList<'_>
|
||||
found core::ffi::VaList<'_>
|
||||
note: but, the lifetime must be valid for the method call at 16:13...
|
||||
--> $DIR/variadic-ffi-4.rs:16:13
|
||||
|
|
||||
LL | let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
note: ...so type `core::ffi::VaList<'_>` of expression is valid during the expression
|
||||
--> $DIR/variadic-ffi-4.rs:16:13
|
||||
|
|
||||
LL | let _ = ap.copy(|ap| { ap }); //~ ERROR: cannot infer an appropriate lifetime
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/variadic-ffi-4.rs:20:12
|
||||
|
|
||||
LL | *ap0 = ap1; //~ ERROR: mismatched types
|
||||
| ^^^ lifetime mismatch
|
||||
|
|
||||
= note: expected type `core::ffi::VaList<'_>`
|
||||
found type `core::ffi::VaList<'_>`
|
||||
note: the anonymous lifetime #3 defined on the function body at 19:1...
|
||||
--> $DIR/variadic-ffi-4.rs:19:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | *ap0 = ap1; //~ ERROR: mismatched types
|
||||
LL | | }
|
||||
| |_^
|
||||
note: ...does not necessarily outlive the anonymous lifetime #2 defined on the function body at 19:1
|
||||
--> $DIR/variadic-ffi-4.rs:19:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape3(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | *ap0 = ap1; //~ ERROR: mismatched types
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error[E0490]: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
note: the type is valid for the anonymous lifetime #1 defined on the function body at 23:1
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
note: but the borrow lasts for the anonymous lifetime #3 defined on the function body at 23:1
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^ lifetime mismatch
|
||||
|
|
||||
= note: expected type `&mut core::ffi::VaList<'_>`
|
||||
found type `&mut core::ffi::VaList<'_>`
|
||||
note: the anonymous lifetime #3 defined on the function body at 23:1...
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
note: ...does not necessarily outlive the anonymous lifetime #2 defined on the function body at 23:1
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^ lifetime mismatch
|
||||
|
|
||||
= note: expected type `&mut core::ffi::VaList<'_>`
|
||||
found type `&mut core::ffi::VaList<'_>`
|
||||
note: the anonymous lifetime #2 defined on the function body at 23:1...
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
note: ...does not necessarily outlive the anonymous lifetime #3 defined on the function body at 23:1
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
|
||||
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^
|
||||
|
|
||||
note: first, the lifetime cannot outlive the anonymous lifetime #3 defined on the function body at 23:1...
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
note: ...so that the type `core::ffi::VaList<'_>` is not borrowed for too long
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^
|
||||
note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the function body at 23:1...
|
||||
--> $DIR/variadic-ffi-4.rs:23:1
|
||||
|
|
||||
LL | / pub unsafe extern "C" fn no_escape4(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
LL | | ap0 = &mut ap1;
|
||||
LL | | //~^ ERROR: a value of type `core::ffi::VaList<'_>` is borrowed for too long
|
||||
LL | | //~^^ ERROR: mismatched types
|
||||
LL | | //~^^^ ERROR: mismatched types
|
||||
LL | | //~^^^^ ERROR: cannot infer an appropriate lifetime
|
||||
LL | | }
|
||||
| |_^
|
||||
note: ...so that reference does not outlive borrowed content
|
||||
--> $DIR/variadic-ffi-4.rs:24:11
|
||||
|
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^
|
||||
|
||||
error: aborting due to 8 previous errors
|
||||
|
||||
Some errors occurred: E0308, E0490, E0495, E0621.
|
||||
For more information about an error, try `rustc --explain E0308`.
|
31
src/test/ui/c-variadic/variadic-ffi-5.rs
Normal file
31
src/test/ui/c-variadic/variadic-ffi-5.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
#![crate_type="lib"]
|
||||
#![no_std]
|
||||
#![feature(c_variadic)]
|
||||
// The tests in this file are similar to that of variadic-ffi-4, but this
|
||||
// one enables nll.
|
||||
#![feature(nll)]
|
||||
|
||||
use core::ffi::VaList;
|
||||
|
||||
pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
|
||||
ap //~ ERROR: explicit lifetime required
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
|
||||
ap //~ ERROR: explicit lifetime required
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape2(_: usize, ap: ...) {
|
||||
let _ = ap.copy(|ap| { ap }); //~ ERROR: lifetime may not live long enough
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape3(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
*ap0 = ap1; //~ ERROR: lifetime may not live long enough
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
ap0 = &mut ap1;
|
||||
//~^ ERROR: lifetime may not live long enough
|
||||
//~^^ ERROR: lifetime may not live long enough
|
||||
//~^^^ ERROR: `ap1` does not live long enough
|
||||
}
|
73
src/test/ui/c-variadic/variadic-ffi-5.stderr
Normal file
73
src/test/ui/c-variadic/variadic-ffi-5.stderr
Normal file
|
@ -0,0 +1,73 @@
|
|||
error[E0621]: explicit lifetime required in the type of `ap`
|
||||
--> $DIR/variadic-ffi-5.rs:11:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape0<'a>(_: usize, ap: ...) -> VaList<'a> {
|
||||
| --- help: add explicit lifetime `'a` to the type of `ap`: `core::ffi::VaList<'a>`
|
||||
LL | ap //~ ERROR: explicit lifetime required
|
||||
| ^^ lifetime `'a` required
|
||||
|
||||
error[E0621]: explicit lifetime required in the type of `ap`
|
||||
--> $DIR/variadic-ffi-5.rs:15:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape1(_: usize, ap: ...) -> VaList<'static> {
|
||||
| --- help: add explicit lifetime `'static` to the type of `ap`: `core::ffi::VaList<'static>`
|
||||
LL | ap //~ ERROR: explicit lifetime required
|
||||
| ^^ lifetime `'static` required
|
||||
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/variadic-ffi-5.rs:19:28
|
||||
|
|
||||
LL | let _ = ap.copy(|ap| { ap }); //~ ERROR: lifetime may not live long enough
|
||||
| --- ^^ returning this value requires that `'1` must outlive `'2`
|
||||
| | |
|
||||
| | return type of closure is core::ffi::VaList<'2>
|
||||
| has type `core::ffi::VaList<'1>`
|
||||
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/variadic-ffi-5.rs:23:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape3(_: usize, ap0: &mut VaList, mut ap1: ...) {
|
||||
| --- ------- has type `core::ffi::VaList<'1>`
|
||||
| |
|
||||
| has type `&mut core::ffi::VaList<'2>`
|
||||
LL | *ap0 = ap1; //~ ERROR: lifetime may not live long enough
|
||||
| ^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
|
||||
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/variadic-ffi-5.rs:27:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
| ------- ------- has type `core::ffi::VaList<'2>`
|
||||
| |
|
||||
| has type `&mut core::ffi::VaList<'1>`
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
|
||||
|
||||
error: lifetime may not live long enough
|
||||
--> $DIR/variadic-ffi-5.rs:27:5
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
| ------- ------- has type `core::ffi::VaList<'1>`
|
||||
| |
|
||||
| has type `&mut core::ffi::VaList<'2>`
|
||||
LL | ap0 = &mut ap1;
|
||||
| ^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'2`
|
||||
|
||||
error[E0597]: `ap1` does not live long enough
|
||||
--> $DIR/variadic-ffi-5.rs:27:11
|
||||
|
|
||||
LL | pub unsafe extern "C" fn no_escape4(_: usize, mut ap0: &mut VaList, mut ap1: ...) {
|
||||
| - let's call the lifetime of this reference `'1`
|
||||
LL | ap0 = &mut ap1;
|
||||
| ------^^^^^^^^
|
||||
| | |
|
||||
| | borrowed value does not live long enough
|
||||
| assignment requires that `ap1` is borrowed for `'1`
|
||||
...
|
||||
LL | }
|
||||
| - `ap1` dropped here while still borrowed
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
||||
Some errors occurred: E0597, E0621.
|
||||
For more information about an error, try `rustc --explain E0597`.
|
13
src/test/ui/c-variadic/variadic-ffi-6.rs
Normal file
13
src/test/ui/c-variadic/variadic-ffi-6.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#![crate_type="lib"]
|
||||
#![feature(c_variadic)]
|
||||
|
||||
pub unsafe extern "C" fn use_vararg_lifetime(
|
||||
x: usize,
|
||||
y: ...
|
||||
) -> &usize { //~ ERROR missing lifetime specifier
|
||||
&0
|
||||
}
|
||||
|
||||
pub unsafe extern "C" fn use_normal_arg_lifetime(x: &usize, y: ...) -> &usize { // OK
|
||||
x
|
||||
}
|
11
src/test/ui/c-variadic/variadic-ffi-6.stderr
Normal file
11
src/test/ui/c-variadic/variadic-ffi-6.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
error[E0106]: missing lifetime specifier
|
||||
--> $DIR/variadic-ffi-6.rs:7:6
|
||||
|
|
||||
LL | ) -> &usize { //~ ERROR missing lifetime specifier
|
||||
| ^ help: consider giving it an explicit bounded or 'static lifetime: `&'static`
|
||||
|
|
||||
= help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0106`.
|
|
@ -1,8 +1,8 @@
|
|||
error[E0045]: variadic function must have C or cdecl calling convention
|
||||
error[E0045]: C-variadic function must have C or cdecl calling convention
|
||||
--> $DIR/E0045.rs:1:17
|
||||
|
|
||||
LL | extern "Rust" { fn foo(x: u8, ...); } //~ ERROR E0045
|
||||
| ^^^^^^^^^^^^^^^^^^^ variadics require C or cdecl calling convention
|
||||
| ^^^^^^^^^^^^^^^^^^^ C-variadics require C or cdecl calling convention
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ fn main() {
|
|||
//~^ ERROR can't pass `u16` to variadic function
|
||||
//~| HELP cast the value to `c_uint`
|
||||
printf(::std::ptr::null(), printf);
|
||||
//~^ ERROR can't pass `unsafe extern "C" fn(*const i8, ...) {printf}` to variadic function
|
||||
//~| HELP cast the value to `unsafe extern "C" fn(*const i8, ...)`
|
||||
//~^ ERROR can't pass `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...) {printf}` to variadic function
|
||||
//~| HELP cast the value to `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...)`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,15 +28,15 @@ error[E0617]: can't pass `u16` to variadic function
|
|||
LL | printf(::std::ptr::null(), 0u16);
|
||||
| ^^^^ help: cast the value to `c_uint`: `0u16 as c_uint`
|
||||
|
||||
error[E0617]: can't pass `unsafe extern "C" fn(*const i8, ...) {printf}` to variadic function
|
||||
error[E0617]: can't pass `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...) {printf}` to variadic function
|
||||
--> $DIR/E0617.rs:24:36
|
||||
|
|
||||
LL | printf(::std::ptr::null(), printf);
|
||||
| ^^^^^^
|
||||
help: cast the value to `unsafe extern "C" fn(*const i8, ...)`
|
||||
help: cast the value to `for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...)`
|
||||
|
|
||||
LL | printf(::std::ptr::null(), printf as unsafe extern "C" fn(*const i8, ...));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
LL | printf(::std::ptr::null(), printf as for<'r> unsafe extern "C" fn(*const i8, std::ffi::VaList<'r>, ...));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: aborting due to 6 previous errors
|
||||
|
||||
|
|
4
src/test/ui/feature-gate/feature-gate-c_variadic.rs
Normal file
4
src/test/ui/feature-gate/feature-gate-c_variadic.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
#![crate_type="lib"]
|
||||
|
||||
pub unsafe extern "C" fn test(_: i32, ap: ...) { }
|
||||
//~^ C-varaidic functions are unstable
|
11
src/test/ui/feature-gate/feature-gate-c_variadic.stderr
Normal file
11
src/test/ui/feature-gate/feature-gate-c_variadic.stderr
Normal file
|
@ -0,0 +1,11 @@
|
|||
error[E0658]: C-varaidic functions are unstable (see issue #44930)
|
||||
--> $DIR/feature-gate-c_variadic.rs:3:1
|
||||
|
|
||||
LL | pub unsafe extern "C" fn test(_: i32, ap: ...) { }
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: add #![feature(c_variadic)] to the crate attributes to enable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0658`.
|
|
@ -1,3 +1,3 @@
|
|||
extern "C" fn foo(x: u8, ...);
|
||||
//~^ ERROR only foreign functions are allowed to be variadic
|
||||
//~^ ERROR only foreign functions are allowed to be C-variadic
|
||||
//~| ERROR expected one of `->`, `where`, or `{`, found `;`
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: only foreign functions are allowed to be variadic
|
||||
error: only foreign functions are allowed to be C-variadic
|
||||
--> $DIR/invalid-variadic-function.rs:1:26
|
||||
|
|
||||
LL | extern "C" fn foo(x: u8, ...);
|
||||
|
|
|
@ -10,11 +10,11 @@ error: expected one of `!`, `(`, `)`, `+`, `,`, `::`, or `<`, found `{`
|
|||
LL | Nope(i32 {}) //~ ERROR: found `{`
|
||||
| ^ expected one of 7 possible tokens here
|
||||
|
||||
error: expected one of `!`, `&&`, `&`, `(`, `)`, `*`, `+`, `,`, `::`, `<`, `?`, `[`, `_`, `crate`, `dyn`, `extern`, `fn`, `for`, `impl`, `pub`, `unsafe`, `}`, or lifetime, found `{`
|
||||
error: expected one of `!`, `&&`, `&`, `(`, `)`, `*`, `+`, `,`, `...`, `::`, `<`, `?`, `[`, `_`, `crate`, `dyn`, `extern`, `fn`, `for`, `impl`, `pub`, `unsafe`, `}`, or lifetime, found `{`
|
||||
--> $DIR/recover-enum2.rs:27:22
|
||||
|
|
||||
LL | Nope(i32 {}) //~ ERROR: found `{`
|
||||
| ^ expected one of 23 possible tokens here
|
||||
| ^ expected one of 24 possible tokens here
|
||||
|
||||
error: expected expression, found reserved identifier `_`
|
||||
--> $DIR/recover-enum2.rs:32:22
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fn foo(x: isize, ...) {
|
||||
//~^ ERROR: only foreign functions are allowed to be variadic
|
||||
//~^ ERROR: only foreign functions are allowed to be C-variadic
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: only foreign functions are allowed to be variadic
|
||||
error: only foreign functions are allowed to be C-variadic
|
||||
--> $DIR/variadic-ffi-3.rs:1:18
|
||||
|
|
||||
LL | fn foo(x: isize, ...) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
extern "C" fn foo(x: isize, ...) {
|
||||
//~^ ERROR: only foreign functions are allowed to be variadic
|
||||
//~^ ERROR: only foreign functions are allowed to be C-variadic
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
error: only foreign functions are allowed to be variadic
|
||||
error: only foreign functions are allowed to be C-variadic
|
||||
--> $DIR/variadic-ffi-4.rs:1:29
|
||||
|
|
||||
LL | extern "C" fn foo(x: isize, ...) {
|
||||
|
|
|
@ -82,25 +82,27 @@ pub fn check(path: &path::Path, bad: &mut bool) {
|
|||
!lang_features.contains_key(name)
|
||||
}).collect();
|
||||
|
||||
// Library features
|
||||
let unstable_lib_feature_names = collect_unstable_feature_names(&lib_features);
|
||||
let unstable_book_lib_features_section_file_names =
|
||||
collect_unstable_book_lib_features_section_file_names(path);
|
||||
|
||||
// Check for Unstable Book sections that don't have a corresponding unstable feature
|
||||
for feature_name in &unstable_book_lib_features_section_file_names -
|
||||
&unstable_lib_feature_names {
|
||||
tidy_error!(bad,
|
||||
"The Unstable Book has a 'library feature' section '{}' which doesn't \
|
||||
correspond to an unstable library feature",
|
||||
feature_name)
|
||||
}
|
||||
|
||||
// Language features
|
||||
|
||||
let unstable_lang_feature_names = collect_unstable_feature_names(&lang_features);
|
||||
let unstable_book_lang_features_section_file_names =
|
||||
collect_unstable_book_lang_features_section_file_names(path);
|
||||
|
||||
// Check for Unstable Book sections that don't have a corresponding unstable feature
|
||||
for feature_name in &unstable_book_lib_features_section_file_names -
|
||||
&unstable_lib_feature_names {
|
||||
if !unstable_lang_feature_names.contains(&feature_name) {
|
||||
tidy_error!(bad,
|
||||
"The Unstable Book has a 'library feature' section '{}' which doesn't \
|
||||
correspond to an unstable library feature",
|
||||
feature_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Unstable Book sections that don't have a corresponding unstable feature.
|
||||
for feature_name in &unstable_book_lang_features_section_file_names -
|
||||
&unstable_lang_feature_names {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue