10000 mpy-cross/mpy_cross: Multiple backend support for mpy-cross. · micropython/micropython@fb92b27 · GitHub
[go: up one dir, main page]

Skip to content

Commit fb92b27

Browse files
committed
mpy-cross/mpy_cross: Multiple backend support for mpy-cross.
This adds support for using either a native or WASM binary in the Python wrapper for mpy-cross. WASM support is provided by wasmtime, pywasm3 and wasmer. It will use whichever is available. At least one should be an option on every supported platform/architecture. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>
1 parent b779d04 commit fb92b27

File tree

10 files changed

+940
-128
lines changed

10 files changed

+940
-128
lines changed

mpy-cross/mpy_cross/__init__.py

Lines changed: 20 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#!/usr/bin/env python3
2-
#
31
# This file is part of the MicroPython project, http://micropython.org/
42
#
53
# The MIT License (MIT)
@@ -25,127 +23,35 @@
2523
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2624
# THE SOFTWARE.
2725

28-
from __future__ import print_function
29-
import os
30-
import re
31-
import stat
32-
import subprocess
33-
34-
NATIVE_ARCHS = {
35-
"NATIVE_ARCH_NONE": "",
36-
"NATIVE_ARCH_X86": "x86",
37-
"NATIVE_ARCH_X64": "x64",
38-
"NATIVE_ARCH_ARMV6": "armv6",
39-
"NATIVE_ARCH_ARMV6M": "armv6m",
40-
"NATIVE_ARCH_ARMV7M": "armv7m",
41-
"NATIVE_ARCH_ARMV7EM": "armv7em",
42-
"NATIVE_ARCH_ARMV7EMSP": "armv7emsp",
43-
"NATIVE_ARCH_ARMV7EMDP": "armv7emdp",
44-
"NATIVE_ARCH_XTENSA": "xtensa",
45-
"NATIVE_ARCH_XTENSAWIN": "xtensawin",
46-
}
47-
48-
globals().update(NATIVE_ARCHS)
49-
50-
__all__ = ["version", "compile", "run", "CrossCompileError"] + list(NATIVE_ARCHS.keys())
51-
52-
53-
class CrossCompileError(Exception):
54-
pass
55-
56-
57-
_VERSION_RE = re.compile("mpy-cross emitting mpy v([0-9]+)(?:.([0-9]+))?")
58-
59-
60-
def _find_mpy_cross_binary(mpy_cross):
61-
if mpy_cross:
62-
return mpy_cross
63-
return os.path.abspath(os.path.join(os.path.dirname(__file__), "../build/mpy-cross"))
64-
65-
66-
def mpy_version(mpy_cross=None):
67-
"""
68-
Get the version and sub-version of the .mpy file format generated by this version of mpy-cross.
69-
70-
Returns: A tuple of `(mpy_version, mpy_sub_version)`
71-
Optional keyword arguments:
72-
- mpy_cross: Specific mpy-cross binary to use
73-
"""
74-
version_info = run(["--version"], mpy_cross=mpy_cross)
75-
match = re.search(_VERSION_RE, version_info)
76-
mpy_version, mpy_sub_version = int(match.group(1)), int(match.group(2) or "0")
77-
return (
78-
mpy_version,
79-
mpy_sub_version,
80-
)
81-
82-
83-
def compile(src, dest=None, src_path=None, opt=None, march=None, mpy_cross=None, extra_args=None):
84-
"""
85-
Compile the specified .py file with mpy-cross.
86-
87-
Returns: Standard output from mpy-cross as a string.
88-
89-
Required arguments:
90-
- src: The path to the .py file
91-
92-
Optional keyword arguments:
93-
- dest: The output .mpy file. Defaults to `src` (with .mpy extension)
94-
- src_path: The path to embed in the .mpy file (defaults to `src`)
95-
- opt: Optimisation level (0-3, default 0)
96-
- march: One of the `NATIVE_ARCH_*` constants (defaults to NATIVE_ARCH_NONE)
97-
- mpy_cross: Specific mpy-cross binary to use
98-
- extra_args: Additional arguments to pass to mpy-cross (e.g. `["-X", "emit=native"]`)
99-
"""
100-
if not src:
101-
raise ValueError("src is required")
102-
if not os.path.exists(src):
103-
raise CrossCompileError("Input .py file not found: {}.".format(src))
104-
105-
args = []
106-
107-
if src_path:
108-
args += ["-s", src_path]
109-
110-
if dest:
111-
args += ["-o", dest]
11226

113-
if march:
114-
args += ["-march=" + march]
27+
from .compiler import *
11528

116-
if opt is not None:
117-
args += ["-O{}".format(opt)]
11829

119-
if extra_args:
120-
args += extra_args
30+
def mpy_version():
31+
return default_compiler().mpy_version()
12132

122-
args += [src]
12333

124-
run(args, mpy_cross)
34+
def version():
35+
return default_compiler().version()
12536

12637

127-
def run(args, mpy_cross=None):
128-
"""
129-
Run mpy-cross with the specified command line arguments.
130-
Prefer to use `compile()` instead.
38+
def compile(*args, **kwargs):
39+
compiler_kwargs = {}
13140

132-
Returns: Standard output from mpy-cross as a string.
41+
if "mpy_cross" in kwargs:
42+
compiler_kwargs.update(binary=kwargs["mpy_cross"])
43+
del kwargs["mpy_cross"]
13344

134-
Optional keyword arguments:
135-
- mpy_cross: Specific mpy-cross binary to use
136-
"""
137-
mpy_cross = _find_mpy_cross_binary(mpy_cross)
45+
if "extra_args" in kwargs:
46+
for arg in kwargs["extra_args"]:
47+
if arg.startswith("-march="):
48+
kwargs.update(march=arg[7:])
49+
else:
50+
raise ValueError("Unknown mpy-cross arg: {}".format(arg))
51+
del kwargs["extra_args"]
13852

139-
if not os.path.exists(mpy_cross):
140-
raise CrossCompileError("mpy-cross binary not found at {}.".format(mpy_cross))
53+
return default_compiler(**compiler_kwargs).compile(*args, **kwargs)
14154

142-
try:
143-
st = os.stat(mpy_cross)
144-
os.chmod(mpy_cross, st.st_mode | stat.S_IEXEC)
145-
except OSError:
146-
pass
14755

148-
try:
149-
return subprocess.check_output([mpy_cross] + args, stderr=subprocess.STDOUT).decode()
150-
except subprocess.CalledProcessError as er:
151-
raise CrossCompileError(er.output.decode())
56+
def description():
57+
return default_compiler().description()

mpy-cross/mpy_cross/__main__.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
#!/usr/bin/env python3
2-
#
31
# This file is part of the MicroPython project, http://micropython.org/
42
#
53
# The MIT License (MIT)
64
#
75
# Copyright (c) 2022 Andrew Leech
8-
# Copyright (c) 2022 Jim Mussared
6+
# Copyright (c) 2023 Jim Mussared
97
#
108
# Permission is hereby granted, free of charge, to any person obtaining a copy
119
# of this software and associated documentation files (the "Software"), to deal
@@ -25,14 +23,6 @@
2523
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2624
# THE SOFTWARE.
2725

28-
from __future__ import print_function
29-
import argparse
30-
import sys
31-
32-
from . import run, CrossCompileError
26+
from .main import main
3327

34-
try:
35-
print(run(sys.argv[1:]))
36-
except CrossCompileError as er:
37-
print(er.args[0], file=sys.stderr)
38-
raise SystemExit(1)
28+
main()

mpy-cross/mpy_cross/compiler.py

Lines changed: 204 additions & 0 deletions
32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# This file is part of the MicroPython project, http://micropython.org/
2+
#
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2022 Andrew Leech
6+
# Copyright (c) 2023 Jim Mussared
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in
16+
# all copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
# THE SOFTWARE.
25+
26+
import os
27+
28+
29+
class CrossCompileError(Exception):
30+
pass
31+
+
33+
class CompilerNotFoundError(Exception):
34+
pass
35+
36+
37+
EMIT_BYTECODE = "bytecode"
38+
EMIT_NATIVE = "native"
39+
EMIT_VIPER = "viper"
40+
41+
42+
def emit_id(name):
43+
return {
44+
EMIT_BYTECODE: 1,
45+
EMIT_NATIVE: 2,
46+
EMIT_VIPER: 3,
47+
}[name]
48+
49+
50+
NATIVE_ARCHS = {
51+
"NATIVE_ARCH_NONE": "",
52+
"NATIVE_ARCH_X86": "x86",
53+
"NATIVE_ARCH_X64": "x64",
54+
"NATIVE_ARCH_ARMV6": "armv6",
55+
"NATIVE_ARCH_ARMV6M": "armv6m",
56+
"NATIVE_ARCH_ARMV7M": "armv7m",
57+
"NATIVE_ARCH_ARMV7EM": "armv7em",
58+
"NATIVE_ARCH_ARMV7EMSP": "armv7emsp",
59+
"NATIVE_ARCH_ARMV7EMDP": "armv7emdp",
60+
"NATIVE_ARCH_XTENSA": "xtensa",
61+
"NATIVE_ARCH_XTENSAWIN": "xtensawin",
62+
}
63+
64+
globals().update(NATIVE_ARCHS)
65+
66+
67+
def native_arch_id(name):
68+
if name is None:
69+
return 0
70+
71+
return {
72+
"": 0,
73+
"x86": 1,
74+
"x64": 2,
75+
"armv6": 3,
76+
"armv6m": 4,
77+
"armv7m": 5,
78+
"armv7em": 6,
79+
"armv7emsp": 7,
80+
"armv7emdp": 8,
81+
"xtensa": 9,
82+
"xtensawin": 10,
83+
}[name]
84+
85+
86+
class Compiler:
87+
def mpy_version(self):
88+
"""
89+
Get the version and sub-version of the .mpy file format generated by this compiler.
90+
91+
Returns: A tuple of `(mpy_version, mpy_sub_version)`
92+
"""
93+
raise NotImplementedError()
94+
95+
def version(self):
96+
"""
97+
Get the version string from this compiler.
98+
99+
Returns: A string, typically "MicroPython <git tag> on <build date>".
100+
"""
101+
raise NotImplementedError()
102+
103+
def compile(self, src, *, dest=None, src_path=None, opt=0, march=None, emit=EMIT_BYTECODE):
104+
"""
105+
Compile the specified .py file with mpy-cross.
106+
107+
Returns: Standard output from mpy-cross as a string.
108+
109+
Required arguments:
110+
- src: The path to the .py file
111+
112+
Optional keyword arguments:
113+
- dest: The output .mpy file. Defaults to `src` (with .mpy extension)
114+
- src_path: The path to embed in the .mpy file (defaults to `src`)
115+
- opt: Optimisation level (0-3, default 0)
116+
- march: One of the `NATIVE_ARCH_*` constants (defaults to
117+
None, which is treated as NATIVE_ARCH_NONE). Architecture to
118+
use when generating code for @native/@viper functions.
119+
- emit: One of the `EMIT_*` constants (defaults to
120+
EMIT_BYTECODE). This sets the default emitter to use for
121+
functions that do not have a decorator.
122+
"""
123+
raise NotImplementedError()
124+
125+
def description(self):
126+
"""
127+
Returns: A string containing a description of this compiler.
128+
"""
129+
raise NotImplementedError()
130+
131+
def _find_binary(self, binary=None, *, name="mpy-cross", ext="", build_dir="build"):
132+
# If an explicit binary is given, use that.
133+
if binary and os.path.exists(binary):
134+
return binary
135+
136+
# If we're running the installed version, then the binary will be
137+
# alongside the Python files.
138+
install_path = os.path.dirname(__file__)
139+
wheel_binary = os.path.abspath(os.path.join(install_path, name + ext))
140+
if os.path.exists(wheel_binary):
141+
return wheel_binary
142+
143+
# Otherwise, running a locally-built copy, grab it from the build
144+
# directory.
145+
build_binary = os.path.abspath(os.path.join(install_path, "..", build_dir, name + ext))
146+
if os.path.exists(build_binary):
147+
return build_binary
148+
149+
raise CompilerNotFoundError("Unable to find {}{} binary.".format(name, ext))
150+
151+
152+
def default_compiler(binary=None):
153+
"""
154+
Finds an available compiler.
155+
156+
Optional arguments:
157+
- binary: The path to a native or wasm binary. If this is specified then
158+
no search will be performed, and only this binary will be tried.
159+
"""
160+
161+
if not binary or not binary.lower().endswith(".wasm"):
162+
try:
163+
from .compiler_native import CompilerNative
164+
165+
return CompilerNative(binary=binary)
166+
except CompilerNotFoundError:
167+
pass
168+
169+
if not binary or binary.lower().endswith(".wasm"):
170+
try:
171+
from .compiler_pywasm3 import CompilerPywasm3
172+
173+
return CompilerPywasm3(binary=binary)
174+
except CompilerNotFoundError:
175+
pass
176+
177+
try:
178+
from .compiler_wasmtime import CompilerWasmTime
179+
180+
return CompilerWasmTime(binary=binary)
181+
except CompilerNotFoundError:
182+
pass
183+
184+
try:
185+
from .compiler_wasmer import CompilerWasmer
186+
187+
return CompilerWasmer(binary=binary)
188+
except CompilerNotFoundError:
189+
pass
190+
191+
if binary:
192+
raise RuntimeError("Unable to instantiate compiler for {}.".format(binary))
193+
else:
194+
raise RuntimeError("No compiler installed/available.")
195+
196+
197+
__all__ = [
198+
"CrossCompileError",
199+
"Compiler",
200+
"default_compiler",
201+
"EMIT_BYTECODE",
202+
"EMIT_NATIVE",
203+
"EMIT_VIPER",
204+
] + list(NATIVE_ARCHS.keys())

0 commit comments

Comments
 (0)
0