brombres / Rogue

Rogue programming language. Ergonomic, object-oriented, high-level, multi-platform, compiles to C.
MIT License
29 stars 8 forks source link

Routines aren't super easy to call from C/C++ #86

Open MurphyMc opened 3 years ago

MurphyMc commented 3 years ago

A long while ago, I think I either suggested or changed the code for routines so that they're methods on Global. A downside here is that from a C perspective, they take a reference to the Global singleton, which makes them harder to call from C/C++.

Probably lots of ways to fix which don't really cost anything.

AbePralle commented 3 years ago

I thought it would be easy too but I messed around for a few minutes and it's trickier than it seemed. Consider:

test( 5 )

routine test( n:Int32 )
  trace n
endRoutine

Compiles to:

RogueGlobal__test__Int32( ((RogueClassGlobal*)ROGUE_SINGLETON(Global)), 5 );
# equivalent to
RogueGlobal__test__Int32( ROGUE_SINGLETON(Global), 5 );

We can't do a macro like ROGUE_CALL_ROUTINE( RogueGlobal__test__Int32, 5 ) due to macros not allowing a variable number of arguments.

We can't make a function like Rogue_call_routine( void* fn, ... ); because we'd lose the function type.

Only thing I can think of would be to generate a call forwarding function for each routine, but it doesn't seem worth the ROI.

MurphyMc commented 3 years ago

I hadn't thought of the macro approach, but there are variadic macros! They've historically had a problem in many cases when being called with zero arguments, but GCC has long had an extension to fix it, and it's now been standardized in C++20 or something, and MSVC supports it. So maybe that's a viable option? See: https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

(Update: Immediately after having posted the above, the downside of the macro approach occurs to me: they're not useful for using the Rogue routine as a callback for some C/C++ API that wants a function.)

I've lost a lot of context, so I may be forgetting something obvious, but... are these methods being generated as normal methods on the Global singleton? What if they were global methods on the global singleton? No hidden argument in that case, right? (Even if this works, I have no idea what it might break.)

AbePralle commented 3 years ago

Oh - that's the solution! Routines are currently object METHODS of Global, but indeed they work perfectly well as GLOBAL METHODS of Global. I'll make that change sometime soon.

MurphyMc commented 3 years ago

Does this change anything with respect to using the fat arrow operator on such methods from within Rogue? That's the thing that occurs to me might lead to breaking something somewhere.

AbePralle commented 3 years ago

Good call again! I remember now that's the reason we made them object methods instead of class methods: if you call Global.xyz then you can get a function reference with Global=>xyz.

It's prolly still worth switching back to class methods and then doing more work on that function reference operator to automatically generate a Function object (when necessary) that will call the global method.

MurphyMc commented 3 years ago

Just to see if I'm understanding, the proposed solution is:

  1. We change routines to be GLOBAL METHODS on Global
  2. If you ever apply fat arrow to a GLOBAL METHOD, generate a simple function which just wraps a call to the target and have that be the result of the the fat arrow operation

Is that it?

What does fat arrow actually do on a GLOBAL METHOD now? Not compile? If fat arrow on GLOBAL METHODS currently just doesn't work, this seems like a win anyway (since it'd work with any GM, not just routines).

AbePralle commented 3 years ago

Looks like a fat arrow on a global method works just fine already (!). So I'll try just switching routines to be GLOBAL METHODS and maybe it will be as easy as that.

AbePralle commented 3 years ago

Fixed here: https://github.com/AbePralle/Rogue/commit/cd4e3aae453130777abdce8d2d07a2732a038ab8

Works great!