-
Notifications
You must be signed in to change notification settings - Fork 13.5k
libcore: Add VaList and variadic arg handling intrinsics #49878
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
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.