RustAudio / rust-lv2

A safe, fast, and modular framework to create LV2 plugins, written in Rust
Apache License 2.0
171 stars 23 forks source link

Is bindgen necessary? #55

Closed dansgithubuser closed 4 years ago

dansgithubuser commented 4 years ago

I'm unclear the value of the bindgen step -- looks like it generates a file that can be consumed by a rust host? Is it acceptable to add a way to opt out of this?

To my mind, the point of lv2 is to break down language barriers -- it's a standard interface that any language can adhere to, as a plugin or host. Compulsory bindgen seems like prescription of a Rust host.

All the best, Dan

YruamaLairba commented 4 years ago

bindgen generate the lv2-sys crate. This generate the rust equivalent of the standard LV2 C API. In general, these equivalences are only valid for one particular platform, that why bindgen is run as least one time for each new project.

dansgithubuser commented 4 years ago

Oh -- how often does the LV2 C API change? Would it make sense to keep the generation step as a script in the repo, but outside the build? Like a deploy step for example, run every time the crate is published to crates.io, but devs who depend on the crate don't need clang.

JOOpdenhoevel commented 4 years ago

Yes, bindgen is necessary, unfortunately: The problem is that the C headers have different meanings on different the platforms. For example, while u32 in Rust is always an unsigned, 32-bit wide integer, an int in C may be 16- to 64-bit wide, signed or unsigned; The standard doesn't pin one meaning. Also, alignments of structs are different for different architectures: Some may require fields to be aligned to 64-bit, but some only to 32-bit.

While C is rather exact in it's meaning, Rust is even more exact. The bindings that were generated and are compatible to hosts on a x86-64 Windows may be completely different and incompatible to those generated for a Raspberry Pi.

We shouldn't limit ourselves to only a handful of platforms, since LV2 doesn't too, but even manually generating bindings for stable, beta and nightly Rust on tier 1 platforms would lead to 21(!) completely different versions of the same crate. If we want to add tier 2 support, we would end up with 216(!!!) versions!

Therefore, @prokopyl and I have decided to generate the bindings using bindgen at build time, which is also what bindgen recommends. I know, it's a drag that you have to have clang installed and it takes a lot of time to build, but there aren't any better options, I'm afraid.

YruamaLairba commented 4 years ago

@Janonard , technically, is it possible to pre-generate lv2-sys for the most popular platform and only use bindgen on other platform? I think it's possible but i don't know if it easily maintainable.

JOOpdenhoevel commented 4 years ago

It's possible, sure. There is also a CLI version of bindgen that can generate bindings. In theory, you could create a separate project with a build system that runs bindgen on all headers for all targets and who's output is a Rust crate that can be published. However, there are also some things that need to be implemented manually too.

If someone comes up with an alternative way of binding generation that

  1. is easily maintainable (updating the LV2 headers only requires replacing the headers),
  2. is reproducible (can be done by Travis CI from the command line),
  3. supports stable, beta, and nightly Rust on all Tier 1 targets,
  4. is easily extendable to other Tier 2 targets,
  5. and building a plugin with the solution takes an equal or less amount of time, I might consider it. But for now, using bindgen as a build dependency is the way to go.

EDIT: Made point 5 clearer.

dansgithubuser commented 4 years ago

@Janonard This is insightful!

Looking through LV2 headers, I find they are pretty careful about using types with consistent sizes. I notice there is one use of #ifdef _WIN32 in lv2.h. Does the problem boil down to that? Am I missing some usage of variable-acrosss-platform-sized types?

Points 1-4 are clear and fair requirements, I'm unclear about 5. Are you saying "new build must be quicker than old build" or "new build + new deploy must be quicker than old build"?

JOOpdenhoevel commented 4 years ago

Does the problem boil down to that? Am I missing some usage of variable-acrosss-platform-sized types?

There are other problems too: For example, C enums are represented as i32 on Windows, but as u32 on Linux and MacOS. Struct layouts are also different on completely different platforms: In the early days of lv2rs (one of the precursor projects), I've used bindings that we're pre-generated on an x86_64 Linux. However, all alignment and size checks that are generated by bindgen failed on my Raspberry Pi (ARMv6 Linux). C definitions simply have a different meaning on different platforms and we have to cope with that.

Points 1-4 are clear and fair requirements, I'm unclear about 5. Are you saying "new build must be quicker than old build" or "new build + new deploy must be quicker than old build"?

If the new rust-lv2 requires a new deployment, it must be separate to the rust-lv2 project, since the lv2-sys crate would need to be deployed first. This deployment, however, may take as long as it requires, my specifications only include the time it takes to build the deployed crate.

The whole problem is that using bindgen, building a simple plugin library takes a long time. This problem has to be solved by the new solution. I will update the requirements.