Skip to content

Commit

Permalink
Wrap va_list function as variadic function
Browse files Browse the repository at this point in the history
  • Loading branch information
Urgau committed Apr 18, 2023
1 parent cb28c6f commit 8a6fa59
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 41 deletions.
10 changes: 10 additions & 0 deletions bindgen/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ pub trait ParseCallbacks: fmt::Debug {
fn process_comment(&self, _comment: &str) -> Option<String> {
None
}

/// Process a function name that as exactly one `va_list` argument
/// to be wrapped as a variadic function with the wrapped static function
/// feature.
///
/// The returned string is new function name.
#[cfg(feature = "experimental")]
fn wrap_as_variadic_fn(&self, _name: &str) -> Option<String> {
None
}
}

/// Relevant information about a type to which new derive attributes will be added using
Expand Down
125 changes: 112 additions & 13 deletions bindgen/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ impl From<DerivableTraits> for Vec<&'static str> {
}
}

#[derive(Debug)]
struct WrapAsVariadic {
new_name: String,
idx_of_va_list_arg: usize,
}

struct CodegenResult<'a> {
items: Vec<proc_macro2::TokenStream>,
dynamic_items: DynamicItems,
Expand Down Expand Up @@ -268,7 +274,9 @@ struct CodegenResult<'a> {
/// that name. This lets us give each overload a unique suffix.
overload_counters: HashMap<String, u32>,

items_to_serialize: Vec<ItemId>,
/// List of items to serialize. With optionally the argument for the wrap as
/// variadic transformation to be applied.
items_to_serialize: Vec<(ItemId, Option<WrapAsVariadic>)>,
}

impl<'a> CodegenResult<'a> {
Expand Down Expand Up @@ -4210,9 +4218,6 @@ impl CodeGenerator for Function {
result.saw_function(seen_symbol_name);
}

let args = utils::fnsig_arguments(ctx, signature);
let ret = utils::fnsig_return_ty(ctx, signature);

let mut attributes = vec![];

if ctx.options().rust_features().must_use_function {
Expand Down Expand Up @@ -4297,10 +4302,6 @@ impl CodeGenerator for Function {
abi => abi,
};

if is_internal && ctx.options().wrap_static_fns {
result.items_to_serialize.push(item.id());
}

// Handle overloaded functions by giving each overload its own unique
// suffix.
let times_seen = result.overload_number(&canonical_name);
Expand Down Expand Up @@ -4334,12 +4335,49 @@ impl CodeGenerator for Function {
quote! { #[link(wasm_import_module = #name)] }
});

if is_internal && ctx.options().wrap_static_fns && !has_link_name_attr {
let should_wrap =
is_internal && ctx.options().wrap_static_fns && !has_link_name_attr;

if should_wrap {
let name = canonical_name.clone() + ctx.wrap_static_fns_suffix();
attributes.push(attributes::link_name::<true>(&name));
}

let ident = ctx.rust_ident(canonical_name);
let wrap_as_variadic = if should_wrap && !signature.is_variadic() {
utils::wrap_as_variadic_fn(ctx, signature, name)
} else {
None
};

let (ident, args) = if let Some(WrapAsVariadic {
idx_of_va_list_arg,
new_name,
}) = &wrap_as_variadic
{
(
new_name,
utils::fnsig_arguments_iter(
ctx,
// Prune argument at index (idx_of_va_list_arg)
signature.argument_types().iter().enumerate().filter_map(
|(idx, t)| {
if idx == *idx_of_va_list_arg {
None
} else {
Some(t)
}
},
),
// and replace it by a `...` (variadic symbol and the end of the signature)
true,
),
)
} else {
(&canonical_name, utils::fnsig_arguments(ctx, signature))
};
let ret = utils::fnsig_return_ty(ctx, signature);

let ident = ctx.rust_ident(ident);
let tokens = quote! {
#wasm_link_attribute
extern #abi {
Expand All @@ -4348,6 +4386,13 @@ impl CodeGenerator for Function {
}
};

// Add the item to the serialization list if necessary
if should_wrap {
result
.items_to_serialize
.push((item.id(), wrap_as_variadic));
}

// If we're doing dynamic binding generation, add to the dynamic items.
if is_dynamic_function {
let args_identifiers =
Expand Down Expand Up @@ -4850,16 +4895,70 @@ pub(crate) mod utils {

writeln!(code, "// Static wrappers\n")?;

for &id in &result.items_to_serialize {
let item = context.resolve_item(id);
item.serialize(context, (), &mut vec![], &mut code)?;
for (id, wrap_as_variadic) in &result.items_to_serialize {
let item = context.resolve_item(*id);
item.serialize(context, wrap_as_variadic, &mut vec![], &mut code)?;
}

std::fs::write(source_path, code)?;

Ok(())
}

pub(super) fn wrap_as_variadic_fn(
ctx: &BindgenContext,
signature: &FunctionSig,
name: &str,
) -> Option<super::WrapAsVariadic> {
// Fast path, exclude because:
// - with 0 args: no va_list possible, so no point searching for one
// - with 1 args: cannot have a `va_list` and another arg (required by va_start)
if signature.argument_types().len() <= 1 {
return None;
}

let mut it = signature.argument_types().iter().enumerate().filter_map(
|(idx, (_name, mut type_id))| {
// Hand rolled visitor that checks for the presence of `va_list`
loop {
let ty = ctx.resolve_type(type_id);
if Some("__builtin_va_list") == ty.name() {
return Some(idx);
}
match ty.kind() {
TypeKind::Alias(type_id_alias) => {
type_id = *type_id_alias
}
TypeKind::ResolvedTypeRef(type_id_typedef) => {
type_id = *type_id_typedef
}
_ => break,
}
}
None
},
);

// Return THE idx (by checking that there is no idx after)
// This is done since we cannot handle multiple `va_list`
it.next().filter(|_| it.next().is_none()).and_then(|idx| {
// Call the `wrap_as_variadic_fn` callback
#[cfg(feature = "experimental")]
{
ctx.options()
.last_callback(|c| c.wrap_as_variadic_fn(name))
.map(|new_name| super::WrapAsVariadic {
new_name,
idx_of_va_list_arg: idx,
})
}
#[cfg(not(feature = "experimental"))]
{
None
}
})
}

pub(crate) fn prepend_bitfield_unit_type(
ctx: &BindgenContext,
result: &mut Vec<proc_macro2::TokenStream>,
Expand Down
106 changes: 78 additions & 28 deletions bindgen/codegen/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ use crate::ir::item::ItemCanonicalName;
use crate::ir::item_kind::ItemKind;
use crate::ir::ty::{FloatKind, Type, TypeKind};

use super::CodegenError;
use super::{CodegenError, WrapAsVariadic};

fn get_loc(item: &Item) -> String {
item.location()
.map(|x| x.to_string())
.unwrap_or_else(|| "unknown".to_owned())
}

pub(crate) trait CSerialize<'a> {
pub(super) trait CSerialize<'a> {
type Extra;

fn serialize<W: Write>(
Expand All @@ -31,18 +31,18 @@ pub(crate) trait CSerialize<'a> {
}

impl<'a> CSerialize<'a> for Item {
type Extra = ();
type Extra = &'a Option<WrapAsVariadic>;

fn serialize<W: Write>(
&self,
ctx: &BindgenContext,
(): Self::Extra,
extra: Self::Extra,
stack: &mut Vec<String>,
writer: &mut W,
) -> Result<(), CodegenError> {
match self.kind() {
ItemKind::Function(func) => {
func.serialize(ctx, self, stack, writer)
func.serialize(ctx, (self, extra), stack, writer)
}
kind => Err(CodegenError::Serialize {
msg: format!("Cannot serialize item kind {:?}", kind),
Expand All @@ -53,12 +53,12 @@ impl<'a> CSerialize<'a> for Item {
}

impl<'a> CSerialize<'a> for Function {
type Extra = &'a Item;
type Extra = (&'a Item, &'a Option<WrapAsVariadic>);

fn serialize<W: Write>(
&self,
ctx: &BindgenContext,
item: Self::Extra,
(item, wrap_as_variadic): Self::Extra,
stack: &mut Vec<String>,
writer: &mut W,
) -> Result<(), CodegenError> {
Expand All @@ -85,19 +85,30 @@ impl<'a> CSerialize<'a> for Function {
let args = {
let mut count = 0;

let idx_to_prune = wrap_as_variadic.as_ref().map(
|WrapAsVariadic {
idx_of_va_list_arg, ..
}| *idx_of_va_list_arg,
);

signature
.argument_types()
.iter()
.cloned()
.map(|(opt_name, type_id)| {
(
opt_name.unwrap_or_else(|| {
let name = format!("arg_{}", count);
count += 1;
name
}),
type_id,
)
.enumerate()
.filter_map(|(idx, (opt_name, type_id))| {
if Some(idx) == idx_to_prune {
None
} else {
Some((
opt_name.unwrap_or_else(|| {
let name = format!("arg_{}", count);
count += 1;
name
}),
type_id,
))
}
})
.collect::<Vec<_>>()
};
Expand All @@ -106,33 +117,72 @@ impl<'a> CSerialize<'a> for Function {
let wrap_name = format!("{}{}", name, ctx.wrap_static_fns_suffix());

// The function's return type
let ret_ty = {
let (ret_item, ret_ty) = {
let type_id = signature.return_type();
let item = ctx.resolve_item(type_id);
let ret_ty = item.expect_type();
let ret_item = ctx.resolve_item(type_id);
let ret_ty = ret_item.expect_type();

// Write `ret_ty`.
ret_ty.serialize(ctx, item, stack, writer)?;
ret_ty.serialize(ctx, ret_item, stack, writer)?;

ret_ty
(ret_item, ret_ty)
};

// Write `wrap_name(args`.
write!(writer, " {}(", wrap_name)?;
serialize_args(&args, ctx, writer)?;

// Write `) { name(` if the function returns void and `) { return name(` if it does not.
if ret_ty.is_void() {
write!(writer, ") {{ {}(", name)?;
if wrap_as_variadic.is_none() {
// Write `) { name(` if the function returns void and `) { return name(` if it does not.
if ret_ty.is_void() {
write!(writer, ") {{ {}(", name)?;
} else {
write!(writer, ") {{ return {}(", name)?;
}
} else {
write!(writer, ") {{ return {}(", name)?;
// Write `, ...) {`
writeln!(writer, ", ...) {{")?;

// Declare the return type `RET_TY ret;` if their is a need to do so
if !ret_ty.is_void() {
ret_ty.serialize(ctx, ret_item, stack, writer)?;
writeln!(writer, " ret;")?;
}

// Setup va_list
writeln!(writer, "va_list ap;\n")?;
writeln!(writer, "va_start(ap, {});", args.last().unwrap().0)?;

// Write `ret = name(` or `name(` depending if the function returns something
if !ret_ty.is_void() {
write!(writer, "ret = ")?;
}
write!(writer, "{}(", name)?;
}

let mut args: Vec<_> = args.into_iter().map(|(name, _)| name).collect();
if let Some(WrapAsVariadic {
idx_of_va_list_arg, ..
}) = wrap_as_variadic
{
args.insert(*idx_of_va_list_arg, "ap".to_owned());
}

// Write `arg_names); }`.
serialize_sep(", ", args.iter(), ctx, writer, |(name, _), _, buf| {
// Write `arg_names);`.
serialize_sep(", ", args.iter(), ctx, writer, |name, _, buf| {
write!(buf, "{}", name).map_err(From::from)
})?;
writeln!(writer, "); }}")?;
write!(writer, ");{}", if wrap_as_variadic.is_none() { " " } else { "\n" })?;

if wrap_as_variadic.is_some() {
// End va_list and return the result if their is one
writeln!(writer, "va_end(ap);")?;
if !ret_ty.is_void() {
writeln!(writer, "return ret;")?;
}
}

writeln!(writer, "}}")?;

Ok(())
}
Expand Down

0 comments on commit 8a6fa59

Please sign in to comment.