-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libcore: Add VaList and variadic arg handling intrinsics #49878
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
#![stable(feature = "", since = "1.30.0")] | ||
|
||
#![allow(non_camel_case_types)] | ||
#![cfg_attr(stage0, allow(dead_code))] | ||
|
||
//! Utilities related to FFI bindings. | ||
|
||
|
@@ -40,3 +41,187 @@ impl fmt::Debug for c_void { | |
f.pad("c_void") | ||
} | ||
} | ||
|
||
/// Basic implementation of a `va_list`. | ||
#[cfg(any(all(not(target_arch = "aarch64"), not(target_arch = "powerpc"), | ||
not(target_arch = "x86_64")), | ||
windows))] | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
extern { | ||
type VaListImpl; | ||
} | ||
|
||
#[cfg(any(all(not(target_arch = "aarch64"), not(target_arch = "powerpc"), | ||
not(target_arch = "x86_64")), | ||
windows))] | ||
impl fmt::Debug for VaListImpl { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "va_list* {:p}", self) | ||
} | ||
} | ||
|
||
/// AArch64 ABI implementation of a `va_list`. See the | ||
/// [Aarch64 Procedure Call Standard] for more details. | ||
/// | ||
/// [AArch64 Procedure Call Standard]: | ||
/// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf | ||
#[cfg(all(target_arch = "aarch64", not(windows)))] | ||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
struct VaListImpl { | ||
stack: *mut (), | ||
gr_top: *mut (), | ||
vr_top: *mut (), | ||
gr_offs: i32, | ||
vr_offs: i32, | ||
} | ||
|
||
/// PowerPC ABI implementation of a `va_list`. | ||
#[cfg(all(target_arch = "powerpc", not(windows)))] | ||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
struct VaListImpl { | ||
gpr: u8, | ||
fpr: u8, | ||
reserved: u16, | ||
overflow_arg_area: *mut (), | ||
reg_save_area: *mut (), | ||
} | ||
|
||
/// x86_64 ABI implementation of a `va_list`. | ||
#[cfg(all(target_arch = "x86_64", not(windows)))] | ||
#[repr(C)] | ||
#[derive(Debug)] | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
struct VaListImpl { | ||
gp_offset: i32, | ||
fp_offset: i32, | ||
overflow_arg_area: *mut (), | ||
reg_save_area: *mut (), | ||
} | ||
|
||
/// A wrapper for a `va_list` | ||
#[lang = "va_list"] | ||
#[derive(Debug)] | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
#[repr(transparent)] | ||
#[cfg(not(stage0))] | ||
pub struct VaList<'a>(&'a mut VaListImpl); | ||
|
||
// The VaArgSafe trait needs to be used in public interfaces, however, the trait | ||
// itself must not be allowed to be used outside this module. Allowing users to | ||
// implement the trait for a new type (thereby allowing the va_arg intrinsic to | ||
// be used on a new type) is likely to cause undefined behavior. | ||
// | ||
// FIXME(dlrobertson): In order to use the VaArgSafe trait in a public interface | ||
// but also ensure it cannot be used elsewhere, the trait needs to be public | ||
// within a private module. Once RFC 2145 has been implemented look into | ||
// improving this. | ||
mod sealed_trait { | ||
/// Trait which whitelists the allowed types to be used with [VaList::arg] | ||
/// | ||
/// [VaList::va_arg]: struct.VaList.html#method.arg | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
pub trait VaArgSafe {} | ||
} | ||
|
||
macro_rules! impl_va_arg_safe { | ||
($($t:ty),+) => { | ||
$( | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
impl sealed_trait::VaArgSafe for $t {} | ||
)+ | ||
} | ||
} | ||
|
||
impl_va_arg_safe!{i8, i16, i32, i64, usize} | ||
impl_va_arg_safe!{u8, u16, u32, u64, isize} | ||
impl_va_arg_safe!{f64} | ||
|
||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
impl<T> sealed_trait::VaArgSafe for *mut T {} | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
impl<T> sealed_trait::VaArgSafe for *const T {} | ||
|
||
#[cfg(not(stage0))] | ||
impl<'a> VaList<'a> { | ||
/// Advance to the next arg. | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
pub unsafe fn arg<T: sealed_trait::VaArgSafe>(&mut self) -> T { | ||
va_arg(self) | ||
} | ||
|
||
/// Copy the `va_list` at the current location. | ||
#[unstable(feature = "c_variadic", | ||
reason = "the `c_variadic` feature has not been properly tested on \ | ||
all supported platforms", | ||
issue = "27745")] | ||
pub unsafe fn copy<F, R>(&mut self, f: F) -> R | ||
where F: for<'copy> FnOnce(VaList<'copy>) -> R { | ||
#[cfg(any(all(not(target_arch = "aarch64"), not(target_arch = "powerpc"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the |
||
not(target_arch = "x86_64")), | ||
windows))] | ||
let mut ap = va_copy(self); | ||
#[cfg(all(any(target_arch = "aarch64", target_arch = "powerpc", target_arch = "x86_64"), | ||
not(windows)))] | ||
let mut ap_inner = va_copy(self); | ||
#[cfg(all(any(target_arch = "aarch64", target_arch = "powerpc", target_arch = "x86_64"), | ||
not(windows)))] | ||
let mut ap = VaList(&mut ap_inner); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh you actually have a reference there. Why do you want a reference to uninitialized data? So far this should be considered UB, until we have made the decision to allow it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmpf. Maybe we can There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or use a raw pointer in I have no idea what this is all about, just making some guesses here.^^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's semantically a reference, with a lifetime. Really, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, you want
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This could work. It is a type that isn't exposed to users, so I don't see a way that this could cause unintended havoc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that I still prefer making |
||
let ret = f(VaList(ap.0)); | ||
va_end(&mut ap); | ||
ret | ||
} | ||
} | ||
|
||
#[cfg(not(stage0))] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should just be around the whole module, in |
||
extern "rust-intrinsic" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We loose some type safety here by using a raw pointer as the type of the input, but this makes the codegen a little less complex. We can either have a |
||
/// Destroy the arglist `ap` after initialization with `va_start` or | ||
/// `va_copy`. | ||
fn va_end(ap: &mut VaList); | ||
|
||
/// Copy the current location of arglist `src` to the arglist `dst`. | ||
#[cfg(any(all(not(target_arch = "aarch64"), not(target_arch = "powerpc"), | ||
not(target_arch = "x86_64")), | ||
windows))] | ||
fn va_copy<'a>(src: &VaList<'a>) -> VaList<'a>; | ||
#[cfg(all(any(target_arch = "aarch64", target_arch = "powerpc", target_arch = "x86_64"), | ||
not(windows)))] | ||
fn va_copy(src: &VaList) -> VaListImpl; | ||
|
||
/// Loads an argument of type `T` from the `va_list` `ap` and increment the | ||
/// argument `ap` points to. | ||
fn va_arg<T: sealed_trait::VaArgSafe>(ap: &mut VaList) -> T; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,13 +24,14 @@ use context::CodegenCx; | |
use type_::Type; | ||
use type_of::LayoutLlvmExt; | ||
use rustc::ty::{self, Ty}; | ||
use rustc::ty::layout::{LayoutOf, HasTyCtxt}; | ||
use rustc::ty::layout::{self, LayoutOf, HasTyCtxt, Primitive}; | ||
use rustc_codegen_ssa::common::TypeKind; | ||
use rustc::hir; | ||
use syntax::ast; | ||
use syntax::ast::{self, FloatTy}; | ||
use syntax::symbol::Symbol; | ||
use builder::Builder; | ||
use value::Value; | ||
use va_arg::emit_va_arg; | ||
|
||
use rustc_codegen_ssa::traits::*; | ||
|
||
|
@@ -146,6 +147,59 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> { | |
let tp_ty = substs.type_at(0); | ||
self.cx().const_usize(self.cx().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_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") | ||
}; | ||
let intrinsic = self.cx().get_intrinsic(&("llvm.va_copy")); | ||
self.call(intrinsic, &[llresult, va_list], None); | ||
return; | ||
} | ||
"va_arg" => { | ||
match fn_ty.ret.layout.abi { | ||
layout::Abi::Scalar(ref scalar) => { | ||
match scalar.value { | ||
Primitive::Int(..) => { | ||
if self.cx().size_of(ret_ty).bytes() < 4 { | ||
// va_arg should not be called on a integer type | ||
// less than 4 bytes in length. If it is, promote | ||
// the integer to a `i32` and truncate the result | ||
// back to the smaller type. | ||
let promoted_result = emit_va_arg(self, args[0], | ||
tcx.types.i32); | ||
self.trunc(promoted_result, llret_ty) | ||
} else { | ||
emit_va_arg(self, args[0], ret_ty) | ||
} | ||
} | ||
Primitive::Float(FloatTy::F64) | | ||
Primitive::Pointer => { | ||
emit_va_arg(self, args[0], ret_ty) | ||
} | ||
// `va_arg` should never be used with the return type f32. | ||
Primitive::Float(FloatTy::F32) => { | ||
bug!("the va_arg intrinsic does not work with `f32`") | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can remove this special-casing here now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed. I was going to leave it for when arbitrary types are supported, but I think a |
||
} | ||
_ => { | ||
bug!("the va_arg intrinsic does not work with non-scalar types") | ||
} | ||
} | ||
} | ||
"size_of_val" => { | ||
let tp_ty = substs.type_at(0); | ||
if let OperandValue::Pair(_, meta) = args[0].val { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it work with
#[repr(C)]
instead of#[repr(transparent)]
?And if so, can you also try
extern { type VaListImpl; }
?(that would be ideal because it can't be misused like
_dummy
can.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although then the uninitialized in
va_copy
wouldn't work as well - I'm assuming the uninitializedVaListImpl
in this case is unused, and LLVM overwrites the pointer itself?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you could have a method on
VaListImpl
that takes&mut Self
(in the other cases) or&()
(in this case), returning&mut Self
in both cases, and&mut uninitialized_variable
is passed to it fromcopy
.In this case, you'd return
&mut *(1 as *mut Self)
or something like that - a dummy pointer that could be overwritten later.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying this at the moment.
I think this should work. I don't think it should need to take any parameters though, since
self
should have no impact on the uninitialized output.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, in all other cases you need to return a reference to an uninitialized memory location, but the pointer needs to be initialized. That's why the argument.