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

C/C++ Bindings #618

Merged

Conversation

TheBlueMatt
Copy link
Collaborator

@TheBlueMatt TheBlueMatt commented May 11, 2020

This is a super rough draft first pass at generating C bindings. The code to do so is horrendous, but the resulting C bindings are not completely insane. Thanks to @arik-so for initial passes at cbindgen and helping work through what could work and what downstream clients wont be able to use. Things left to do:

  • Map Result and Option things
  • Map Vec things
  • Map enums with contained values into C union + disciminant
  • Get the trait jump table for concrete structs (allowing us to use eg ChannelManager as ChannelMessageHandler)
  • _free functions to release resources
  • Trait destructors
  • (de)serialization
  • Document the memory management model
  • Carefully walk the generated code to check memory model.

@TheBlueMatt TheBlueMatt force-pushed the 2020-05-sample-c-bindings branch 3 times, most recently from e80f27a to 5daa4b0 Compare May 11, 2020 01:40
@TheBlueMatt TheBlueMatt force-pushed the 2020-05-sample-c-bindings branch 10 times, most recently from 92f61cf to f9c969d Compare May 11, 2020 05:13
@codecov
Copy link

codecov bot commented May 11, 2020

Codecov Report

Merging #618 into master will decrease coverage by 16.53%.
The diff coverage is 0.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master     #618       +/-   ##
===========================================
- Coverage   91.38%   74.84%   -16.54%     
===========================================
  Files          35       55       +20     
  Lines       21727    24437     +2710     
===========================================
- Hits        19855    18290     -1565     
- Misses       1872     6147     +4275     
Impacted Files Coverage Δ
lightning-c-bindings/src/bitcoin/network.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/c_types/derived.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/c_types/mod.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/chain/chaininterface.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/chain/keysinterface.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/chain/transaction.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/lib.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/ln/chan_utils.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/ln/channelmanager.rs 0.00% <0.00%> (ø)
lightning-c-bindings/src/ln/channelmonitor.rs 0.00% <0.00%> (ø)
... and 72 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2dd8b3e...0ab7f90. Read the comment docs.

@TheBlueMatt TheBlueMatt force-pushed the 2020-05-sample-c-bindings branch 5 times, most recently from 91b5eb4 to 0935d67 Compare May 12, 2020 17:10
@TheBlueMatt TheBlueMatt force-pushed the 2020-05-sample-c-bindings branch 2 times, most recently from 6373e67 to b370eda Compare May 12, 2020 19:37
@jkczyz jkczyz self-requested a review May 12, 2020 22:04
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to address these right away. I'm going to keep to send comments out in small bursts as I read through the code. Hopefully a few more rounds today.

Comment on lines +65 to +61
// Really not sure where syn gets '=' from here -
// it somehow represents '///' or '//!'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is because /// ... and //! .. are syntactic sugar for [doc = " ..."].

https://doc.rust-lang.org/rustdoc/the-doc-attribute.html

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh. Funny it even parses them that way.

match token_iter.next().unwrap() {
TokenTree::Literal(lit) => {
let line = format!("{}", lit);
if line.contains("(C-not exported)") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of muddying up our docs, could we define an attribute instead?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. We could change the line, but I actually kinda like putting it in the docs - it lets us link people to the rustdoc output even if they're using the library via C.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow the part about linking docs.

What I meant was we should define attributes that we use to annotate rust-lightning. Then this code checks for that attribute rather than trying to parse Rust docs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, the use of docs serves a dual purpose - both to allow us to make decisions at codegen time, and to actually document the fact that something isn't exported. Currently I've been sending people to the docs.rs docs for C bindings as well, though we could try to change that.

for attr in attrs.iter() {
let tokens_clone = attr.tokens.clone();
let mut token_iter = tokens_clone.into_iter();
if let Some(token) = token_iter.next() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be clearer if the path was examined first before iterating over the tokens. Especially given we can represent NoExport and Rename using attributes rather than with special comments, thus examining the tokens is not always necessary.

c-bindings-gen/src/types.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/types.rs Outdated Show resolved Hide resolved
c-bindings-gen/src/main.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind providing me a brain dump of the following?

  • High-level motivation for the transformation code
  • Each transformation and rationale for it

This would help me get a better understanding of the design decisions and allow me to provide more useful feedback. I'm happy to take that and make a README markdown which we can evolve along the way. I think we'll want something like that for posterity anyhow.

Otherwise, I'm not sure I'm making good use of my time understanding and trying to discern the rationale behind the generated code. Staring at it is kinda frying my brain honestly. :P 


use lightning::chain::chaininterface::FeeEstimator as lnFeeEstimator;
impl lnFeeEstimator for FeeEstimator {
fn get_est_sat_per_1000_weight(&self, confirmation_target: lightning::chain::chaininterface::ConfirmationTarget) -> u64 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For parameter types, you may be able to write this as lnConfirmationTarget since the use statement for it is given earlier.

Or as that only possible because ConfirmationTarget is defined in this file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case its because its defined in this file. There are a few cases where it isnt. While maybe we could add imports, its probably not worth the effort.

Comment on lines +2 to +6
use bitcoin::secp256k1::key::PublicKey as SecpPublicKey;
use bitcoin::secp256k1::key::SecretKey as SecpSecretKey;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way we can take advantage of the fact that these are Rust bindings for C code?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in theory, yea, they wrap underlying ffi/C types explicitly and you can use those, though that then assumes the caller is in C and/or using libsecp under the hood. I presume that this would be annoying to deal with if you were not, and byte arrays are universal. That said, it does result in an extra copy or two, including for private keys, which kinda sucks.

use lightning::ln::msgs::ChannelMessageHandler as lnChannelMessageHandler;
impl lnChannelMessageHandler for ChannelMessageHandler {
fn handle_open_channel(&self, their_node_id: &bitcoin::secp256k1::key::PublicKey, their_features: lightning::ln::features::InitFeatures, msg: &lightning::ln::msgs::OpenChannel) {
(self.handle_open_channel)(self.this_arg, crate::c_types::PublicKey::from_rust(their_node_id), crate::ln::features::InitFeatures { inner: Box::into_raw(Box::new(their_features)) }, &OpenChannel { inner: msg })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do types like PublicKey need to be serialized while types like InitFeatures do not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fundamentally because there's a C equivalent (for a public key namely a 33 byte string). We could just serialize out InitFeatures into C (though its obviously more complicated since its not fixed-length), but then we'd lose things like "does_support_feature_X" (well, we dont have those now cause we cant run the macro to figure out the generated code, but at least for other types this holds).

@TheBlueMatt TheBlueMatt force-pushed the 2020-05-sample-c-bindings branch 6 times, most recently from e654d7e to cdfc9d1 Compare September 8, 2020 23:38
Copy link
Contributor

@valentinewallace valentinewallace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than these last few nits, looks good!


LDKEventsProvider prov = ChannelManager_as_EventsProvider(&cm);
LDKCVec_EventZ events = (prov.get_and_clear_pending_events)(prov.this_arg);
assert((unsigned long)events.data < 4096); // There's an offset, but it should still be an offset against null in the 0 page
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering, what's the purpose of this check? (and it's casting events.data as a pointer, right?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checks that there was no allocation/malloc done for the data.

lightning-c-bindings/README.md Outdated Show resolved Hide resolved
lightning-c-bindings/README.md Outdated Show resolved Hide resolved
lightning-c-bindings/README.md Outdated Show resolved Hide resolved
lightning-c-bindings/README.md Outdated Show resolved Hide resolved
@TheBlueMatt
Copy link
Collaborator Author

I think this is ready to hit merge!

@valentinewallace
Copy link
Contributor

I think this is ready to hit merge!

You probably saw this, but the check_bindings failure looks legit

@TheBlueMatt
Copy link
Collaborator Author

Oops, I'd accidentally dropped one commit in rebase, fixed now.

This cleans upa few last cases of functions/objects which our C
bindings generator doesn't know how to read.
In general, it maps:
 * Traits to a struct with a void* and a list of function pointers,
   emulating what the compiler will do for a dyn trait anyway,
 * Structs as a struct with a single opaque pointer to the
   underlying type and a flag to indicate ownership. While this is
   a bit less effecient than just a direct pointer, it neatly lets
   us expose in the public interface the concept of ownership by
   setting a flag in the generated struct.
 * Unit enums as enums with each type copied over and conversion
   functions,
 * Non-unit enums have each field converted back and forth with a
   type flag and a union across all the C-mapped fields.
Including:
 * A script to automatically generate all the rest,
 * Cargo.toml and cbindgen.toml,
 * manually-written wrapper types for a few types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants