Skip to content
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

Implement support for inline allocated Boxed types #1234

Merged
merged 5 commits into from Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 54 additions & 20 deletions book/src/config_api.md
Expand Up @@ -253,26 +253,6 @@ status = "generate"
cfg_condition = "feature = \"ser_de\""
```

Gir auto-detects `copy`/`free` or `ref`/`unref` function pairs for memory management
on records. It falls back to generic `g_boxed_copy`/`g_boxed_free` if these are not
found, based on an existing implementation of `get_type`. Otherwise no record
implementation can be generated.

Some boxed types are passed as `out` parameters to functions and the caller is
required to allocate them. For this it is necessary to provide Rust
expressions in the configuration for initializing newly allocated memory for
them, and to free any resources that might be stored in values of that boxed
types. By default the memory is zero-initialized and it is valid to provide an
empty closure like below.

```toml
[[object]]
name = "Gtk.TreeIter"
status = "generate"
init_function_expression = "|_ptr| ()"
clear_function_expression = "|_ptr| ()"
```

For global functions, the members can be configured by configuring the `Gtk.*` object:

```toml
Expand Down Expand Up @@ -474,6 +454,60 @@ must_use = true
err_type = "gst::StateChangeError"
```

## Boxed types vs. BoxedInline types

For boxed types / records, gir auto-detects `copy`/`free` or `ref`/`unref`
function pairs for memory management on records. It falls back to generic
`g_boxed_copy`/`g_boxed_free` if these are not found, based on an existing
implementation of `get_type`. Otherwise no record implementation can be
generated.

This works for the majority of boxed types, which are literally boxed: their
memory is always allocated on the heap and memory management is left to the C
library. Some boxed types, however, are special and in C code they are usually
allocated on the stack or inline inside another struct. As such, their struct
definition is public and part of the API/ABI. Usually these types are
relatively small and allocated regularly, which would make heap allocations
costly.

These special boxed types are usually allocated by the caller of the C
functions, and the functions are then only filling in the memory and taking
values of this type as `(out caller-allocates)` parameters.

In most other bindings, heap allocations are used for these boxed types too
but in Rust we can do better and actually allocate them on the stack or inline
inside another struct.

Gir calls these special boxed types "boxed inline".

```toml
[[object]]
name = "GLib.TreeIter"
status = "generate"
boxed_inline = true
```

For inline-allocated boxed types it is possible to provide Rust expressions in
the configuration for initializing newly allocated memory for them, to copy
from one value into another one, and to free any resources that might be
stored in values of that boxed types.

By default the memory is zero-initialized and copying is done via
`std::ptr::copy()`. If the boxed type contains memory that needs to be freed
then these functions must be provided.

The following configuration is equivalent with the one above.

```toml
[[object]]
name = "GLib.TreeIter"
status = "generate"
boxed_inline = true
init_function_expression = "|_ptr| ()"
copy_into_function_expression = "|dest, src| { std::ptr::copy_nonoverlapping(src, dest, 1); }"
clear_function_expression = "|_ptr| ()"
```

## Generation in API mode

To generate the Rust-user API level, The command is very similar to the previous one. It's better to not put this output in the same directory as where the FFI files are. Just run:
Expand Down
23 changes: 21 additions & 2 deletions src/analysis/record.rs
Expand Up @@ -19,7 +19,9 @@ pub struct Info {
pub glib_get_type: Option<(String, Option<Version>)>,
pub is_boxed: bool,
pub derives: Derives,
pub boxed_inline: bool,
pub init_function_expression: Option<String>,
pub copy_into_function_expression: Option<String>,
pub clear_function_expression: Option<String>,
}

Expand Down Expand Up @@ -77,7 +79,11 @@ pub fn new(env: &Env, obj: &GObject) -> Option<Info> {

let record: &library::Record = type_.maybe_ref()?;

let is_boxed = RecordType::of(record) == RecordType::AutoBoxed;
let is_boxed = matches!(
RecordType::of(record),
RecordType::Boxed | RecordType::AutoBoxed
);
let boxed_inline = obj.boxed_inline;

let mut imports = Imports::with_defined(&env.library, &name);

Expand Down Expand Up @@ -105,8 +111,16 @@ pub fn new(env: &Env, obj: &GObject) -> Option<Info> {
};

let mut derives = if let Some(ref derives) = obj.derives {
if boxed_inline
&& !derives.is_empty()
&& !derives
.iter()
.all(|ds| ds.names.is_empty() || ds.names.iter().all(|n| n == "Debug"))
{
panic!("Can't automatically derive traits other than `Debug` for BoxedInline records");
}
derives.clone()
} else {
} else if !boxed_inline {
let derives = vec![Derive {
names: vec![
"Debug".into(),
Expand All @@ -120,6 +134,9 @@ pub fn new(env: &Env, obj: &GObject) -> Option<Info> {
}];

derives
} else {
// boxed_inline
vec![]
};

for special in specials.traits().keys() {
Expand Down Expand Up @@ -196,7 +213,9 @@ pub fn new(env: &Env, obj: &GObject) -> Option<Info> {
glib_get_type,
derives,
is_boxed,
boxed_inline,
init_function_expression: obj.init_function_expression.clone(),
copy_into_function_expression: obj.copy_into_function_expression.clone(),
clear_function_expression: obj.clear_function_expression.clone(),
};

Expand Down
54 changes: 44 additions & 10 deletions src/codegen/general.rs
Expand Up @@ -224,7 +224,9 @@ fn define_boxed_type_internal(
glib_name: &str,
copy_fn: &TraitInfo,
free_fn: &str,
boxed_inline: bool,
init_function_expression: &Option<String>,
copy_into_function_expression: &Option<String>,
clear_function_expression: &Option<String>,
get_type_fn: Option<&str>,
derive: &[Derive],
Expand All @@ -235,8 +237,11 @@ fn define_boxed_type_internal(
derives(w, derive, 1)?;
writeln!(
w,
"\tpub struct {}(Boxed<{}::{}>);",
type_name, sys_crate_name, glib_name
"\tpub struct {}(Boxed{}<{}::{}>);",
type_name,
if boxed_inline { "Inline" } else { "" },
sys_crate_name,
glib_name
)?;
writeln!(w)?;
writeln!(w, "\tmatch fn {{")?;
Expand All @@ -252,10 +257,17 @@ fn define_boxed_type_internal(
)?;
writeln!(w, "\t\tfree => |ptr| {}::{}(ptr),", sys_crate_name, free_fn)?;

if let (Some(init_function_expression), Some(clear_function_expression)) =
(init_function_expression, clear_function_expression)
{
if let (
Some(init_function_expression),
Some(copy_into_function_expression),
Some(clear_function_expression),
) = (
init_function_expression,
copy_into_function_expression,
clear_function_expression,
) {
writeln!(w, "\t\tinit => {},", init_function_expression,)?;
writeln!(w, "\t\tcopy_into => {},", copy_into_function_expression,)?;
writeln!(w, "\t\tclear => {},", clear_function_expression,)?;
}

Expand All @@ -275,7 +287,9 @@ pub fn define_boxed_type(
glib_name: &str,
copy_fn: &TraitInfo,
free_fn: &str,
boxed_inline: bool,
init_function_expression: &Option<String>,
copy_into_function_expression: &Option<String>,
clear_function_expression: &Option<String>,
get_type_fn: Option<(String, Option<Version>)>,
derive: &[Derive],
Expand All @@ -292,7 +306,9 @@ pub fn define_boxed_type(
glib_name,
copy_fn,
free_fn,
boxed_inline,
init_function_expression,
copy_into_function_expression,
clear_function_expression,
Some(get_type_fn),
derive,
Expand All @@ -307,7 +323,9 @@ pub fn define_boxed_type(
glib_name,
copy_fn,
free_fn,
boxed_inline,
init_function_expression,
copy_into_function_expression,
clear_function_expression,
None,
derive,
Expand All @@ -320,7 +338,9 @@ pub fn define_boxed_type(
glib_name,
copy_fn,
free_fn,
boxed_inline,
init_function_expression,
copy_into_function_expression,
clear_function_expression,
Some(get_type_fn),
derive,
Expand All @@ -334,7 +354,9 @@ pub fn define_boxed_type(
glib_name,
copy_fn,
free_fn,
boxed_inline,
init_function_expression,
copy_into_function_expression,
clear_function_expression,
None,
derive,
Expand All @@ -349,7 +371,9 @@ pub fn define_auto_boxed_type(
env: &Env,
type_name: &str,
glib_name: &str,
boxed_inline: bool,
init_function_expression: &Option<String>,
copy_into_function_expression: &Option<String>,
clear_function_expression: &Option<String>,
get_type_fn: &str,
derive: &[Derive],
Expand All @@ -360,8 +384,11 @@ pub fn define_auto_boxed_type(
derives(w, derive, 1)?;
writeln!(
w,
"\tpub struct {}(Boxed<{}::{}>);",
type_name, sys_crate_name, glib_name
"\tpub struct {}(Boxed{}<{}::{}>);",
type_name,
if boxed_inline { "Inline" } else { "" },
sys_crate_name,
glib_name
)?;
writeln!(w)?;
writeln!(w, "\tmatch fn {{")?;
Expand All @@ -382,10 +409,17 @@ pub fn define_auto_boxed_type(
get_type_fn
)?;

if let (Some(init_function_expression), Some(clear_function_expression)) =
(init_function_expression, clear_function_expression)
{
if let (
Some(init_function_expression),
Some(copy_into_function_expression),
Some(clear_function_expression),
) = (
init_function_expression,
copy_into_function_expression,
clear_function_expression,
) {
writeln!(w, "\t\tinit => {},", init_function_expression,)?;
writeln!(w, "\t\tcopy_into => {},", copy_into_function_expression,)?;
writeln!(w, "\t\tclear => {},", clear_function_expression,)?;
}

Expand Down
9 changes: 7 additions & 2 deletions src/codegen/record.rs
@@ -1,8 +1,9 @@
use super::{function, general, trait_impls};
use crate::{
analysis::{self, special_functions::Type},
analysis::{self, record_type::RecordType, special_functions::Type},
env::Env,
library,
traits::MaybeRef,
};
use std::io::{Result, Write};

Expand All @@ -12,14 +13,16 @@ pub fn generate(w: &mut dyn Write, env: &Env, analysis: &analysis::record::Info)
general::start_comments(w, &env.config)?;
general::uses(w, env, &analysis.imports, type_.version)?;

if analysis.is_boxed {
if RecordType::of(env.type_(analysis.type_id).maybe_ref().unwrap()) == RecordType::AutoBoxed {
if let Some((ref glib_get_type, _)) = analysis.glib_get_type {
general::define_auto_boxed_type(
w,
env,
&analysis.name,
&type_.c_type,
analysis.boxed_inline,
&analysis.init_function_expression,
&analysis.copy_into_function_expression,
&analysis.clear_function_expression,
glib_get_type,
&analysis.derives,
Expand Down Expand Up @@ -61,7 +64,9 @@ pub fn generate(w: &mut dyn Write, env: &Env, analysis: &analysis::record::Info)
&type_.c_type,
copy_fn,
&free_fn.glib_name,
analysis.boxed_inline,
&analysis.init_function_expression,
&analysis.copy_into_function_expression,
&analysis.clear_function_expression,
analysis.glib_get_type.as_ref().map(|(f, v)| {
if v > &analysis.version {
Expand Down
2 changes: 1 addition & 1 deletion src/codegen/sys/tests.rs
Expand Up @@ -182,7 +182,7 @@ fn prepare_cconsts(env: &Env) -> Vec<CConstant> {
/// Checks if type name is unlikely to correspond to a real C type name.
fn is_name_made_up(name: &str) -> bool {
// Unnamed types are assigned name during parsing, those names contain an underscore.
name.contains('_')
name.contains('_') && !name.ends_with("_t")
}

fn generate_manual_h(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> {
Expand Down