Prototype interfaces dynamic casting#21
Conversation
… inheriting types.
…deal with the allocation.
…ent type is derived from the parameter type.
…ment, and call the vtable for virtual calls.
…sions to generate code.
…ase.Method isnt called.
…s-dynamic-casting # Conflicts: # Dntc.Common/Conversion/TypeConversionInfo.cs # Dntc.Common/OpCodeHandling/Handlers/CallHandlers.cs # Dntc.Common/Syntax/Expressions/MethodCallExpression.cs # Dntc.Common/Syntax/TypeDeclaration.cs
…s-dynamic-casting
…ace is not used explicitly.
# Conflicts: # Dntc.Common/Conversion/TypeConversionInfo.cs # Dntc.Common/Definitions/CustomDefinedMethods/ReferenceTypeAllocationMethod.cs # Dntc.Common/Definitions/DefinitionCatalog.cs # Dntc.Common/Definitions/DotNetDefinedMethod.cs # Dntc.Common/Definitions/DotNetDefinedType.cs # Dntc.Common/Dependencies/DependencyGraph.cs # Dntc.Common/OpCodeHandling/Handlers/CallHandlers.cs # Dntc.Common/Syntax/Expressions/MethodCallExpression.cs # Dntc.Common/Syntax/TypeDeclaration.cs # Dntc.Common/Utils.cs # Samples/HelloWorld/Program.cs
|
@KallDrexx this is getting there! I didnt use unions… just added the interfaces to the class stucts… and the interface typedefs just have function pointers. need to check how value types implementing interfaces should work… not sure if that is broken right now… BTW how can I include some C code utility methods (dntc.h) in the generated output? |
|
This is awesome. Generally, C code utility methods are attached to types or methods that need them. So I generally would only use |
…dynamic-casting # Conflicts: # Dntc.Common/OpCodeHandling/Handlers/CallHandlers.cs # Dntc.Common/OpCodeHandling/Handlers/StoreHandlers.cs
|
This is really cool, and again you are showing my ignorance and lack of experience with C :) The only real concern is that I've kind of hoped this transpiler was mostly a "pay for only what you use" system. This seems like it'll create the interface implementations and type info records even if they aren't needed or referenced. It would be nice if the interface functionality was only attached if you actually did a real cast to the interface (or called a function requiring the interface). That being said, that's not something we can't address later.
I've gone back and forth thinking about this a bunch, and I don't really have a good solution. The type union idea only really makes sense with value types, I think it loses a lot of the appeal once reference types are involved. I had some other ideas swirling in my head but the more I think about it the more it causes complications, especially with reference counting. So the way you've embedded the interface's function pointers into the type itself makes sense. And having everything start with a |
|
I guess the only other idea I have for interfaces, is something like having the following code public interface IFoo {
int DoStuff();
}
public class Foo : IFoo {
public int DoStuff() { ... }
}convert into enum dntc_types {
Dntc_Type_Foo,
};
typedef struct IFoo {
dntc_types type;
void *instance;
dntc_reference_counter *refCounter;
int32_t (*DoStuff)(void *__this);
} IFoo;
typedef struct Foo {
dntc_reference_counter *refCounter;
} Foo;
int32_t Foo_DoStuff(Foo *__this);
IFoo Foo_To_IFoo(Foo *instance) {
IFoo interface = {
.type = Dntc_Type_Foo,
.instance = instance,
.refCounter = instance->refCounter,
.DoStuff = Foo_DoStuff,
};
ref_counter_increment(interface->refCounter);
return interface;
}
Foo *Foo_From_IFoo(IFoo *interface) {
if (interface->type != Dntc_Type_Foo) return NULL;
Foo *result = (Foo *)(interface->instance);
ref_counter_increment(result->refCounter);
return result;
}I'd really need to prototype if there are holes in this or not. My gut reaction is this somewhat reduces the complexity and dynamicness of the system, and we are able to generate these conversion methods only when we actually know that the type of conversion is required or not. However, there are probably some nuances that I'm missing where your approach is better and I'm not fully convinced yet my idea is better. One thing that's missing from your implementation is I"m not sure how we'd implement downcasting (going from interface back to the type). It seems like we'd need a reverse One thing I should note that while my goal has always been for dntc to allow C (and other languages) to host C# authored code, I'm not exactly trying to allow for full dynamicness. So I don't expect the C host code to ever define their own custom structs that implement a C# interface (and thus be able to adhere to casting requirements). I feel like that would bind the dntc generated code too much and make backwards compatibility of changes really difficult. It's already going to be non-trivial for a C host to manage reference counts of C# authored objects correctly (I'm not quite sure what I'm going to do to help make that a bit easier). So (personally) I'm ok with "manual" casting methods because we should know any possible type of cast that could occur. |
|
Actually, thinking about this more how do you envision handling casting structs to interfaces? I see in the octahedron example your code adds a field to the struct with the interface struct. That seems a bit heavy though and means that if the code is running from a C host it can't be naturally formed and passed into the C# code. You also can't just pass a C# struct into a function that takes an interface as-is, because it will need to be boxed properly to be reference counted and passed by reference where it's used. So it would really require a Box type for every type that the So maybe that's enough to adapt your solution to structs? You can't convert a struct to its interface without boxing (which I think is always the case on the MSIL level anyway), you don't cast the C# struct to the interface but the box container for it, the Box implements |
|
@KallDrexx for dynamic casting from the base type to a more concrete type or from an interface to a concrete type I can add the implementation for that, it should be fairly trivial. I guess we can implement casting methods… I will have a look at that. Regarding the boxing, yes I think perhaps that boxing struct can take care of that, I guess we can generate BoxMyStruct functions that wire everything up, your idea makes alot of sense to me. Regarding only paying for what you use… yes, I guess we need to make the dependency graph work out if a method did any casting and if a type was actually used anywhere…. rather than just blindly adding all types that could possibly be found in the code. In theory most c compilers will strip out unused functions and types, but not generating the code in the first place is nicer, helps the code be more readable. We should also not emit type information unless its needed I guess (assuming we can know that, say if someone wanted to compile 2 different assemblies with that same type, we might have to give the option to not strip type information) then again if your choosing to use features that need type information then your not necessarily likely to be on the most resource constrained systems. We should make type information sit behind a feature flag. I think my immediate plan then when I get time, is to implement casting in the other directions. "I see in the octahedron example your code adds a field to the struct with the interface struct. That seems a bit heavy though and means that if the code is running from a C host it can't be naturally formed and passed into the C# code.” I will take a look at this, yes I saw that, I think we shouldnt need to add that extra stuff there… Ill check it out :) |
|
The dependency graph should be able to figure that out via the analyze calls. That's one of the main reasons I added the analyze path to the op code handlers. So we just need to figure out what msil we need to look for. If we can get interface casting to be caught by the dependency graph, then we can focus on structs in a follow up PR, because then you can't encounter it until the Box op code is handled anyway, dntc will exception due to an unknown op code I agree with the idea that if you are working on a system that the overhead of the type info is too much then you shouldn't be using it. That's why I'm more focused on the struct overhead than on reference types, where the intent is much more that if your overhead budget is that small you should focus your c# on value types. And again, don't feel time pressured to complete anything if you are busy. We all have lives outside of this and this isn't blocking me. |
|
Fwiw I have started adding some foundational pieces to reference counting support, and to do so I've probably made some merge conflicts. Specifically I pulled down the concept you had of having the reference base type. It's not the same implementation. The way I went allows hooks for customization of what fields are included. So you can see the reference counting fields arent directly included, but are added after definition creation. In theory this will allow swapping reference counting implementations. Hopefully I didn't make integration of this or too difficult. |
…dynamic-casting # Conflicts: # Dntc.Common/Definitions/CustomDefinedMethods/ReferenceTypeAllocationMethod.cs # Dntc.Common/Definitions/DotNetDefinedType.cs
click to watch on youtube… demo!

This is madness! :)
makes this code compilable….