From 08140878fefaa4b16939b904bf825b7107069b42 Mon Sep 17 00:00:00 2001 From: Dan Robertson Date: Tue, 23 Oct 2018 23:13:33 +0000 Subject: [PATCH 1/2] libcore: Add va_list lang item and intrinsics - Add the llvm intrinsics used to manipulate a va_list. - Add the va_list lang item in order to allow implementing VaList in libcore. --- src/libcore/ffi.rs | 185 +++++++++++++++++++++++++ src/librustc/middle/lang_items.rs | 1 + src/librustc_codegen_llvm/context.rs | 27 ++-- src/librustc_codegen_llvm/intrinsic.rs | 58 +++++++- src/librustc_codegen_llvm/lib.rs | 1 + src/librustc_codegen_llvm/va_arg.rs | 142 +++++++++++++++++++ src/librustc_typeck/check/intrinsic.rs | 52 +++++++ src/libstd/ffi/mod.rs | 7 + src/libstd/lib.rs | 1 + src/tools/tidy/src/pal.rs | 7 + 10 files changed, 468 insertions(+), 13 deletions(-) create mode 100644 src/librustc_codegen_llvm/va_arg.rs diff --git a/src/libcore/ffi.rs b/src/libcore/ffi.rs index a03756f9c229f..edeb3b0d368b3 100644 --- a/src/libcore/ffi.rs +++ b/src/libcore/ffi.rs @@ -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 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 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(&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(&mut self, f: F) -> R + where F: for<'copy> FnOnce(VaList<'copy>) -> R { + #[cfg(any(all(not(target_arch = "aarch64"), not(target_arch = "powerpc"), + 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); + let ret = f(VaList(ap.0)); + va_end(&mut ap); + ret + } +} + +#[cfg(not(stage0))] +extern "rust-intrinsic" { + /// 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(ap: &mut VaList) -> T; +} diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index 55ffa50e7c896..e7a8baf738395 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -297,6 +297,7 @@ language_item_table! { IndexMutTraitLangItem, "index_mut", index_mut_trait, Target::Trait; UnsafeCellTypeLangItem, "unsafe_cell", unsafe_cell_type, Target::Struct; + VaListTypeLangItem, "va_list", va_list, Target::Struct; DerefTraitLangItem, "deref", deref_trait, Target::Trait; DerefMutTraitLangItem, "deref_mut", deref_mut_trait, Target::Trait; diff --git a/src/librustc_codegen_llvm/context.rs b/src/librustc_codegen_llvm/context.rs index 5b088ad290810..d954eb838cb8a 100644 --- a/src/librustc_codegen_llvm/context.rs +++ b/src/librustc_codegen_llvm/context.rs @@ -723,17 +723,17 @@ impl IntrinsicDeclarationMethods<'tcx> for CodegenCx<'b, 'tcx> { ifn!("llvm.bitreverse.i64", fn(t_i64) -> t_i64); ifn!("llvm.bitreverse.i128", fn(t_i128) -> t_i128); - ifn!("llvm.fshl.i8", fn(t_i8, t_i8, t_i8) -> t_i8); - ifn!("llvm.fshl.i16", fn(t_i16, t_i16, t_i16) -> t_i16); - ifn!("llvm.fshl.i32", fn(t_i32, t_i32, t_i32) -> t_i32); - ifn!("llvm.fshl.i64", fn(t_i64, t_i64, t_i64) -> t_i64); - ifn!("llvm.fshl.i128", fn(t_i128, t_i128, t_i128) -> t_i128); - - ifn!("llvm.fshr.i8", fn(t_i8, t_i8, t_i8) -> t_i8); - ifn!("llvm.fshr.i16", fn(t_i16, t_i16, t_i16) -> t_i16); - ifn!("llvm.fshr.i32", fn(t_i32, t_i32, t_i32) -> t_i32); - ifn!("llvm.fshr.i64", fn(t_i64, t_i64, t_i64) -> t_i64); - ifn!("llvm.fshr.i128", fn(t_i128, t_i128, t_i128) -> t_i128); + ifn!("llvm.fshl.i8", fn(t_i8, t_i8, t_i8) -> t_i8); + ifn!("llvm.fshl.i16", fn(t_i16, t_i16, t_i16) -> t_i16); + ifn!("llvm.fshl.i32", fn(t_i32, t_i32, t_i32) -> t_i32); + ifn!("llvm.fshl.i64", fn(t_i64, t_i64, t_i64) -> t_i64); + ifn!("llvm.fshl.i128", fn(t_i128, t_i128, t_i128) -> t_i128); + + ifn!("llvm.fshr.i8", fn(t_i8, t_i8, t_i8) -> t_i8); + ifn!("llvm.fshr.i16", fn(t_i16, t_i16, t_i16) -> t_i16); + ifn!("llvm.fshr.i32", fn(t_i32, t_i32, t_i32) -> t_i32); + ifn!("llvm.fshr.i64", fn(t_i64, t_i64, t_i64) -> t_i64); + ifn!("llvm.fshr.i128", fn(t_i128, t_i128, t_i128) -> t_i128); ifn!("llvm.sadd.with.overflow.i8", fn(t_i8, t_i8) -> mk_struct!{t_i8, i1}); ifn!("llvm.sadd.with.overflow.i16", fn(t_i16, t_i16) -> mk_struct!{t_i16, i1}); @@ -783,6 +783,11 @@ impl IntrinsicDeclarationMethods<'tcx> for CodegenCx<'b, 'tcx> { ifn!("llvm.assume", fn(i1) -> void); ifn!("llvm.prefetch", fn(i8p, t_i32, t_i32, t_i32) -> void); + // variadic intrinsics + ifn!("llvm.va_start", fn(i8p) -> void); + ifn!("llvm.va_end", fn(i8p) -> void); + ifn!("llvm.va_copy", fn(i8p, i8p) -> void); + if self.sess().opts.debuginfo != DebugInfo::None { ifn!("llvm.dbg.declare", fn(self.type_metadata(), self.type_metadata()) -> void); ifn!("llvm.dbg.value", fn(self.type_metadata(), t_i64, self.type_metadata()) -> void); diff --git a/src/librustc_codegen_llvm/intrinsic.rs b/src/librustc_codegen_llvm/intrinsic.rs index 3548ccfd5a537..9c9b73f63faed 100644 --- a/src/librustc_codegen_llvm/intrinsic.rs +++ b/src/librustc_codegen_llvm/intrinsic.rs @@ -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`") + } + } + } + _ => { + 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 { diff --git a/src/librustc_codegen_llvm/lib.rs b/src/librustc_codegen_llvm/lib.rs index f904a928d53e6..4f90cb793b6d2 100644 --- a/src/librustc_codegen_llvm/lib.rs +++ b/src/librustc_codegen_llvm/lib.rs @@ -127,6 +127,7 @@ mod mono_item; mod type_; mod type_of; mod value; +mod va_arg; #[derive(Clone)] pub struct LlvmCodegenBackend(()); diff --git a/src/librustc_codegen_llvm/va_arg.rs b/src/librustc_codegen_llvm/va_arg.rs new file mode 100644 index 0000000000000..fbc3e6f06d147 --- /dev/null +++ b/src/librustc_codegen_llvm/va_arg.rs @@ -0,0 +1,142 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use builder::Builder; +use rustc_codegen_ssa::mir::operand::OperandRef; +use rustc_codegen_ssa::traits::{BaseTypeMethods, BuilderMethods, ConstMethods, DerivedTypeMethods}; +use rustc::ty::layout::{Align, HasDataLayout, HasTyCtxt, LayoutOf, Size}; +use rustc::ty::Ty; +use type_::Type; +use type_of::LayoutLlvmExt; +use value::Value; + +#[allow(dead_code)] +fn round_pointer_up_to_alignment( + bx: &mut Builder<'a, 'll, 'tcx>, + addr: &'ll Value, + align: Align, + ptr_ty: &'ll Type +) -> &'ll Value { + let mut ptr_as_int = bx.ptrtoint(addr, bx.cx().type_isize()); + ptr_as_int = bx.add(ptr_as_int, bx.cx().const_i32(align.bytes() as i32 - 1)); + ptr_as_int = bx.and(ptr_as_int, bx.cx().const_i32(-(align.bytes() as i32))); + bx.inttoptr(ptr_as_int, ptr_ty) +} + +fn emit_direct_ptr_va_arg( + bx: &mut Builder<'a, 'll, 'tcx>, + list: OperandRef<'tcx, &'ll Value>, + llty: &'ll Type, + size: Size, + align: Align, + slot_size: Align, + allow_higher_align: bool +) -> (&'ll Value, Align) { + let va_list_ptr_ty = bx.cx().type_ptr_to(bx.cx.type_i8p()); + let va_list_addr = if list.layout.llvm_type(bx.cx) != va_list_ptr_ty { + bx.bitcast(list.immediate(), va_list_ptr_ty) + } else { + list.immediate() + }; + + let ptr = bx.load(va_list_addr, bx.tcx().data_layout.pointer_align.abi); + + let (addr, addr_align) = if allow_higher_align && align > slot_size { + (round_pointer_up_to_alignment(bx, ptr, align, bx.cx().type_i8p()), align) + } else { + (ptr, slot_size) + }; + + + let aligned_size = size.align_to(slot_size).bytes() as i32; + let full_direct_size = bx.cx().const_i32(aligned_size); + let next = bx.inbounds_gep(addr, &[full_direct_size]); + bx.store(next, va_list_addr, bx.tcx().data_layout.pointer_align.abi); + + if size.bytes() < slot_size.bytes() && + &*bx.tcx().sess.target.target.target_endian == "big" { + let adjusted_size = bx.cx().const_i32((slot_size.bytes() - size.bytes()) as i32); + let adjusted = bx.inbounds_gep(addr, &[adjusted_size]); + (bx.bitcast(adjusted, bx.cx().type_ptr_to(llty)), addr_align) + } else { + (bx.bitcast(addr, bx.cx().type_ptr_to(llty)), addr_align) + } +} + +fn emit_ptr_va_arg( + bx: &mut Builder<'a, 'll, 'tcx>, + list: OperandRef<'tcx, &'ll Value>, + target_ty: Ty<'tcx>, + indirect: bool, + slot_size: Align, + allow_higher_align: bool +) -> &'ll Value { + let layout = bx.cx.layout_of(target_ty); + let (llty, size, align) = if indirect { + (bx.cx.layout_of(bx.cx.tcx.mk_imm_ptr(target_ty)).llvm_type(bx.cx), + bx.cx.data_layout().pointer_size, + bx.cx.data_layout().pointer_align) + } else { + (layout.llvm_type(bx.cx), + layout.size, + layout.align) + }; + let (addr, addr_align) = emit_direct_ptr_va_arg(bx, list, llty, size, align.abi, + slot_size, allow_higher_align); + if indirect { + let tmp_ret = bx.load(addr, addr_align); + bx.load(tmp_ret, align.abi) + } else { + bx.load(addr, addr_align) + } +} + +pub(super) fn emit_va_arg( + bx: &mut Builder<'a, 'll, 'tcx>, + addr: OperandRef<'tcx, &'ll Value>, + target_ty: Ty<'tcx>, +) -> &'ll Value { + // Determine the va_arg implementation to use. The LLVM va_arg instruction + // is lacking in some instances, so we should only use it as a fallback. + let arch = &bx.cx.tcx.sess.target.target.arch; + match (&**arch, + bx.cx.tcx.sess.target.target.options.is_like_windows) { + ("x86", true) => { + emit_ptr_va_arg(bx, addr, target_ty, false, + Align::from_bytes(4).unwrap(), false) + } + ("x86_64", true) => { + let target_ty_size = bx.cx.size_of(target_ty).bytes(); + let indirect = if target_ty_size > 8 || !target_ty_size.is_power_of_two() { + true + } else { + false + }; + emit_ptr_va_arg(bx, addr, target_ty, indirect, + Align::from_bytes(8).unwrap(), false) + } + ("x86", false) => { + emit_ptr_va_arg(bx, addr, target_ty, false, + Align::from_bytes(4).unwrap(), true) + } + _ => { + let va_list = if (bx.tcx().sess.target.target.arch == "aarch64" || + bx.tcx().sess.target.target.arch == "x86_64" || + bx.tcx().sess.target.target.arch == "powerpc") && + !bx.tcx().sess.target.target.options.is_like_windows { + bx.load(addr.immediate(), bx.tcx().data_layout.pointer_align.abi) + } else { + addr.immediate() + }; + bx.va_arg(va_list, bx.cx.layout_of(target_ty).llvm_type(bx.cx)) + } + } +} + diff --git a/src/librustc_typeck/check/intrinsic.rs b/src/librustc_typeck/check/intrinsic.rs index 9aad17626f9a1..2af21f547445e 100644 --- a/src/librustc_typeck/check/intrinsic.rs +++ b/src/librustc_typeck/check/intrinsic.rs @@ -14,6 +14,7 @@ use intrinsics; use rustc::traits::{ObligationCause, ObligationCauseCode}; use rustc::ty::{self, TyCtxt, Ty}; +use rustc::ty::subst::Subst; use rustc::util::nodemap::FxHashMap; use require_same_types; @@ -81,6 +82,16 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, it: &hir::ForeignItem) { let param = |n| tcx.mk_ty_param(n, Symbol::intern(&format!("P{}", n)).as_interned_str()); let name = it.name.as_str(); + + let mk_va_list_ty = || { + tcx.lang_items().va_list().map(|did| { + let region = tcx.mk_region(ty::ReLateBound(ty::INNERMOST, ty::BrAnon(0))); + let env_region = ty::ReLateBound(ty::INNERMOST, ty::BrEnv); + let va_list_ty = tcx.type_of(did).subst(tcx, &[region.into()]); + tcx.mk_mut_ref(tcx.mk_region(env_region), va_list_ty) + }) + }; + let (n_tps, inputs, output, unsafety) = if name.starts_with("atomic_") { let split : Vec<&str> = name.split('_').collect(); assert!(split.len() >= 2, "Atomic intrinsic in an incorrect format"); @@ -323,6 +334,47 @@ pub fn check_intrinsic_type<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, (0, vec![tcx.mk_fn_ptr(fn_ty), mut_u8, mut_u8], tcx.types.i32) } + "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") + } + } + + "va_copy" => { + match tcx.lang_items().va_list() { + Some(did) => { + let region = tcx.mk_region(ty::ReLateBound(ty::INNERMOST, ty::BrAnon(0))); + let env_region = ty::ReLateBound(ty::INNERMOST, ty::BrEnv); + let va_list_ty = tcx.type_of(did).subst(tcx, &[region.into()]); + let ret_ty = match va_list_ty.sty { + ty::Adt(def, _) if def.is_struct() => { + let fields = &def.non_enum_variant().fields; + match tcx.type_of(fields[0].did).subst(tcx, &[region.into()]).sty { + ty::Ref(_, element_ty, _) => match element_ty.sty { + ty::Adt(..) => element_ty, + _ => va_list_ty + } + _ => bug!("va_list structure is invalid") + } + } + _ => { + bug!("va_list structure is invalid") + } + }; + (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") + } + } + + "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") + } + } + "nontemporal_store" => { (1, vec![ tcx.mk_mut_ptr(param(0)), param(0) ], tcx.mk_unit()) } diff --git a/src/libstd/ffi/mod.rs b/src/libstd/ffi/mod.rs index a3345df5e0d85..b438417fd4ac6 100644 --- a/src/libstd/ffi/mod.rs +++ b/src/libstd/ffi/mod.rs @@ -174,5 +174,12 @@ pub use self::os_str::{OsString, OsStr}; #[stable(feature = "raw_os", since = "1.1.0")] pub use core::ffi::c_void; +#[cfg(not(stage0))] +#[unstable(feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "27745")] +pub use core::ffi::VaList; + mod c_str; mod os_str; diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 575903d576a23..5945ba6b09066 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -244,6 +244,7 @@ #![feature(array_error_internals)] #![feature(asm)] #![feature(box_syntax)] +#![feature(c_variadic)] #![feature(cfg_target_has_atomic)] #![feature(cfg_target_thread_local)] #![feature(cfg_target_vendor)] diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index 12e9a54da5281..822db25319138 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -74,6 +74,13 @@ const EXCEPTION_PATHS: &[&str] = &[ "src/libcore/tests", "src/liballoc/tests/lib.rs", + // The `VaList` implementation must have platform specific code. + // The Windows implementation of a `va_list` is always a character + // pointer regardless of the target architecture. As a result, + // we must use `#[cfg(windows)]` to conditionally compile the + // correct `VaList` structure for windows. + "src/libcore/ffi.rs", + // non-std crates "src/test", "src/tools", From e9e084f5fa8820aca67e5cf0ec301e52bbae1028 Mon Sep 17 00:00:00 2001 From: Dan Robertson Date: Tue, 23 Oct 2018 23:25:58 +0000 Subject: [PATCH 2/2] test: Add basic test for VaList --- .../c-link-to-rust-va-list-fn/Makefile | 6 + .../c-link-to-rust-va-list-fn/checkrust.rs | 142 ++++++++++++++++++ .../c-link-to-rust-va-list-fn/test.c | 95 ++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/Makefile create mode 100644 src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs create mode 100644 src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c diff --git a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/Makefile b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/Makefile new file mode 100644 index 0000000000000..f124ca2ab614a --- /dev/null +++ b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/Makefile @@ -0,0 +1,6 @@ +-include ../tools.mk + +all: + $(RUSTC) checkrust.rs + $(CC) test.c $(call STATICLIB,checkrust) $(call OUT_EXE,test) $(EXTRACFLAGS) + $(call RUN,test) diff --git a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs new file mode 100644 index 0000000000000..d4cc4a0ed5e2b --- /dev/null +++ b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/checkrust.rs @@ -0,0 +1,142 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_type = "staticlib"] +#![feature(c_variadic)] +#![feature(libc)] + +extern crate libc; + +use libc::{c_char, c_double, c_int, c_long, c_longlong}; +use std::ffi::VaList; +use std::slice; +use std::ffi::CStr; + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +pub enum AnswerType { + Double, + Long, + LongLong, + Int, + Byte, + CStr, + Skip, +} + +#[repr(C)] +pub union AnswerData { + pub double: c_double, + pub long: c_long, + pub longlong: c_longlong, + pub int: c_int, + pub byte: c_char, + pub cstr: *const c_char, + pub skip_ty: AnswerType, +} + +#[repr(C)] +pub struct Answer { + tag: AnswerType, + data: AnswerData, +} + +#[no_mangle] +pub unsafe fn compare_answers(answers: &[Answer], mut ap: VaList) -> usize { + for (i, answer) in answers.iter().enumerate() { + match answer { + Answer { tag: AnswerType::Double, data: AnswerData { double: d } } => { + let tmp = ap.arg::(); + if d.floor() != tmp.floor() { + println!("Double: {} != {}", d, tmp); + return i + 1; + } + } + Answer { tag: AnswerType::Long, data: AnswerData { long: l } } => { + let tmp = ap.arg::(); + if *l != tmp { + println!("Long: {} != {}", l, tmp); + return i + 1; + } + } + Answer { tag: AnswerType::LongLong, data: AnswerData { longlong: l } } => { + let tmp = ap.arg::(); + if *l != tmp { + println!("Long Long: {} != {}", l, tmp); + return i + 1; + } + } + Answer { tag: AnswerType::Int, data: AnswerData { int: n } } => { + let tmp = ap.arg::(); + if *n != tmp { + println!("Int: {} != {}", n, tmp); + return i + 1; + } + } + Answer { tag: AnswerType::Byte, data: AnswerData { byte: b } } => { + let tmp = ap.arg::(); + if *b != tmp { + println!("Byte: {} != {}", b, tmp); + return i + 1; + } + } + Answer { tag: AnswerType::CStr, data: AnswerData { cstr: c0 } } => { + let c1 = ap.arg::<*const c_char>(); + let cstr0 = CStr::from_ptr(*c0); + let cstr1 = CStr::from_ptr(c1); + if cstr0 != cstr1 { + println!("C String: {:?} != {:?}", cstr0, cstr1); + return i + 1; + } + } + _ => { + println!("Unknown type!"); + return i + 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn check_rust(argc: usize, answers: *const Answer, ap: VaList) -> usize { + let slice = slice::from_raw_parts(answers, argc); + compare_answers(slice, ap) +} + +#[no_mangle] +pub unsafe extern "C" fn check_rust_copy(argc: usize, answers: *const Answer, + mut ap: VaList) -> usize { + let slice = slice::from_raw_parts(answers, argc); + let mut skip_n = 0; + for (i, answer) in slice.iter().enumerate() { + match answer { + Answer { tag: AnswerType::Skip, data: AnswerData { skip_ty } } => { + match skip_ty { + AnswerType::Double => { ap.arg::(); } + AnswerType::Long => { ap.arg::(); } + AnswerType::LongLong => { ap.arg::(); } + AnswerType::Int => { ap.arg::(); } + AnswerType::Byte => { ap.arg::(); } + AnswerType::CStr => { ap.arg::<*const c_char>(); } + _ => { return i; } + }; + } + _ => { + skip_n = i; + break; + } + } + } + + ap.copy(|ap| { + compare_answers(&slice[skip_n..], ap) + }) +} diff --git a/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c new file mode 100644 index 0000000000000..80d9a48014203 --- /dev/null +++ b/src/test/run-make-fulldeps/c-link-to-rust-va-list-fn/test.c @@ -0,0 +1,95 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include +#include +#include +#include + +typedef enum { + TAG_DOUBLE, + TAG_LONG, + TAG_LONGLONG, + TAG_INT, + TAG_BYTE, + TAG_CSTR, + TAG_SKIP, +} tag; + +typedef struct { + tag answer_type; + union { + double double_precision; + long num_long; + long long num_longlong; + int num_int; + int8_t byte; + char* cstr; + tag skip_ty; + } answer_data; +} answer; + +#define MK_DOUBLE(n) \ + { TAG_DOUBLE, { .double_precision = n } } +#define MK_LONG(n) \ + { TAG_LONG, { .num_long = n } } +#define MK_LONGLONG(n) \ + { TAG_LONGLONG, { .num_longlong = n } } +#define MK_INT(n) \ + { TAG_INT, { .num_int = n } } +#define MK_BYTE(b) \ + { TAG_BYTE, { .byte = b } } +#define MK_CSTR(s) \ + { TAG_CSTR, { .cstr = s } } +#define MK_SKIP(ty) \ + { TAG_SKIP, { .skip_ty = TAG_ ## ty } } + +extern size_t check_rust(size_t argc, const answer* answers, va_list ap); +extern size_t check_rust_copy(size_t argc, const answer* answers, va_list ap); + +size_t test_check_rust(size_t argc, const answer* answers, ...) { + size_t ret = 0; + va_list ap; + va_start(ap, answers); + ret = check_rust(argc, answers, ap); + va_end(ap); + return ret; +} + +size_t test_check_rust_copy(size_t argc, const answer* answers, ...) { + size_t ret = 0; + va_list ap; + va_start(ap, answers); + ret = check_rust_copy(argc, answers, ap); + va_end(ap); + return ret; +} + +int main(int argc, char* argv[]) { + answer test_alignment0[] = {MK_LONGLONG(0x01LL), MK_INT(0x02), MK_LONGLONG(0x03LL)}; + assert(test_check_rust(3, test_alignment0, 0x01LL, 0x02, 0x03LL) == 0); + + answer test_alignment1[] = {MK_INT(-1), MK_BYTE('A'), MK_BYTE('4'), MK_BYTE(';'), + MK_INT(0x32), MK_INT(0x10000001), MK_CSTR("Valid!")}; + assert(test_check_rust(7, test_alignment1, -1, 'A', '4', ';', 0x32, 0x10000001, + "Valid!") == 0); + + answer basic_answers[] = {MK_DOUBLE(3.14), MK_LONG(12l), MK_BYTE('a'), + MK_DOUBLE(6.28), MK_CSTR("Hello"), MK_INT(42), + MK_CSTR("World")}; + assert(test_check_rust(7, basic_answers, 3.14, 12l, 'a', 6.28, "Hello", + 42, "World") == 0); + + answer copy_answers[] = { MK_SKIP(DOUBLE), MK_SKIP(INT), MK_SKIP(BYTE), MK_SKIP(CSTR), + MK_CSTR("Correctly skipped and copied list") }; + assert(test_check_rust_copy(5, copy_answers, 6.28, 16, 'A', "Skip Me!", + "Correctly skipped and copied list") == 0); + return 0; +}