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# / dotnet target for diplomat-tool #124

Merged
merged 19 commits into from Feb 2, 2022

Conversation

CBenoit
Copy link
Contributor

@CBenoit CBenoit commented Jan 20, 2022

Hi,

I've been writing a dotnet target for diplomat-tool in order to expose a C# API to picky.
I actually used it to generate fully functional bindings to picky in my experiment repository. The only missing piece is a way to return a buffer of bytes (basically something like the DiplomatWriteable, but using std::io::Write instead of std::fmt::Write I guess).

I still have a few corner cases to fix, but I figured I could open this PR as a draft.

Todo before I consider the MVP complete

  • Fix generated code when bool type is in return position.
  • Special case to handle results using the unit () type as error.

Future improvements

  • Proper handling of Option of enum in parameters
  • Support custom types in managed non-opaque struct's fields (aka: avoid double freeing). Currently the only way to access custom types of non-opaque structs is to use the "raw" API with pointers.

@CBenoit CBenoit marked this pull request as draft January 20, 2022 23:42
@Manishearth
Copy link
Contributor

Manishearth commented Jan 20, 2022

Wow, this is amazing! cc @sffc @nciric

I can't commit to maintaining this backend, and I may still be making changes to the rest of the code here: I'm kinda fine with landing this here with the understanding that the backend may occasionally break (which I can always tag you in for). Does that seem okay to you? In general Diplomat is still kinda unstable so users should be installing specific git hashes (or cargo versions) anyway. Over time this should become less of a problem, it's just that right now there are still missing features and in the process of adding these features I can't (at the moment) commit to making sure the dotnet backend works too.

Another option is to create a separate dotnet repo that lives underneath rust-diplomat where the tool lives. Eventually I would like all diplomat plugins to be convenient to write this way, but that might not be the case right now, so I'm okay with adding more backends here for now, and it will certainly be the most convenient for you if it's in this repo for now.

(We may have more bandwidth to work on dotnet ourselves in the future, it's certainly something we'd like to support, and we're happy to make space for this backend to be worked on right now as well, just that we may not be able to commit to working on it too)

@Manishearth
Copy link
Contributor

Manishearth commented Jan 20, 2022

The only missing piece is a way to return a buffer of bytes (basically something like the DiplomatWriteable, but using std::io::Write instead of std::fmt::Write I guess).

Yeah I think we need some kind of DiplomatWriteable-like thing that allows one to return Vec<T>s. It's complicated though, since not all T types can interchange cleanly. Might be okay to restrict it to bytes.

In the meantime, it should be possible to define your own custom vector type that has an API exposed over FFI. That's the solution for the general case anyway.

@nciric
Copy link
Member

nciric commented Jan 20, 2022 via email

@CBenoit
Copy link
Contributor Author

CBenoit commented Jan 21, 2022

The only missing piece is a way to return a buffer of bytes (basically something like the DiplomatWriteable, but using std::io::Write instead of std::fmt::Write I guess).

Yeah I think we need some kind of DiplomatWriteable-like thing that allows one to return Vec<T>s. It's complicated though, since not all T types can interchange cleanly. Might be okay to restrict it to bytes.

Yeah, restricting to bytes (and maybe other primitive types) sounds like a good stopgap. Not urgent, since one can work around using a custom vector type as you mentioned, but still nice to have.

I can't commit to maintaining this backend, and I may still be making changes to the rest of the code here: I'm kinda fine with landing this here with the understanding that the backend may occasionally break (which I can always tag you in for). Does that seem okay to you? In general Diplomat is still kinda unstable so users should be installing specific git hashes (or cargo versions) anyway. Over time this should become less of a problem, it's just that right now there are still missing features and in the process of adding these features I can't (at the moment) commit to making sure the dotnet backend works too.

Another option is to create a separate dotnet repo that lives underneath rust-diplomat where the tool lives. Eventually I would like all diplomat plugins to be convenient to write this way, but that might not be the case right now, so I'm okay with adding more backends here for now, and it will certainly be the most convenient for you if it's in this repo for now.

(We may have more bandwidth to work on dotnet ourselves in the future, it's certainly something we'd like to support, and we're happy to make space for this backend to be worked on right now as well, just that we may not be able to commit to working on it too)

Sure, this sounds good to me 👍
As you said, I'll be working with a specific git hash, update when appropriate and send PR to fix breakages.
I was aware I would have to maintain the backend mostly myself for a while when I started working on it. It's some work but that's still a win considering I can now basically write a thin Rust layer and get idiomatic C# bindings for free. Writing wrappers by hand, while not complex, is still a pretty repetitive, time-consuming and somewhat error-prone task. As a bonus, I also halved the size of my Rust-side ffi modules thanks to diplomat macros. Great potential 😄

I'm happy to see you guys are interested as well!

@Manishearth
Copy link
Contributor

Manishearth commented Jan 21, 2022

Sounds great! Once this is ready and lands I'll be sure to tag you (but not block on you) for any changes to the dotnet module. I may feature-gate the dotnet module and add a separate CI job that does dotnet generation and testing if it ends up breaking often, but I suspect that may not be necessary.

@Manishearth
Copy link
Contributor

Yeah, restricting to bytes (and maybe other primitive types) sounds like a good stopgap. Not urgent, since one can work around using a custom vector type as you mentioned, but still nice to have.

If you'd like to add such a feature I'd be happy to help out! Writeable is certainly a bit annoying implementation-wise so duplicating it is probably similarly annoying, but maybe not too bad.

@CBenoit CBenoit force-pushed the csharp-backend branch 6 times, most recently from 8c19b1f to ff5afc1 Compare January 22, 2022 00:35
@CBenoit CBenoit marked this pull request as ready for review January 22, 2022 00:39
@CBenoit
Copy link
Contributor Author

CBenoit commented Jan 22, 2022

Current state: C# bindings for feature_tests are correctly generated. I ported C++ unit tests to C# xunit, and got everything green.
I consider the MVP ready to be merged!

I may feature-gate the dotnet module and add a separate CI job that does dotnet generation and testing if it ends up breaking often, but I suspect that may not be necessary.

This sounds good.

Yeah, restricting to bytes (and maybe other primitive types) sounds like a good stopgap. Not urgent, since one can work around using a custom vector type as you mentioned, but still nice to have.

If you'd like to add such a feature I'd be happy to help out! Writeable is certainly a bit annoying implementation-wise so duplicating it is probably similarly annoying, but maybe not too bad.

Great! I'll give it a try for my next contribution then 😄

@CBenoit CBenoit force-pushed the csharp-backend branch 6 times, most recently from 3be0548 to 0bcdbd8 Compare January 24, 2022 18:16
Copy link
Member

@nciric nciric left a comment

Choose a reason for hiding this comment

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

Should we be checking in:
.editorconfig, .gitignore?
Anything under Generated (I assume those files are a product of running the diplomat-tool c# command.

@Manishearth
Copy link
Contributor

We should check in any .gitignores in the generated folder to get rid of build artifacts, and should ignore editor configs.

Most non-build artifact files under example/ and feature-tests/ should be committed. It should be possible to run the tests without regen

@CBenoit
Copy link
Contributor Author

CBenoit commented Jan 25, 2022

I rebased on main to fix conflicts and removed the .editorconfig file from the dotnet project

@Manishearth
Copy link
Contributor

Thanks for your patience (been rather busy this week), I've started reviewing: For future changes can you push them as individual commits without squashing/rebasing? We use squash merges and GitHub isn't good at remembering review state between rebases.

If you need to rebase over main it's fine to do that, but please do that as a separate step.

@CBenoit
Copy link
Contributor Author

CBenoit commented Jan 28, 2022

Thanks for your patience (been rather busy this week), I've started reviewing: For future changes can you push them as individual commits without squashing/rebasing? We use squash merges and GitHub isn't good at remembering review state between rebases.

If you need to rebase over main it's fine to do that, but please do that as a separate step.

Oh, sorry if i caused any inconvenience. I’ll refrain from squashing my commits next time 👍
Also, since PR got big enough already, I think I’ll only add commits addressing change requests and open new PRs for anything new. Smaller changes are easier to review after all. Thank you for your time!

@Manishearth
Copy link
Contributor

Oh, sorry if i caused any inconvenience. I’ll refrain from squashing my commits next time +1

Oh, no worries, I hadn't started yet and planned to let you know when I did

Copy link
Contributor

@Manishearth Manishearth left a comment

Choose a reason for hiding this comment

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

Looking pretty good so far! I haven't done a thorough review but I don't think I'll be able to do one and I'm happy to land this without that level of review since we discussed potentially disabling this if things break.

One common thread here is the handling of opaque/non-opaque types, which has cropped up a couple times. Might be worth discussing further.

(I would like to eventually get all my thoughts on this documented in the diplomat book so that backend writers have it easy, but I don't have time for that right now)

tool/src/dotnet/Runtime.cs Show resolved Hide resolved
tool/src/dotnet/conversions.rs Show resolved Hide resolved
tool/src/dotnet/idiomatic.rs Outdated Show resolved Hide resolved
feature_tests/src/ownership.rs Outdated Show resolved Hide resolved
@CBenoit
Copy link
Contributor Author

CBenoit commented Jan 30, 2022

Looking pretty good so far! I haven't done a thorough review but I don't think I'll be able to do one and I'm happy to land this without that level of review since we discussed potentially disabling this if things break.

Yes, as long as I didn’t break anything for other backends it should be safe! I’m pretty satisfied with a general review for stuff like how opaque/non-opaque types are managed.
(I also have plans for refactoring the dotnet backend itself, especially thinking about future support for tera templates, so I don’t even expect the detailed implementation to remain in it’s current state for long)

One common thread here is the handling of opaque/non-opaque types, which has cropped up a couple times. Might be worth discussing further.

I’ll indeed revisit non-opaque types following your comments 👍

My understanding so far: non-opaque structs should be as dumb as it comes: fully managed by C#, no destructor. Since destructor do not need to be run, I don’t even need to manually allocate the raw struct on heap like I did in current implementation (it was mostly done for simplicity assuming destructor could be run… because I didn’t want to manage Raw.MyStruct along a Raw.MyStruct* depending on whether it was allocated by Rust or copied to C#).

I’ll also remove the "move semantic" from generated C# classes.

@Manishearth
Copy link
Contributor

My understanding so far: non-opaque structs should be as dumb as it comes: fully managed by C#, no destructor. Since destructor do not need to be run, I don’t even need to manually allocate the raw struct on heap like I did in current implementation (it was mostly done for simplicity assuming destructor could be run… because I didn’t want to manage Raw.MyStruct along a Raw.MyStruct* depending on whether it was allocated by Rust or copied to C#).

Correct. WASM does need to allocate the raw struct because it needs a place to read it from (a quirk of how WASM memory works), but you do have a stack-able Raw.MyStruct anyway so you don't need to allocate anything at all.

As a followup we probably should tighten some of the restrictions here.

This test was taking references to non-opaque structs.
However, Diplomat doesn't allow this pattern and only opaque types can
be passed by reference.
@CBenoit
Copy link
Contributor Author

CBenoit commented Feb 2, 2022

Okay, I think I addressed all current concerns.

  • Non-opaque structs are now plain old data (POD) with no Drop/Destruction logic whatsoever.
  • Move-semantic in generated C# bindings is not a thing anymore.
  • Added a few comments when appropriate.
  • Fixed tests that were taking references to non-opaque structs.
  • Opened a few follow-up issues.

Let me know if there any remaining concern!

Copy link
Contributor

@Manishearth Manishearth left a comment

Choose a reason for hiding this comment

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

minor issues.

I think in the long run C# non opaques will not be able to cache the full struct locally since they'll also support references to opaque types and such. But it's fine for now

struct MyStruct {
a: Box<MyStruct>,
a: Box<MyOpaqueStruct>,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think this needs to be & MyOpaqueStruct and we can later enforce that non opaque structs are fully Copy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I changed that

@CBenoit
Copy link
Contributor Author

CBenoit commented Feb 2, 2022

minor issues.

I think in the long run C# non opaques will not be able to cache the full struct locally since they'll also support references to opaque types and such. But it's fine for now

Absolutely, I'll open a follow-up issue about that.

Except cpp one because it currently panics
@Manishearth
Copy link
Contributor

I think we're good now! Thanks for all this work! We've found a bunch of followups to fix, which I might take a stab at if I get time, but if you plan to pick stuff up please leave a comment so we don't end up duplicating work 😄

@CBenoit
Copy link
Contributor Author

CBenoit commented Feb 2, 2022

Just letting you know: I also changed tho pointer_types test for dotnet and js backends (cpp currently doesn't support structs so I ignored that)

@Manishearth
Copy link
Contributor

CPP does support structs, fwiw. But that's fine, I think it might not support refs-in-structs yet

@CBenoit
Copy link
Contributor Author

CBenoit commented Feb 2, 2022

Sorry I indeed meant refs-in-structs (forgot to re-read myself).

I think we're good now! Thanks for all this work! We've found a bunch of followups to fix, which I might take a stab at if I get time, but if you plan to pick stuff up please leave a comment so we don't end up duplicating work smile

Great! Definitely going to let you know! 😄

@Manishearth Manishearth merged commit b971915 into rust-diplomat:main Feb 2, 2022
@CBenoit CBenoit deleted the csharp-backend branch September 29, 2022 20:12
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

3 participants