-
Notifications
You must be signed in to change notification settings - Fork 28
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
Proposal: User-defined native modules #52
Comments
I'm assuming that by "native-code" you mean code that the user can write in C/C++ but use in Wren ? Akin to how Python uses libraries written in C where performance is important ? (Sorry, I'm unfamiliar with those terms) While I think it's important to eventually offer that, I'm not sure it's the team's main focus right now. Performance does not seem to be too much of a concern, as it's already pretty good. However, I'm sure that if you wrote it and submitted a PR, they would be delighted to review it. I don't think you can go wrong with this, and I agree that it would be pretty nice even when embedding wren in other applications. I can imagine a game benefiting from an extra boost in the scripts where it's needed |
Another nice thing to work on in terms of performance would be the ability to save and load bytecode. Bypassing the front-end of the interpreter would surely give a nice boost. But there are again multiple matters at hand and it's not as easy as it sounds |
Performance are a nice side benefit, but they're certainly not the main concern. How do you control the mouse in Python (or Wren)? Well, you have no way except native extensions. Native code is not only for performance, but also to communicate with another native code, often of the underlying platform (i.e. OS) that you have no other way to talk to, but not only: can you implement libuv in wren, given the raw API of the OS? yes, but it will be effectively reinventing the wheel. Many code was already written, and any language needs a way to use it. Native code is the bridge - you can communicate even between Python and Wren with native code. Of course we can theoretically create bindings for any language, but practically it's not possible. The only way is to build a bridge from every language to one pre-agreed language, and the only one that meets this criteria is C (or sometimes C++, like with V8). |
And why I don't just send a PR, let me cite you the official docs:
This is a significant change, and moreover, it includes design choices. I don't want to take the responsibility, nor to design Wren - Bob does the job best. I'm just proposing my ideas, based on my personal and professional experience and what was made in other programming languages, and suggesting help with their implementation. |
And a last side note: when the VM implementation is good (and it is), native code generally slow down the perf. I saw V8 benchmarks where trials to native-ize the code just made it slower. This is because the communication between the VM and the native code adds overhead. And yes, V8 does JIT and optimizes the code so the overhead will be much smaller in Wren, but it'll still be there. Maybe native code will make things faster, but the complexity doesn't worth it. Generally, it only good at expensive and CPU-intensive operations. NumPy is a great example, but I can't see Wren steps into this area. Maybe the problem is with my imagination, who knows 😜 |
I promised a last comment, but excuse me: Did you mention games? Games make use of the GPU. Can you access the GPU natively in Wren?... |
That's a very good point. I haven't looked at it, but DOME and luxe most certainly have solved that issue, so it might be interesting to look at the source.
Also a good point when it comes to proving that I haven't read the documentation enough haha. Sorry about that
This is very interesting. I wasn't aware that saving/loading bytecode would actually make interpreters slower. I'm going to look into it. I was almost certain that this was one of the strategies used by CPython, but I guess I understood that wrong.
That's true haha. Again, I wonder how DOME and Luxe do it. |
@CohenArthur I took a look at DOME: It does not solves this issues. It does contain native module to control the mouse etc., but does not contain an ability to use user-defined native modules. And that's important, because obviously you cannot provide a builtin module for anything the user will like to do. This is the reason why languages contain bunch of useful modules, and let ecosystem to complete it. And about GPU: I don't know whether they make use of it, but if so, they can implement it just as C code... |
DOME does have a mechanism for this, actually, but it's not a standard feature. You can link to arbitrary compiled DLLs via libffi, but it requires you to create a special API spec for any structs and function calls you need. Further more, DOME is designed so that it's fairly easy to modify, so you can add your own modules if you really need to. |
OK. Now I read DOME's docs, and you can use the I strictly prefer my suggestion because it is natural to the Wren user as it's identical to how you embed Wren (mostly), and can be used to emulate the DOME's method. For example, CPython uses C extension similar to what I proposed, but also has the built-in module |
@munificent or @ruby0x1 ? |
An approach to FFI I tried in DOME before libffi was to use a DLL with a well defined interface for providing Wren module source and setting up foreign method bindings for the VM, but these are all problems a Wren VM user has to solve themselves. I'm not sure it's the job of the VM to have this built in, as it's very application specific. A downside to this approach is you need to compile a special binding module in order to use other people's DLLs, which is less convenient. That's why I took the libffi approach, because you could work with arbitrary DLLs. |
As I said, one that have native modules support can build a module similar to libffi (Python's ctypes is an example). I've already specified the downsides of your approach, which are why I prefer mine. A simple proof that it's better will be to inspect other languages: all languages I know uses the DLL approach. Of course the VM should not handle this which is why I opened this issue in the CLI repo 😄 It's explicitly stated in my first comment (moreover, if the VM would expose such a method it'll be a security hole, since a Wren script in, say, your word processor, will have access to the full filesystem etc.). |
Sorry, still getting used to the CLI/VM split. Having reviewed your suggestion again with a clearer head, it's almost exactly what I had experimented with in DOME, except a nicer interface and better explained. I was thinking about the case where you wanted to integrate a library such as libcurl or similar. You'd need a precompiled DLL for that, and a seperate one for your Wren-libcurl bindings, which felt excessive to me. The libffi does away with that approach, but you do lose the potential for type safety. |
I already answered you two times before: first, you can implement libffi with native modules whilst the opposite is not true. Second, speed. Third, type safety. Fourth, access to Wren types ( And thanks for the compliments 💛 |
I assume this would be fully compatible with foreign classes as well since you'd just add
This looks pretty great. 💛 I like that it gives us a way to de-couple things a bit more and could take the pressure off of what belongs in the standard library vs what can be provided by the ecosystem. I was imagining things that "touch the OS" MUST be compiled into the CLI directly, but this entirely changes the dynamic. I dunno about the linking/compiling/loading DLL side of this but it sounds like you know how that would work. Ideally it'd be easy to compile these modules (and eventually perhaps we'd have some tooling for installing them)... once compiled the source and binaries would just need to be placed in a place where they could be found - as in the proposal in #78, correct?
Ditto. ❣️❣️❣️ |
This is how the current structures are setup in the CLI. I'd suggest also adding a fifth field: |
We discussed this on channel and it seems this complexity may not be necessary. We can just overload the module loader in the CLI itself by adding the library name to the module prefix:
The library itself ( This statement would result in:
I was originally thinking that libraries would hold SINGLE modules but it does seem more flexible if a library can consist of many modules... ie, So I've been looking at the minimal changes to CLI to make this happen and I have added the concept of libraries: typedef struct
{
const char* name;
ModuleRegistry (*modules)[MAX_MODULES_PER_LIBRARY];
} LibraryRegistry;
static LibraryRegistry libraries[MAX_LIBRARIES] = {
{ "core", &coreCLImodules},
{ "added", &moreModules},
{ NULL, NULL }
}; So what was So the shared library would need only to export it's Now all I need is a compilable shared native-module to test this with. Throwing ball back to you. :) It doesn't need to be much, just a class with a foreign C function that printed some output to the string would probably be sufficient? |
I can do that, through it's definitely not something to push into production. We need growing arrays. I'll do that in some hours. Don't have a mind for that now, sorry. |
No rush!
That's fair long term. I'm not sure what "production" means in this context... I'd say this could be merged with reasonable static limits for starters - with a todo to make it dynamic later. The CLI currently has static limits for everything. This type of dynamism has tremendous utility - even if it was capped. Would growing arrays be a reasonable requirement for 1.0, absolutely. :-) Perhaps that's all you meant, unsure. I think if this landed in 0.4 or 0.5 with limited that'd still be great. Perhaps some of the code for this already exists in Wren proper and we could just pull over the "magic dynamic growing array" code from there? I feel like what I'm doing here is helping validate your proof of concept. I really don't love C (though I'm getting more familiar with it again). I'm not really productive/effective in C. My module resolver is all Wren. :-) I think the interesting/hard piece here is the actual DLL loading/registration piece. If a Wren resolver isn't workable then someone can write the whole thing in C - it just won't be me. :-) I'll muddle thru the DLL loading and registration though if it's clear cut and then at least we'll have something to test/play around with. This also touched on #78 as we need somewhere to load these libraries from - though I suppose they also could be local. |
I can do that when I'll find time. It also has some design difficulties (for example, exposing libuv API). But yours is good as a PoC. |
It doesn't need to be exposed (as in foreign), all the native module stuff (or at least the DLL/registration pieces) would be purely on the C side of things. Unless I'm misunderstanding you? |
@ChayimFriedman2 It was all much easier than I thought (thanks to premake). I have the whole stack working. I'm loading a compiled dynamic library into the CLI dynamically. And if we assume (for now) that the module code also lives inside the DLL then they are 100% self-contained - so you don't need to have any // import time module from the essentials library
import "essentials:time" for Time
System.print(Time.now())
System.print(Time.highResolution()) https://github.com/joshgoebel/wren-essentials You can build the library. I will try to clean up and push my |
Ok, I removed all the work I was doing on custom resolvers and left just the binary library stuff: https://github.com/joshgoebel/wren-cli/tree/binary-libs The key portions of interest in if (pathIncludesLibrary(module)) {
loadSharedLibrary(libName); void loadSharedLibrary(char* libName) {
uv_lib_t *lib = (uv_lib_t*) malloc(sizeof(uv_lib_t));
int r = uv_dlopen("lib/libwren_essentials.dylib", lib);
if (r !=0) { fprintf(stderr, "error with dlopen"); }
registryGiverFunc registryGiver;
if (uv_dlsym(lib, "returnRegistry", (void **) ®istryGiver)) {
fprintf(stderr, "dlsym error: %s\n", uv_dlerror(lib));
}
ModuleRegistry* m = registryGiver();
registerLibrary(libName, m);
} The path is hard coded now, but it all works. I've been using it inside my
|
Following a long conversation over on wren-lang/wren#868 I've ported @mhermier amazing mirror module work into my proof of concept |
I do wonder if the "module registry" data-structure approach used in the CLI or more direct This would be very similar to the approach taken by Core for it's optional modules. I guess nothing mandates they do it this way, just it's the pattern that both |
Any language needs its way to run native code. As a language for embedding, Wren gives you the flexibility to control the modules you load through the config
bindForeignMethodFn
andbindForeignClassFn
.However, the CLI is meant to run programs and not for embedded scripting. As such, it needs a way to load user-written native modules.
I suggest the following:
Module.importNative()
, which will take a normal module path.wrenInitModule()
.WrenNativeModule
. This struct will be distributed in a .h file, and any native module will include it.classes
array of typeWrenNativeClass
, and its length (alternatively, we can pass an entry filled withNULL
s at last. This is the strategy used in CPython).methods
array of typeWrenNativeMethod
and its length (ditto).Module.importNative()
will append these arrays to vectors ofWrenNativeClass
andWrenNativeMethod
, accordingly.bindForeignClassFn
andbindForeignMethodFn
, the CLI will look at its defined native methods and return the one appropriate, orNULL
if not found.A complete example:
Let's say we want to implement a method that squares its argument, but as native.
Let's start with the C code:
Now, define
myMath.wren
:Now, compile the C code to a dll or so under the name
myMath-native
. Our Wren code is serving a bridge between Wren's foreign interface and the user code, so it can use it just as if it would be a normal Wren module:Once the discussion around this topic is finished, I would like to implement this myself.
Wren is SO AMAZING!!!!! 🎉 🎉 🎉 Best regards.
Edit:
We'll still need one special case handling in
bindForeignMethodFn
- theModule.importNative()
function itself...The text was updated successfully, but these errors were encountered: