Skip to content

Commit

Permalink
Support defining C compatible variadic functions
Browse files Browse the repository at this point in the history
Add support for defining C compatible variadic functions in unsafe rust
with extern "C".
  • Loading branch information
dlrobertson committed Feb 27, 2019
1 parent cd56472 commit 58147d4
Show file tree
Hide file tree
Showing 48 changed files with 848 additions and 152 deletions.
3 changes: 3 additions & 0 deletions src/librustc/hir/intravisit.rs
Expand Up @@ -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 => {}
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/librustc/hir/lowering.rs
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/librustc/hir/mod.rs
Expand Up @@ -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)]
Expand Down
3 changes: 3 additions & 0 deletions src/librustc/hir/print.rs
Expand Up @@ -434,6 +434,9 @@ impl<'a> State<'a> {
self.s.word("/*ERROR*/")?;
self.pclose()?;
}
hir::TyKind::CVarArgs(_) => {
self.s.word("...")?;
}
}
self.end()
}
Expand Down
3 changes: 2 additions & 1 deletion src/librustc/ich/impls_hir.rs
Expand Up @@ -361,7 +361,8 @@ 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 {
Expand Down
31 changes: 21 additions & 10 deletions src/librustc/middle/resolve_lifetime.rs
Expand Up @@ -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),
}
}
Expand Down Expand Up @@ -2225,18 +2232,22 @@ 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 {
for bound in bounds {
self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None);
}
match ty.node {
hir::TyKind::TraitObject(ref bounds, ref lifetime) => {
for bound in bounds {
self.visit_poly_trait_ref(bound, hir::TraitBoundModifier::None);
}

// Stay on the safe side and don't include the object
// lifetime default (which may not end up being used).
if !lifetime.is_elided() {
self.visit_lifetime(lifetime);
// Stay on the safe side and don't include the object
// lifetime default (which may not end up being used).
if !lifetime.is_elided() {
self.visit_lifetime(lifetime);
}
}
hir::TyKind::CVarArgs(_) => {}
_ => {
intravisit::walk_ty(self, ty);
}
} else {
intravisit::walk_ty(self, ty);
}
if let hir::TyKind::BareFn(_) = ty.node {
self.outer_index.shift_out(1);
Expand Down
6 changes: 3 additions & 3 deletions src/librustc/ty/sty.rs
Expand Up @@ -977,9 +977,9 @@ 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.
/// - `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>>,
Expand Down
45 changes: 39 additions & 6 deletions src/librustc_codegen_llvm/abi.rs
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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.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);
},
_ => (),
}
}
_ => {}
}
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -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));
Expand Down Expand Up @@ -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)) => {
Expand Down Expand Up @@ -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)) => {
Expand Down
51 changes: 41 additions & 10 deletions src/librustc_codegen_llvm/intrinsic.rs
Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down
8 changes: 4 additions & 4 deletions src/librustc_codegen_llvm/mono_item.rs
Expand Up @@ -36,10 +36,10 @@ impl PreDefineMethods<'tcx> for CodegenCx<'ll, 'tcx> {
}

fn predefine_fn(&self,
instance: Instance<'tcx>,
linkage: Linkage,
visibility: Visibility,
symbol_name: &str) {
instance: Instance<'tcx>,
linkage: Linkage,
visibility: Visibility,
symbol_name: &str) {
assert!(!instance.substs.needs_infer() &&
!instance.substs.has_param_types());

Expand Down
4 changes: 2 additions & 2 deletions src/librustc_codegen_llvm/va_arg.rs
Expand Up @@ -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)
}
Expand Down
50 changes: 46 additions & 4 deletions src/librustc_codegen_ssa/mir/block.rs
Expand Up @@ -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;
Expand All @@ -18,7 +18,7 @@ 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> {
Expand Down Expand Up @@ -232,12 +232,21 @@ impl<'a, 'tcx: 'a, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
}

mir::TerminatorKind::Return => {
if self.fn_ty.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));
Expand Down Expand Up @@ -481,7 +490,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.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)
Expand Down Expand Up @@ -658,7 +670,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.variadic && last_arg_idx.map(|x| x == i).unwrap_or(false) {
let layout = match 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) {
Expand Down

0 comments on commit 58147d4

Please sign in to comment.