-
Notifications
You must be signed in to change notification settings - Fork 6
Description
This is an explanation of the rationale for the REN_STD_FUNCTION macro for reference. Here were the original notes:
While preprocessor macros are to be avoided whenever possible, the particulars of this case require them. At the moment, the only "identity" the generated parameter-unpacking "shim" function gets when it is called is its own pointer pushed on the stack. That's the only way it can look up the C++ function it's supposed to unpack and forward to. And C/C++ functions don't even know their own pointer!
So what we do here is a trick. The first call to the function isn't asking it to forward parameters, it's just asking it to save knowledge of its own identity and the pointer to the function it should forward to in future calls. Because the macro is instantiated by each client, there is a unique pointer for each lambda. It's really probably the only way this can be done without changing the runtime to pass something more to us.
It should be noted that if you try to re-use a lambda to make a function value twice this way, you will be getting another spec block. It is technically possible to get multiple function values for the same lambda with different specs (though this is probably not what you want; you should probably find a way to create functions only once).
And here was the macro code:
#define REN_STD_FUNCTION \
[](RenCall * call) -> RenResult {\
static ren::internal::RenShimId id = -1; \
static ren::internal::RenShimBouncer bouncer = nullptr; \
if (call) \
return bouncer(id, call); \
if (id != -1) \
return REN_SHIM_INITIALIZED; \
id = ren::internal::shimIdToCapture; \
bouncer = ren::internal::shimBouncerToCapture; \
return REN_SHIM_INITIALIZED; \
}
The reasoning is accurate...and this really is probably is the only way to do such a thing with a C-style function (if the caller is not passing you the address or you don't have it some other way).
It turned out that Rebol was passing in the address--though I didn't know it at the time of coming up with it...or at least not until partway through it. It was something you could get at by a negative index, hiding behind the function parameters. At the time I thought this was incidental and something a code reorganization might just as easily remove. And remarks from DocKimbel indicated he wasn't sure that Red would pay for the overhead of making the function value being called available as part of the stack protocol.
Over time, it came to be apparent that in order for a running function to not be garbage collected in mid-call it would have to be reachable somewhere. Since functions can be generated and applied spontaneously, and stored nowhere but in the fact of the current invocation, it makes sense to line up the current invocation somewhere in the place already being "held alive" in the root set. So being able to sniff the function pointer out from the call makes sense as a feature for Rebol-and-Red style implementations.
Other push-coming-to-shove reasons motivate taking out the bouncing. The REN_SHIM_INITIALIZED fake return value is really only used as a sanity check that the first call did its capture properly. But it is going away, as Rebol natives are likely no longer going to have an integer return code and be void. So the only reason to keep it would be "just in case there's no way for Red to give us the pointer from the call frame where variables are coming from". As mentioned that's likely a necessity--if Red can't do it today, it probably has a GC bug.
Note: Initially I thought this meant REN_STD_FUNCTION could be removed, but it can't... just the bouncing bit.