I was one of the people who added sound into a TI83 game. It ended up in my IceClimb game. Not particularly well done or anything, it's just there. I also didn't properly adjust the timing as the notes got higher, so it plays faster when you get higher in the level.
Turns out that it's actually 2-bit and not 1-bit. It uses a combination of the EAR and MIC jacks, and when you output through the MIC jack, it slightly boosts the EAR jack. That gives you 4 possible levels, but they are not evenly balanced.
How much data does it download off the server to run the challenge? I've made some tiny webassembly binaries before, but I've never seen Rust generate something tiny.
What do you need the executable stack for? You call using a function pointer, there's no executable read/write memory involved in using a function pointer.
A nested function requires an extra parameter for the nested stack pointer, which is passed in a dedicated register on most ABIs. You can't spell this kind of function type in C. To make a C-callable function pointer, the compiler needs to generate a little bit of code (a trampoline) that stuffs the appropriate stack pointer in the appropriate register.
That trampoline needs to live somewhere. Since the function is inherently noncallable after the stack returns, and C programmers hate it when their compiler sneaks in extra malloc calls under the hood, the compiler decides to stick the trampoline on the stack instead of heap-allocating it.
Nested functions may require a context pointer of some kind, which the caller can't supply. One way of doing this: create a thunk on the stack that provides the context pointer, and use that address as the pointer to the nested function.
It’s not a question of ISA support. If you call via a function pointer, how do you supply the pointer to the data? (It has to either be in a separate place from the function code, or the same place. One requires an ABI change, the other an executable, writable section of memory.)
> If you call via a function pointer, how do you supply the pointer to the data?
D has the notion of a "delegate", which is a (function pointer) and (context pointer) pair. This is incredibly useful, because delegates can:
1. call nested functions that need a pointer to the stack frame of the nestee function
2. call member functions that need `this` pointer
3. call lambdas
4. call COM member functions
The neato thing about this is the ABI for delegates is all the same, so a function that gets a delegate parameter will work with any of 1..4. It's one of the most used features of D.
This feature would IMO violate the contract that C allows you to specify memory layout of objects, to some level of detail (I am being a little vague about the “level of detail”).
Supporting closures, more or less, requires design decisions that are equivalent to choosing a specific layout for objects in an object-oriented language. C, as it is, makes none of these assumptions and you can translate a lot of different language ABIs into some C code (that may be clumsy). Keeping the abstraction that function pointers = pointers to entry points for functions, well, that’s frustrating for C programmers writing C programs, but extremely useful for interoperability.
At the moment we can not call nested functions or other things from other language from C, which is a pretty big hole in our interoperability story. I do not see why we need to make any design decision to specify object layout, we simply need a code pointer and static chain pair, which would then be sufficient to call arbitrary entities from other languages.
> I do not see why we need to make any design decision to specify object layout
> we simply need a code pointer and static chain pair
The code pointer and chain pair needs an object layout. You would have to pick a specific layout, and it would not be compatible with other languages that have a different layout.
Right now I can do this:
struct a {
void (*fun)(void *ctx);
int data1;
int data2;
};
struct b {
void (*fun)(void *ctx);
void *ctx;
};
And I could call them:
struct a *aptr;
a->fun(aptr);
struct b *bptr;
b->fun(bptr->ctx);
There is only one neutral, maximally compatible option here—which is to have the function pointer separate from the arguments you want to pass in, and pass them in explicitly.
If you add closures to C you are making compatibility worse, not better.
Except doing this manually does not allow me to directly call a C++ lambda, a Go closure, an Ada closure etc which I can not even express in C. So compatibility can not become worse, it is already maximally bad. And even where you can build a compatible solution in C, there are now different choices. Your examples already directly shows this contradicting your claim that here is only one option.
Adding such a type as a vocabulary type would fix all this. You can argue that we fix an object layout for a pointer pair, but this seems an acceptable trade-off to me. This seems far from your previous claim that this "requires design decisions that are equivalent to choosing a specific layout for objects in an object-oriented language." Note also that such a type can always adapt to different calling conventions of other languages by using the address of a static thunk as code pointer so it is very generic.
A C++ lambda (as essentially is the case for a Rust lambda as well) is just an unnameable class object with an overloaded call operator, which makes the closure function body itself just a regular member function ABI-wise. You're not calling these things outside of the language, because they're only really callable via the language-specific static dispatch mechanism of templates. If you need to be able to dynamically call a lambda, you're going to be stuffing them in an object that contains a pointer to the lambda object and a pointer to the function of the actual body, something that looks essentially like what GP's assertion of what a lambda ABI looks like.
You can stuff it into something such as std::function_ref which erases the Voldemort type and then it can be dynamically called without needing templates. And this could be from C or other languages if only there was a common type in C we could use.
C++ lambdas and Go closures are incompatible with each other. If you pick something that lets C call Go closures (which are fat pointers passed around), it is likely to be incompatible with whatever your C++ library is doing.
The point is that it can always be adapted to a common wide pointer type, which C could provide as a standard. C++ already has a solution for this on their ide: std::function(_ref).
C# does closures by creating a class. When you run the function, captured local variables actually live inside the object instead of in the stack. This makes use of an object, but no memory pages need to become read/write/executable to make C#-style closures happen. You just have a combination function/object pointer (a delegate) instead of a single function pointer.
Question isn't how can a function closure be represented anywhere, but how it can be represented in a way that can be aliased by a vanilla C function-pointer, which is a more specific and challenging question.
reply