-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Add support to save native, viper and inline-asm code to .mpy files #4535
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
py/persistentcode.c
Outdated
byte *bytecode = m_new(byte, bc_len); | ||
read_bytes(reader, bytecode, bc_len); | ||
// load function kind | ||
int kind = read_byte(reader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it's possible to combine "kind" with some other flags into this byte?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, since it's only 2 bits I've now combined it with the number of bytes in the bytecode/function data, like: write_uint((len << 2) | (kind - 2))
py/persistentcode.c
Outdated
mp_uint_t *ct = const_table; | ||
size_t i; | ||
if (kind != MP_CODE_NATIVE_VIPER) { | ||
for (i = 0; i < prelude.n_pos_args + prelude.n_kwonly_args; ++i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I woulnd't find comments like "first entries in const table are function argument names" superfluous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I added a few comments to the load/save functions.
tools/mpy-tool.py
Outdated
@@ -428,6 +428,9 @@ def read_bytecode_qstrs(file, bytecode, ip): | |||
ip += sz | |||
|
|||
def read_raw_code(f): | |||
kind = read_uint(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, this doesn't correspond to C code, which reads a byte.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now fixed.
I don't see anything specific to that in this PR. And it's easier said than done, there're a lot of different things to consider for that. For example, C module may want to print a Python object. But exposing all the bunch of print functions doesn't make sense, instead mp_printf() should be extended to be able to print an object, just like it already was to print qstr. And that's just one of the examples. |
24d9884
to
83c26b0
Compare
I updated and force-pushed this branch. New additions include:
|
That and this PR actually have quite a lot in common. The main difference here is that the existing native function types (native/viper/asm) are reused to implement dynamically loadable native code from another language (like C). #1627 added some constants (false, true, none, ellipsis) to the So this PR here is kind of like a rework of #1627 given all the things that have changed since then, and also includes the ability to save native/viper/asm in .mpy files. |
I pushed a commit with an example of pure C code that generates a valid .mpy file containing a C function that can be imported and executed. The outer module is bytecode and there is a single function (called |
Quickly recording comments I had in mind on Monday looking at it, but didn't write down then: So, this looks like a cute and creative way, but level of hackiness can be judged by the fact that "foo" function is not references anywhere, i.e. apprently it's just assumed that there's a single function in the module, which will end up at a given offset. And yeah, sure, it's possible to write code in that way (@Native indeed does!), and thanks for showing a way to do that in C. But I doubt utility and practicality of that. After all, a (reasonable) human uses C when compiling Python isn't enough, and they they would like to use a plethora of devices and optimizations offered by C and native C interface of the system, not code it up thru the needle's eye of a JIT compiler. And beyond that, as I mentioned in my recent comment on #1627 (comment), the starting requirement for dynamically loaded modules should be, should have always been, "allow to compile the same module either statically or dynamically [and in static case, get the same (or 95%) of efficiency of natively static module]". As we already have static modules, they dictate the source format and structure. Dyna support could rehash something (e.g., add more macro calls, or wrap a few of existing things in macros), but that's it. I have a prototype which works like that. While working on that prototype,which of course reuses your idea of own port-portable dynamod format, it occurred to me that that format won't be enough after all, e.g. it doesn't support dynamic linking itself, and one of reasons people will write modules in C is to interface with existing system libraries. So, supporting native executable/shlib format is a must for systems which have it. Let's count:
And now you propose:
Again, I'm not sure of practicality of p.4. But the C example is of course golden, as reference to someone who'll implement other types of JIT/AOT. |
Yes, it's a basic example to show that it's possible at all. It's also possible to have multiple entry points into the C code and a tool would need to be written to create a full-featured .mpy file from such a piece of C code (eg give a list of C symbols mapped to their Python name, in a macro like the existing MP_DEFINE_CONST_FUN_OBJ).
I don't see why they can't. As long as it compiles with -fPIC and -fPIE and doesn't have any relocations it will work.
On bare metal (which is really the focus of all this work) there is not really such a thing, or at least it is yet to be defined. The current (dynamic) native C interface is the ABI defined by the
With enough macros and maybe some extra preprocessing this could probably be achieved with the format here. To go a step further and do proper linking of symbols would be a lot of extra work, and I guess take a lot of extra code space to store all the possible symbol names and their addresses in the bare-metal firmware.
As above, it's a lot of work to add full dynamic linking to a bare-metal port, and it would be specific for each architecture. And I don't think it would buy much (over the current scheme of having no linking): the main case for dynamic loadable code would be to implement things that are too slow in Python, and for such routines (eg a FFT) the calls into the runtime system are not the bottleneck, it's the non-Python computation that is.
Yes, we only have one format at the moment. And the proposal here is to add just one, to allow dynamic native code to be loaded in the most simplest way (no linking, except for the qstr table). This comes for free with being able to load native/viper/asm and already opens up many possibilities.
As I said above, I'm not sure how this can be done efficiently. But I'm not opposed to eventually adding linking support to .mpy files, eg so the C code can have a BSS section and create static data structures. What's wrong though with starting at the simplest case (what's proposed in this PR) and improving it as usage experience dictates? But remember, this PR here is primarily about adding support to save native/viper/asm code to .mpy files. It does it in a obvious and pretty minimal way. |
Sure, with macros, then external preprocessors it can be brushed up. It still will remain awkward API-wise IMHO.
API issue, again. I gave example with mp_printf above - it's rather awkward to call Python print() from C.
Well, there's - it's possible to write a new module, using the whole of existing MicroPython API. And with #4195 even possible to distribute them without uPy itself (at the risk that if module really used a private API, it'll stop working with another version).
Yes, for dynamic case it yet needs to be defined, and my point was that defining it as
Yes, +1 to that. Please merge ASAP ;-). Well, it's worth consider implications of the file naming. Using the same suffix for all (binary) MicroPython modules is rather efficient. But is it really user friendly? Consider for example that it won't be possible to have both bytecode and compile module of the same name. Is it relevant? I'm sure that a lot of clueless users, who will download random binaries from internet prepared by other clueless users - will find that yes. Should it be the limiting factor? Well, I'm ready to have all the usecases 1, 2, 3 I listed above to have .mpy extension, and if someone complains, say "it's not me, it's @dpgeorge who did that" ;-). |
+1 |
Excited for this feature, I just ran into the limitation of not being able to freeze a viper function along with the rest of my code in the FROZEN_MPY, it's scary how often you fix/extend something just before I realise I need it! |
But #1627 doesn't do anything really different to what's done here, it also just provides
Yes it's worth considering. But I don't see how it could be anything other than .mpy, for the fact that this PR allows mixed bytecode and native code in the same file. If you have some pure bytecode in foo.mpy and then want to change just one small function to be faster with native code generation, it seems sensible that the file stays foo.mpy.
If needed that can be handled by using different names, eg |
Well, that's exactly my point - neither #1627 nor this PR provides adequate implementation of "dynamically loadable modules", though they may provide some bits and pieces towards that yeah.
Right, the threshold is passed with saying "... and you can reuse the same format for native C modules". Should that be .mpy? If that format after found to not be adequate enough for native modules, and other formats are added, should those still be .mpy?
Yes, something like that could be done. Anyway, this apparently would better be continued within frame of #1627, and this just hopefully merged soon. |
72bd732
to
c4695d6
Compare
This was rebased on current master (with recent changes to optimise mpy size), some things fixed, tests added, coverage improved. |
The new compile-time option is MICROPY_DEBUG_MP_OBJ_SENTINELS, disabled by default. This is to allow finer control of whether this debugging feature is enabled or not (because, for example, this setting must be the same for mpy-cross and the MicroPython main code when using native code generation).
Simplifies the code and fixes handling of the Ellipsis const in native code generation (which also needs the constant table so must set this flag).
n_obj no longer includes a count for mp_fun_table to make it a bit simpler.
This commit adds support for saving and loading .mpy files that contain native code (native, viper and inline-asm). A lot of the ground work was already done for this in the form of removing pointers from generated native code. The changes here are mainly to link in qstr values to the native code, and change the format of .mpy files to contain native code blocks (possibly mixed with bytecode). A top-level summary: - @micropython.native, @micropython.viper and @micropython.asm_thumb/ asm_xtensa are now allowed in .py files when compiling to .mpy, and they work transparently to the user. - Entire .py files can be compiled to native via mpy-cross -X emit=native and for the most part the generated .mpy files should work the same as their bytecode version. - The .mpy file format is changed to 1) specify in the header if the file contains native code and if so the architecture (eg x86, ARMV7M, Xtensa); 2) for each function block the kind of code is specified (bytecode, native, viper, asm). - When native code is loaded from a .mpy file the native code must be modified (in place) to link qstr values in, just like bytecode (see py/persistentcode.c:arch_link_qstr() function). In addition, this now defines a public, native ABI for dynamically loadable native code generated by other languages, like C.
This adds support to freeze .mpy files that contain native code blocks.
Build with: $ ./mk_native.sh Execute with: $ ./micropython import native_ex native_ex.foo()
Two small PacketBuffer fixes
This set of patches provides support for saving and loading .mpy files that contain native code (native, viper and inline-asm). A lot of the ground work was already done for this in the form of removing pointers from generated native code. The changes here are mainly to link in qstrs to the native code, and change to the format of .mpy files to contain native code blocks.
A top-level summary:
@micropython.native
,@micropython.viper
and@micropython.asm_thumb/asm_xtensa
are now allowed in .py files when compiling to .mpy, and they work transparently to the usermpy-cross -X emit=native
and for the most part the generated .mpy files should work the same as their bytecode versionlink_qstr()
functions in the patch)To do:
mpy-cross -march=thumb2
(at the moment it must be compiled separately for each arch, likempy-cross-thumb2
)In addition, this now defines a public, native ABI for dynamically loadable native code generated by other languages, like C, and so is intended to replace #1627.
This was tested on the unix port (x86, x86-64), stm32 and esp8266. To test it on unix do: