-
Notifications
You must be signed in to change notification settings - Fork 256
C# future simple codegen #1357
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
base: main
Are you sure you want to change the base?
C# future simple codegen #1357
Conversation
b14b14b to
c3fce61
Compare
|
Thanks for doing this, @yowl! I'm planning to review it by the end of the week. |
dicej
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this! Looks like a good start; please see my comments inline.
| let op = &operands[0]; | ||
| self.interface_gen.add_future(self.func_name); | ||
|
|
||
| results.push(format!("{op}.Handle")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FutureReader should probably have a TakeHandle() method that zeros out the handle field (and asserts that it wasn't already zero) before returning the original value so that the application won't accidentally try to use the no-longer-valid handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've done this, but not sure I understand in what situation it would be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I meant not only that we should add a FutureReader.TakeHandle() method but that we should use it here (instead of just reading the Handle field) for the Instruction::FutureLower { .. } case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| results.push(format!("{op}.Handle")); | |
| results.push(format!("{op}.TakeHandle()")); |
Combine FutureReader and FutureWriter to AsyncSupport. Start the process of adding futures per type
|
@dicej Hi, I think I've covered the points from the review now, was a bit of a change for the generic refactoring, sorry about that churn. |
dicej
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for continuing to iterate on this, @yowl, and sorry for the delayed review.
Looks good overall; just a few more comments inline.
|
|
||
| public static WaitableSet WaitableSetNew() | ||
| {{ | ||
| var waitable = Interop.WaitableSetNew(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: consider naming this waitableSet or handle. The name waitable is a bit confusing since that term means something else in the component model.
| // TODO: Generate per type for this instrinsic. | ||
| public Task Read() | ||
| { | ||
| // TODO: Generate for the interop name and the namespace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should check here to see if Handle is zero and throw an exception if so.
| void Dispose(bool _disposing) | ||
| { | ||
| // Free unmanaged resources if any. | ||
| vTable.DropReader(Handle); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should only call this if Handle != 0
| // TODO: Generate per type for this instrinsic. | ||
| public Task Read() | ||
| { | ||
| // TODO: Generate for the interop name and the namespace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should throw exception if Handle == 0 here.
| void Dispose(bool _disposing) | ||
| { | ||
| // Free unmanaged resources if any. | ||
| vTable.DropReader(Handle); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should only call this if Handle != 0
| // TODO: Generate per type for this instrinsic. | ||
| public Task Write() | ||
| { | ||
| // TODO: Generate for the interop name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should throw exception if Handle == 0 here.
| void Dispose(bool _disposing) | ||
| { | ||
| // Free unmanaged resources if any. | ||
| VTable.DropWriter(Handle); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should only call this if Handle != 0.
| .collect::<Vec<_>>(); | ||
| let ty = self.interface_gen.type_name_with_qualifier(ty, true); | ||
| //let ty = self.gen.type_name(ty); | ||
| //let ty = self.r#gentype_name(ty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| //let ty = self.r#gentype_name(ty); | |
| //let ty = self.interface_gen.type_name(ty); |
Or can we just get rid of this line?
| let op = &operands[0]; | ||
| self.interface_gen.add_future(self.func_name); | ||
|
|
||
| results.push(format!("{op}.Handle")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| results.push(format!("{op}.Handle")); | |
| results.push(format!("{op}.TakeHandle()")); |
| return match kind { | ||
| TypeDefKind::Flags(_) => true, | ||
| TypeDefKind::Enum(_) => true, | ||
| _ => false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If desired, you can return true here for any type that doesn't refer transitively to an exported resource. For Go, we use this function to determine whether the type can be shared or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the tip, I had a go at this, no pun intended, but I have a question. Types are added from the import_types method, and that is where the resources are added in C#, is that where Go adds the resources as well? I ask because the `has_exported_resource`` function checks for the resource being in the export direction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My recollection is that import_types is used for types imported at the world level, but that interface-level types are added in import_interface and export_interface, respectively:
wit-bindgen/crates/go/src/lib.rs
Lines 680 to 689 in 85369ad
| let mut data = { | |
| let mut generator = InterfaceGenerator::new(self, resolve, Some((id, name)), true); | |
| for (name, ty) in resolve.interfaces[id].types.iter() { | |
| if !generator.generator.types.contains(ty) { | |
| generator.generator.types.insert(*ty); | |
| generator.define_type(name, *ty); | |
| } | |
| } | |
| InterfaceData::from(generator) | |
| }; |
wit-bindgen/crates/go/src/lib.rs
Lines 730 to 751 in 85369ad
| for (type_name, ty) in &resolve.interfaces[id].types { | |
| let exported = matches!(resolve.types[*ty].kind, TypeDefKind::Resource) | |
| || self.has_exported_resource(resolve, Type::Id(*ty)); | |
| let mut generator = InterfaceGenerator::new(self, resolve, Some((id, name)), false); | |
| if exported || !generator.generator.types.contains(ty) { | |
| generator.generator.types.insert(*ty); | |
| generator.define_type(type_name, *ty); | |
| } | |
| let data = generator.into(); | |
| if exported { | |
| &mut self.export_interfaces | |
| } else { | |
| &mut self.interfaces | |
| } | |
| .entry(interface_name(resolve, Some(name))) | |
| .or_default() | |
| .extend(data); | |
| } |
Note the logic that sorts types according to has_exported_resource.
Anyway, don't worry about this for now; this PR is big enough as it is, so we can follow up with type sharing tweaks later.
This PR adds enough code gen to support the simple-future wit runtime test. As for the async PR, this is pretty much the minimum PR in terms of future support. I've not tackled the typed canonical methods except to add a "void" implementation which is hard coded as the one to use.
Have followed the c test cases rather than the rust ones.
Also changed Export and Import in namespaces to be uppercase and moved resources and other methods to the appropriate import or export class. Some types are still produced from the import side, and have introduced a concept of a bidirectional type (enum, flags) that sit above the import/export split.
The current codegen produced is at https://github.com/yowl/wit-bindgen-simple-future