-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Dynamic native modules v2 #1627
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
Conversation
Ok, why not call it "loadable native" after all? Otherwise, I looked thru code. In my current state, I can't understand it well ;-). Well, I see that it reuses a lot of machinery from persistent bytecode and native codegens, which causes you to select "persistent native" term and ".mpy" extension. I'm not sure I agree with either. Regardless of underlying implementation, cached (byte)code and code purposely written in C are 2 rather different things, and I don't think they should be mixed up on terminology level and external user interface levels. I'd suggest ".mpd" extension for compiled modules (following ".pyd" for CPython). Unless you have arguments why it should be .mpy. For example, I'd +0.5 argument that it saves a file look up. But we'll definitely end up confusing users (for example, "cached" .mpy can always be deleted and will be regenerated from source, not so for "implemented in C" .mpy). |
Some random thoughts: nice way to get forward with this would be building some real-world code with this. Like sqlite ;-). And no, examples/modx/modx.c here in the patch doesn't give warm fuzzy feelings - it's very different from how built-in modules are coded. I'd set that as one of main goals to be able to code modules so they were able to be used both statically and dynamically (efficiently!). That will certainly require resurrecting my idea of having custom preprocessor for stuff like QSTR(foo) (for dynamic module that would resolve to something like __qstr_arr[QSTR_foo] and code to init __qstr_arr). |
I went to a lot of effort with this new version to provide this feature. See these macros:
That's how it works at the moment. For dynamic modules everything must go through a table: qstrs, constants, and calls to the runtime (unless you want to provide a proper linker in uPy to link external symbols, but that's going to require a lot of code, and specific code for each arch). |
9167980
to
1cc81ed
Compare
With this option enabled MicroPython supports loading of .mpy files that contain code compiled directly from C (ie dynamic loadable modules). Position independent code is enabled by having 2 "link" tables: one for runtime functions and constants, and one for qstrs that are local to the loaded code. The runtime function table is shared with that used by the native emitter. The qstr table needs to be populated by the loaded module on loading of this module.
To build just type "make". To build for Thumb2 target, use "make CROSS=1". Then modx.mpy is ready for importing.
162a024
to
b02cecc
Compare
This was rebased on top of current master and force pushed. @aykevl you may want to try it out. |
Thanks! It doesn't seem to work out of the box (probably due to the age), I'm now fixing a few things. Will share when I have something working. |
Thank you for the Python 2 fix. I use Debian which uses Python 2 by default. It now works on my side. |
Smaller microcontrollers may want the former without the latter.
For an MCU with no filesystem and if qstrings are not used, is it possible to load new modules or native code by modifying the table If it's not possible now, could this become possible with some linker file modification or is this fundamentally impossible for micropython? Or fundamentally impossible for any executable? I can imagine that with qstrings, this would be impossible or very hard because the qstring table would not be populated with the new module. |
So what would be the use case for dynamically loadable modules, then? Why not just integrate them into the ROM? It might be possible if you make |
We want to give our customers the ability to extend the micropython installation but only allow them to write to certain areas of flash (so they can't brick the module). The micropython installation is very integrated into our product so to flash everything as part of a monolithic I'm not saying it's a compelling enough feature for the community here to spend oodles of time on it; I just want to know whether it's possible to do this assuming you don't use qstrings. Though, if all it takes is adding a |
Ah, that's certainly a use case, though I wonder if such a feature could be useful for open source projects. But I'm not a maintainer so can't say anything about whether it will be done. I think the harder problem is disabling qstrs. I suspect they're integrated so deeply they can't simply be disabled - e.g. they're used for fast comparisons between any two strings. |
So, I'm looking into this again. And for the life of me I cannot understand what were the ideas and requirements which went into this "persistent native" stuff. The only reason I may imagine is desire to prototype something for saving @micropython.native/@micropython.viper code into .mpy. Because there's no other explanation why would all this over-engineering, all these parallel hierarchies of "persistent native" functions and types be required to implement just "dynamically loadable modules". (Heck, it's not clear why they would be needed even for persisting - there's already types for native and viper functions). Perhaps I'm just stupid. But real fun just begins. The included modx.c doesn't have any (global) variables. That's not realistic, any more or less non-trivial code will have variables, data structures, etc. Trying to add those, I see [rip+xx] in the .o file. But within the produced .elf, there're direct addressing/immediate values instead! Stupid modern compiler smartasses! Ok, adding Looking at the generated code again, the persistence worthy of Sisyphus becomes apparent:
Look how carefully it loads pointer to the function table again and again, again and again - instead of just caching it in a register! After jerking back and forth, this one becomes apparent too - wonderful semantics of the C language, where every function is suspected of being able to modify a global. Wait, this should, work, right:
? No! It's 2019, but in C, it's possible to only declare var as ever-changing (volatile) or fully constant-down-to-being-immediate-value. Ok, after some thinking (which included abusing .got table to do the needful), solution was found of declaring it Summing up: the idea to use -fPIC seemed bright, but actually is brittle like hell. And I'm looking at the most popular arch. Something like Xtensa will just crumble down. Anyway, I'm proceeding ;-). |
Heh, that was done by elftompy.py (of course, I'm changing module format). Poor binutils slandered by me! |
Some more notes on the design of dynaloaded modules format: #4535 (comment) |
This PR is well and truly superseded by #5083 |
Now that persistent bytecode is supported (well, at least the beginnings of it are there, still pending #1619) the dynamic-native-modules branch is stale, since it used a different format for the loadable .mpy file.
This PR improved upon dynamic-native-modules branch by adding support to load .mpy files that contain position-independent code compiled from C (or any other language). Features of this new version are:
At the moment things are working and you can test it by building modx.mpy in the examples/modx directory (just type "make"), and then building unix port, and then doing "import modx" (make sure modx.mpy is in the current directory, or the module search path).
Building modx example with "make CROSS=1" will allow modx.mpy to be loaded on Thumb2 arch (eg pyboard, wipy).
As usual, naming things is hard. There is now the MICROPY_PERSISTENT_NATIVE config variable to enable this dynamic loadable C module feature, along with some structures and functions named with "persistent_native". It's a long name, but I can't think of anything better. And it's only going to get more confusing because one day there will be support for persistent native code generated by the native emitter (eg @micropython.native or @micropython.viper). Anyway, what we have here is a start and we will need to say that things are subject to change.