From fc40fbc9289f0e5fc5e37cd87f319119b9f985ce Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sun, 6 Jul 2025 13:44:13 +0100 Subject: [PATCH] m86kmac: Let there be quickdraw. I'm sure it barely works, but hey, it drew a rectangle ``` import qd import array rect = array.array('h', [0, 0, 64, 200]) pattern = bytes([0xaa, 0x55] * 4) qd.FillRect(rect, pattern) ``` Signed-off-by: Jeff Epler --- .github/workflows/ports_m68kmac.yml | 4 + ports/m68kmac/Makefile | 7 +- ports/m68kmac/tools/mkapi.py | 339 ++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 ports/m68kmac/tools/mkapi.py diff --git a/.github/workflows/ports_m68kmac.yml b/.github/workflows/ports_m68kmac.yml index c27d2cbcde..8af4c9b289 100644 --- a/.github/workflows/ports_m68kmac.yml +++ b/.github/workflows/ports_m68kmac.yml @@ -16,7 +16,11 @@ jobs: - uses: actions/checkout@v4 - name: Build run: | + apt update + apt install -y python3-pip + pip install pyyaml git config --global --add safe.directory $(pwd) + git submodule update --init --recursive ports/m68kmac make -C mpy-cross -j$(nproc) make -C ports/m68kmac submodules make -C ports/m68kmac -j$(nproc) diff --git a/ports/m68kmac/Makefile b/ports/m68kmac/Makefile index 58544d1c30..1ad81df0c6 100644 --- a/ports/m68kmac/Makefile +++ b/ports/m68kmac/Makefile @@ -42,6 +42,7 @@ SRC_C = \ main.c \ vfs_mac.c \ macutil.c \ + $(BUILD)/modqd.c \ SRC_C += \ shared/readline/readline.c \ @@ -64,12 +65,16 @@ SRC_QSTR += \ vfs_mac.c \ shared/readline/readline.c \ shared/runtime/pyexec.c \ + $(BUILD)/modqd.c \ OBJ += $(PY_CORE_O) $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o)) +$(BUILD)/modqd.c: $(BUILD)/ tools/mkapi.py + $(PYTHON) tools/mkapi.py lib/multiversal/defs/QuickDraw.yaml qd > $@ + all: $(BUILD)/micropython.bin $(BUILD)/micropython.code.bin: $(OBJ) @@ -89,7 +94,7 @@ $(BUILD)/micropython.bin $(BUILD)/micropython.APPL $(BUILD)/micropython.dsk: $(B -o $(BUILD)/micropython.bin --cc $(BUILD)/micropython.APPL --cc $(BUILD)/micropython.dsk .PHONY: docker-build -docker-build: +docker-build: $(BUILD)/modqd.py docker run --rm --mount type=bind,source=$(abspath $(TOP)),destination=/work ghcr.io/autc04/retro68 make -C /work/ports/m68kmac -j$(shell nproc) .PHONY: docker-build-% diff --git a/ports/m68kmac/tools/mkapi.py b/ports/m68kmac/tools/mkapi.py new file mode 100644 index 0000000000..eb3fde4721 --- /dev/null +++ b/ports/m68kmac/tools/mkapi.py @@ -0,0 +1,339 @@ +import pathlib +import sys +import textwrap +import yaml + +with open(sys.argv[1]) as f: + defs = yaml.safe_load(f) + +if len(sys.argv) > 2: + modname = sys.argv[2] +else: + modname = pathlib.Path(sys.argv[1]).stem + +signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t', 'Fixed', 'GrafVerb', 'CharParameter'} +unsigned_integer_types = {'int8_t', 'int16_t', 'int32_t'} + + +class PointConverter: + def emit_to_c(self, name_py, name_c): + return f"Point {name_c} = Point_to_c({name_py});\n" + + def emit_to_py(self, name_c): + return f"NEW_TUPLE({name_c}.x, {name_c}.y)" + + +converters = { + 'Point': PointConverter, +} + + +class SimpleConverter: + def __init__(self, type_c, to_c, to_py): + self.type_c = type_c + self.to_c = to_c + self.to_py = to_py + + def emit_decls(self): + pass + + def emit_to_c(self, name_py, name_c): + return f"{self.type_c} {name_c} = {self.to_c}({name_py});\n" + + def emit_to_py(self, name_c): + return f"{self.to_py}({name_c})" + + +class PtrConverter: + def __init__(self, type_c): + self.type_c = type_c + + def emit_to_c(self, name_py, name_c): + is_const = +self.type_c.startswith("const ") # to get 0/1, not True/False + return f"{self.type_c} {name_c} = to_c_helper({name_py}, sizeof(*{name_c}), {is_const});\n" + + def emit_to_py(self, name_c): + is_signed_hint = not self.type_c.lower().startswith('u') + return f"from_c_helper({name_c}, sizeof(*{name_c}), {+is_signed_hint})" + + +def make_converter(type_c): + if converter := converters.get(type_c): + return converter() + if type_c in signed_integer_types: + return SimpleConverter(type_c, "mp_obj_get_int", "mp_obj_new_int") + if type_c in unsigned_integer_types: + return SimpleConverter(type_c, "mp_obj_get_int_truncated", "mp_obj_new_int_from_uint") + if type_c.endswith("*") or type_c.endswith("Handle") or type_c.endswith("Ptr"): + return PtrConverter(type_c) + raise ValueError(f"no converter possible for {type_c}") + + +class Processor: + def __init__(self, modname): + self.modname = modname + self.decls = [] + self.body = [] + self.locals = [] + self.info = [] + self.unknowns = set() + self.types = { + 'Byte': 'uint8_t', + 'Boolean': 'uint8_t', + 'SignedByte': 'int8_t', + 'Sint8': 'int8_t', + 'Uint8': 'uint8_t', + 'Sint16': 'int16_t', + 'Uint16': 'uint16_t', + 'Sint32': 'int32_t', + 'Uint32': 'uint32_t', + 'ULONGINT': 'uint32_t', + 'INTEGER': 'int16_t', + 'LONGINT': 'int32_t', + } + self.body_dedent(""" + #include "py/obj.h" + #include "py/runtime.h" + #include + + // Relies on gcc Variadic Macros and Statement Expressions + #define NEW_TUPLE(...) \ + ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); }) + + void *to_c_helper(mp_obj_t obj, size_t objsize, bool is_const) { + if (mp_obj_is_int(obj)) { + return (void*)mp_obj_get_int_truncated(obj); + } + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(obj, &bufinfo, is_const ? MP_BUFFER_READ : MP_BUFFER_READ | MP_BUFFER_WRITE); + if (bufinfo.len != objsize) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); + } + return bufinfo.buf; + } + + Point Point_to_c(mp_obj_t obj) { + Point result; + if (mp_obj_len_maybe(obj) == MP_OBJ_NEW_SMALL_INT(2)) { + result.h = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(0), MP_OBJ_SENTINEL)); + result.v = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(1), MP_OBJ_SENTINEL)); + } else { + result = *(Point*)to_c_helper(obj, sizeof(Point), true); + } + return result; + } + + mp_obj_t from_c_helper(void *buf, size_t objsize, bool is_signed_hint) { + return mp_obj_new_int_from_uint(*(unsigned long*)buf); + } + + mp_obj_t LMGet_common(long address, size_t objsize, mp_obj_t arg) { + if (arg == mp_const_none) { + return mp_obj_new_bytearray(objsize, (void*)address); + } + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_WRITE); + if (bufinfo.len != objsize) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); + } + memcpy(bufinfo.buf, (void*)address, objsize); + return arg; + } + void LMSet_common(long address, size_t objsize, mp_obj_t arg) { + mp_buffer_info_t bufinfo = {0}; + mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != objsize) { + mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length")); + } + memcpy((void*)address, bufinfo.buf, objsize); + } + """) + + def resolve_type(self, typename): + is_const = self.is_const(typename) + typename = typename.removeprefix("const ") + + while typename in self.types: + typename = self.types[typename] + + if is_const and self.is_ptr(typename): + return f"const {typename}" + + return typename + + def is_ptr(self, typename): + return self.resolve_type(typename).endswith("*") + + def is_const(self, typename): + return typename.startswith("const ") + + def body_dedent(self, text): + self.body.append(textwrap.dedent(text.rstrip())) + + def process(self, defs): + for d in defs: + self.only_for = d.pop('only-for', None) + self.not_for = d.pop('not-for', None) + self.api = d.pop('api', None) + + if len(d) != 1: + raise ValueError(f"Definition with more than one key: {defs!r}") + k, v = d.popitem() + meth = getattr(self, 'handle_' + k, lambda v: self.handle_unknown(k, v)) + meth(v) + + def handle_unknown(self, k, v): + if k in self.unknowns: + return + self.unknowns.add(k) + self.info.append(f"# Unknown {k} {v!r:.55s}...") + + def handle_enum(self, e): + for v in e['values']: + if 'value' in v: + self.locals.append( + f"{{ MP_ROM_QSTR(MP_QSTR_{v['name']}), MP_ROM_INT({v['value']}) }}," + ) + # else: + # self.info.append(f"enumerant without value: {v['name']}") + + def handle_lowmem(self, lm): + name = lm['name'] + address = lm['address'] + typename = lm['type'] + self.body_dedent(f""" + static mp_obj_t LMGet{name}_fn(size_t n_args, const mp_obj_t *args) {{ + return LMGet_common({address}, sizeof({typename}), n_args == 0 ? mp_const_none : args[0]); + }} + MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(LMGet{name}_obj, 0, 1, LMGet{name}_fn); + + static mp_obj_t LMSet{name}_fn(mp_obj_t value) {{ + LMSet_common({address}, sizeof({typename}), value); + return mp_const_none; + }} + MP_DEFINE_CONST_FUN_OBJ_1(LMSet{name}_obj, LMSet{name}_fn); + """) + self.locals.append( + f"{{ MP_ROM_QSTR(MP_QSTR_LMGet{name}), MP_ROM_PTR(&LMGet{name}_obj) }}," + ) + self.locals.append( + f"{{ MP_ROM_QSTR(MP_QSTR_LMSet{name}), MP_ROM_PTR(&LMSet{name}_obj) }}," + ) + + def handle_typedef(self, v): + pass # ignore typedefs for now ?? + + def handle_verbatim(self, v): + pass # Ignore verbatim blocks + + # {'args': [{'name': 'src_bitmap', 'type': 'BitMap*'}, + # {'name': 'dst_bitmap', 'type': 'BitMap*'}, + # {'name': 'src_rect', 'type': 'const Rect*'}, + # {'name': 'dst_rect', 'type': 'const Rect*'}, + # {'name': 'mode', 'type': 'INTEGER'}, + # {'name': 'mask', 'type': 'RgnHandle'}], + # 'executor': 'C_', + # 'name': 'CopyBits', + # 'trap': 43244} + + def fun_declare_args_enum(self, args): + if args: + args = ", ".join(f"ARG_{arg['name']}" for arg in args) + return f"enum {{ {args} }};" + return "" + + @staticmethod + def fun_declare_allowed_arg(arg): + name = arg['name'] + return f"{{ MP_QSTR_{name}, MP_ARG_OBJ | MP_ARG_REQUIRED, {{0}}, }}," + + def fun_parse_args(self, args): + body = [ + self.fun_declare_args_enum(args), + "static const mp_arg_t allowed_args[] = {", + *(self.fun_declare_allowed_arg(a) for a in args), + "};", + "mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];", + "mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);", + ] + return "\n".join(f" {line}" for line in body) + + def make_converter(self, typename): + print(f"make_converter {typename}", file=sys.stderr) + if not typename.endswith('*'): + typename = self.resolve_type(typename) + print(f" -> {typename}", file=sys.stderr) + return make_converter(typename) + + def fun_convert_arg(self, idx, arg): + return self.make_converter(arg['type']).emit_to_c(f"args[{idx}].u_obj", arg['name']) + + def fun_convert_args(self, args): + return "".join(" " + self.fun_convert_arg(i, a) for i, a in enumerate(args)) + + def fun_call_fun(self, fun): + return_type = fun.get('return', None) + args = fun.get('args', []) + fun_args = ", ".join(arg['name'] for arg in args) + funcall = f"{fun['name']}({fun_args});" + if return_type: + funcall = f"{return_type} retval = {funcall}" + return " " + funcall + + def fun_convert_return(self, fun): + return_type = fun.get('return', None) + if return_type: + converter = self.make_converter(return_type) + return f" return {converter.emit_to_py('retval')};// TODO" + else: + return " return mp_const_none;" + + def handle_function(self, fun): + name = fun['name'] + args = fun.get('args', []) + print(f"## API={self.api}", file=sys.stderr) + if self.api == 'carbon': + return + self.body_dedent(f""" + mp_obj_t {name}_fn(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {{ + {self.fun_parse_args(args)} + {self.fun_convert_args(args)} + {self.fun_call_fun(fun)} + {self.fun_convert_return(fun)} + }} + MP_DEFINE_CONST_FUN_OBJ_KW({name}_obj, {len(args)}, {name}_fn); + """) + self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{name}), MP_ROM_PTR(&{name}_obj) }},") + + def handle_funptr(self, fun): + pass # Ignore function pointers for now + + def emit(self): + for row in self.decls: + print(row) + for row in self.body: + print(row) + print() + print("static const mp_rom_map_elem_t module_globals_table[] = {") + print(f" {{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_{self.modname}) }},") + for row in self.locals: + print(f" {row}") + print("};") + print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);") + print( + textwrap.dedent(f""" + const mp_obj_module_t {self.modname}_module = {{ + .base = {{ &mp_type_module }}, + .globals = (mp_obj_dict_t *)&module_globals, + }}; + + MP_REGISTER_MODULE(MP_QSTR_{self.modname}, {self.modname}_module);""") + ) + + for row in self.info: + print(row, file=sys.stderr) + + +p = Processor(modname) +p.process(defs) +p.emit()