Skip to content

Commit fc40fbc

Browse files
committed
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 <jepler@gmail.com>
1 parent c891afd commit fc40fbc

File tree

3 files changed

+349
-1
lines changed

3 files changed

+349
-1
lines changed

.github/workflows/ports_m68kmac.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ jobs:
1616
- uses: actions/checkout@v4
1717
- name: Build
1818
run: |
19+
apt update
20+
apt install -y python3-pip
21+
pip install pyyaml
1922
git config --global --add safe.directory $(pwd)
23+
git submodule update --init --recursive ports/m68kmac
2024
make -C mpy-cross -j$(nproc)
2125
make -C ports/m68kmac submodules
2226
make -C ports/m68kmac -j$(nproc)

ports/m68kmac/Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ SRC_C = \
4242
main.c \
4343
vfs_mac.c \
4444
macutil.c \
45+
$(BUILD)/modqd.c \
4546

4647
SRC_C += \
4748
shared/readline/readline.c \
@@ -64,12 +65,16 @@ SRC_QSTR += \
6465
vfs_mac.c \
6566
shared/readline/readline.c \
6667
shared/runtime/pyexec.c \
68+
$(BUILD)/modqd.c \
6769

6870
OBJ += $(PY_CORE_O) $(PY_O)
6971
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
7072
OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o))
7173
OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o))
7274

75+
$(BUILD)/modqd.c: $(BUILD)/ tools/mkapi.py
76+
$(PYTHON) tools/mkapi.py lib/multiversal/defs/QuickDraw.yaml qd > $@
77+
7378
all: $(BUILD)/micropython.bin
7479

7580
$(BUILD)/micropython.code.bin: $(OBJ)
@@ -89,7 +94,7 @@ $(BUILD)/micropython.bin $(BUILD)/micropython.APPL $(BUILD)/micropython.dsk: $(B
8994
-o $(BUILD)/micropython.bin --cc $(BUILD)/micropython.APPL --cc $(BUILD)/micropython.dsk
9095

9196
.PHONY: docker-build
92-
docker-build:
97+
docker-build: $(BUILD)/modqd.py
9398
docker run --rm --mount type=bind,source=$(abspath $(TOP)),destination=/work ghcr.io/autc04/retro68 make -C /work/ports/m68kmac -j$(shell nproc)
9499

95100
.PHONY: docker-build-%

ports/m68kmac/tools/mkapi.py

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
import pathlib
2+
import sys
3+
import textwrap
4+
import yaml
5+
6+
with open(sys.argv[1]) as f:
7+
defs = yaml.safe_load(f)
8+
9+
if len(sys.argv) > 2:
10+
modname = sys.argv[2]
11+
else:
12+
modname = pathlib.Path(sys.argv[1]).stem
13+
14+
signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t', 'Fixed', 'GrafVerb', 'CharParameter'}
15+
unsigned_integer_types = {'int8_t', 'int16_t', 'int32_t'}
16+
17+
18+
class PointConverter:
19+
def emit_to_c(self, name_py, name_c):
20+
return f"Point {name_c} = Point_to_c({name_py});\n"
21+
22+
def emit_to_py(self, name_c):
23+
return f"NEW_TUPLE({name_c}.x, {name_c}.y)"
24+
25+
26+
converters = {
27+
'Point': PointConverter,
28+
}
29+
30+
31+
class SimpleConverter:
32+
def __init__(self, type_c, to_c, to_py):
33+
self.type_c = type_c
34+
self.to_c = to_c
35+
self.to_py = to_py
36+
37+
def emit_decls(self):
38+
pass
39+
40+
def emit_to_c(self, name_py, name_c):
41+
return f"{self.type_c} {name_c} = {self.to_c}({name_py});\n"
42+
43+
def emit_to_py(self, name_c):
44+
return f"{self.to_py}({name_c})"
45+
46+
47+
class PtrConverter:
48+
def __init__(self, type_c):
49+
self.type_c = type_c
50+
51+
def emit_to_c(self, name_py, name_c):
52+
is_const = +self.type_c.startswith("const ") # to get 0/1, not True/False
53+
return f"{self.type_c} {name_c} = to_c_helper({name_py}, sizeof(*{name_c}), {is_const});\n"
54+
55+
def emit_to_py(self, name_c):
56+
is_signed_hint = not self.type_c.lower().startswith('u')
57+
return f"from_c_helper({name_c}, sizeof(*{name_c}), {+is_signed_hint})"
58+
59+
60+
def make_converter(type_c):
61+
if converter := converters.get(type_c):
62+
return converter()
63+
if type_c in signed_integer_types:
64+
return SimpleConverter(type_c, "mp_obj_get_int", "mp_obj_new_int")
65+
if type_c in unsigned_integer_types:
66+
return SimpleConverter(type_c, "mp_obj_get_int_truncated", "mp_obj_new_int_from_uint")
67+
if type_c.endswith("*") or type_c.endswith("Handle") or type_c.endswith("Ptr"):
68+
return PtrConverter(type_c)
69+
raise ValueError(f"no converter possible for {type_c}")
70+
71+
72+
class Processor:
73+
def __init__(self, modname):
74+
self.modname = modname
75+
self.decls = []
76+
self.body = []
77+
self.locals = []
78+
self.info = []
79+
self.unknowns = set()
80+
self.types = {
81+
'Byte': 'uint8_t',
82+
'Boolean': 'uint8_t',
83+
'SignedByte': 'int8_t',
84+
'Sint8': 'int8_t',
85+
'Uint8': 'uint8_t',
86+
'Sint16': 'int16_t',
87+
'Uint16': 'uint16_t',
88+
'Sint32': 'int32_t',
89+
'Uint32': 'uint32_t',
90+
'ULONGINT': 'uint32_t',
91+
'INTEGER': 'int16_t',
92+
'LONGINT': 'int32_t',
93+
}
94+
self.body_dedent("""
95+
#include "py/obj.h"
96+
#include "py/runtime.h"
97+
#include <Multiverse.h>
98+
99+
// Relies on gcc Variadic Macros and Statement Expressions
100+
#define NEW_TUPLE(...) \
101+
({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); })
102+
103+
void *to_c_helper(mp_obj_t obj, size_t objsize, bool is_const) {
104+
if (mp_obj_is_int(obj)) {
105+
return (void*)mp_obj_get_int_truncated(obj);
106+
}
107+
mp_buffer_info_t bufinfo = {0};
108+
mp_get_buffer_raise(obj, &bufinfo, is_const ? MP_BUFFER_READ : MP_BUFFER_READ | MP_BUFFER_WRITE);
109+
if (bufinfo.len != objsize) {
110+
mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length"));
111+
}
112+
return bufinfo.buf;
113+
}
114+
115+
Point Point_to_c(mp_obj_t obj) {
116+
Point result;
117+
if (mp_obj_len_maybe(obj) == MP_OBJ_NEW_SMALL_INT(2)) {
118+
result.h = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(0), MP_OBJ_SENTINEL));
119+
result.v = mp_obj_get_int(mp_obj_subscr(obj, mp_obj_new_int(1), MP_OBJ_SENTINEL));
120+
} else {
121+
result = *(Point*)to_c_helper(obj, sizeof(Point), true);
122+
}
123+
return result;
124+
}
125+
126+
mp_obj_t from_c_helper(void *buf, size_t objsize, bool is_signed_hint) {
127+
return mp_obj_new_int_from_uint(*(unsigned long*)buf);
128+
}
129+
130+
mp_obj_t LMGet_common(long address, size_t objsize, mp_obj_t arg) {
131+
if (arg == mp_const_none) {
132+
return mp_obj_new_bytearray(objsize, (void*)address);
133+
}
134+
mp_buffer_info_t bufinfo = {0};
135+
mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_WRITE);
136+
if (bufinfo.len != objsize) {
137+
mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length"));
138+
}
139+
memcpy(bufinfo.buf, (void*)address, objsize);
140+
return arg;
141+
}
142+
void LMSet_common(long address, size_t objsize, mp_obj_t arg) {
143+
mp_buffer_info_t bufinfo = {0};
144+
mp_get_buffer_raise(arg, &bufinfo, MP_BUFFER_READ);
145+
if (bufinfo.len != objsize) {
146+
mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length"));
147+
}
148+
memcpy((void*)address, bufinfo.buf, objsize);
149+
}
150+
""")
151+
152+
def resolve_type(self, typename):
153+
is_const = self.is_const(typename)
154+
typename = typename.removeprefix("const ")
155+
156+
while typename in self.types:
157+
typename = self.types[typename]
158+
159+
if is_const and self.is_ptr(typename):
160+
return f"const {typename}"
161+
162+
return typename
163+
164+
def is_ptr(self, typename):
165+
return self.resolve_type(typename).endswith("*")
166+
167+
def is_const(self, typename):
168+
return typename.startswith("const ")
169+
170+
def body_dedent(self, text):
171+
self.body.append(textwrap.dedent(text.rstrip()))
172+
173+
def process(self, defs):
174+
for d in defs:
175+
self.only_for = d.pop('only-for', None)
176+
self.not_for = d.pop('not-for', None)
177+
self.api = d.pop('api', None)
178+
179+
if len(d) != 1:
180+
raise ValueError(f"Definition with more than one key: {defs!r}")
181+
k, v = d.popitem()
182+
meth = getattr(self, 'handle_' + k, lambda v: self.handle_unknown(k, v))
183+
meth(v)
184+
185+
def handle_unknown(self, k, v):
186+
if k in self.unknowns:
187+
return
188+
self.unknowns.add(k)
189+
self.info.append(f"# Unknown {k} {v!r:.55s}...")
190+
191+
def handle_enum(self, e):
192+
for v in e['values']:
193+
if 'value' in v:
194+
self.locals.append(
195+
f"{{ MP_ROM_QSTR(MP_QSTR_{v['name']}), MP_ROM_INT({v['value']}) }},"
196+
)
197+
# else:
198+
# self.info.append(f"enumerant without value: {v['name']}")
199+
200+
def handle_lowmem(self, lm):
201+
name = lm['name']
202+
address = lm['address']
203+
typename = lm['type']
204+
self.body_dedent(f"""
205+
static mp_obj_t LMGet{name}_fn(size_t n_args, const mp_obj_t *args) {{
206+
return LMGet_common({address}, sizeof({typename}), n_args == 0 ? mp_const_none : args[0]);
207+
}}
208+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(LMGet{name}_obj, 0, 1, LMGet{name}_fn);
209+
210+
static mp_obj_t LMSet{name}_fn(mp_obj_t value) {{
211+
LMSet_common({address}, sizeof({typename}), value);
212+
return mp_const_none;
213+
}}
214+
MP_DEFINE_CONST_FUN_OBJ_1(LMSet{name}_obj, LMSet{name}_fn);
215+
""")
216+
self.locals.append(
217+
f"{{ MP_ROM_QSTR(MP_QSTR_LMGet{name}), MP_ROM_PTR(&LMGet{name}_obj) }},"
218+
)
219+
self.locals.append(
220+
f"{{ MP_ROM_QSTR(MP_QSTR_LMSet{name}), MP_ROM_PTR(&LMSet{name}_obj) }},"
221+
)
222+
223+
def handle_typedef(self, v):
224+
pass # ignore typedefs for now ??
225+
226+
def handle_verbatim(self, v):
227+
pass # Ignore verbatim blocks
228+
229+
# {'args': [{'name': 'src_bitmap', 'type': 'BitMap*'},
230+
# {'name': 'dst_bitmap', 'type': 'BitMap*'},
231+
# {'name': 'src_rect', 'type': 'const Rect*'},
232+
# {'name': 'dst_rect', 'type': 'const Rect*'},
233+
# {'name': 'mode', 'type': 'INTEGER'},
234+
# {'name': 'mask', 'type': 'RgnHandle'}],
235+
# 'executor': 'C_',
236+
# 'name': 'CopyBits',
237+
# 'trap': 43244}
238+
239+
def fun_declare_args_enum(self, args):
240+
if args:
241+
args = ", ".join(f"ARG_{arg['name']}" for arg in args)
242+
return f"enum {{ {args} }};"
243+
return ""
244+
245+
@staticmethod
246+
def fun_declare_allowed_arg(arg):
247+
name = arg['name']
248+
return f"{{ MP_QSTR_{name}, MP_ARG_OBJ | MP_ARG_REQUIRED, {{0}}, }},"
249+
250+
def fun_parse_args(self, args):
251+
body = [
252+
self.fun_declare_args_enum(args),
253+
"static const mp_arg_t allowed_args[] = {",
254+
*(self.fun_declare_allowed_arg(a) for a in args),
255+
"};",
256+
"mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];",
257+
"mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);",
258+
]
259+
return "\n".join(f" {line}" for line in body)
260+
261+
def make_converter(self, typename):
262+
print(f"make_converter {typename}", file=sys.stderr)
263+
if not typename.endswith('*'):
264+
typename = self.resolve_type(typename)
265+
print(f" -> {typename}", file=sys.stderr)
266+
return make_converter(typename)
267+
268+
def fun_convert_arg(self, idx, arg):
269+
return self.make_converter(arg['type']).emit_to_c(f"args[{idx}].u_obj", arg['name'])
270+
271+
def fun_convert_args(self, args):
272+
return "".join(" " + self.fun_convert_arg(i, a) for i, a in enumerate(args))
273+
274+
def fun_call_fun(self, fun):
275+
return_type = fun.get('return', None)
276+
args = fun.get('args', [])
277+
fun_args = ", ".join(arg['name'] for arg in args)
278+
funcall = f"{fun['name']}({fun_args});"
279+
if return_type:
280+
funcall = f"{return_type} retval = {funcall}"
281+
return " " + funcall
282+
283+
def fun_convert_return(self, fun):
284+
return_type = fun.get('return', None)
285+
if return_type:
286+
converter = self.make_converter(return_type)
287+
return f" return {converter.emit_to_py('retval')};// TODO"
288+
else:
289+
return " return mp_const_none;"
290+
291+
def handle_function(self, fun):
292+
name = fun['name']
293+
args = fun.get('args', [])
294+
print(f"## API={self.api}", file=sys.stderr)
295+
if self.api == 'carbon':
296+
return
297+
self.body_dedent(f"""
298+
mp_obj_t {name}_fn(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {{
299+
{self.fun_parse_args(args)}
300+
{self.fun_convert_args(args)}
301+
{self.fun_call_fun(fun)}
302+
{self.fun_convert_return(fun)}
303+
}}
304+
MP_DEFINE_CONST_FUN_OBJ_KW({name}_obj, {len(args)}, {name}_fn);
305+
""")
306+
self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{name}), MP_ROM_PTR(&{name}_obj) }},")
307+
308+
def handle_funptr(self, fun):
309+
pass # Ignore function pointers for now
310+
311+
def emit(self):
312+
for row in self.decls:
313+
print(row)
314+
for row in self.body:
315+
print(row)
316+
print()
317+
print("static const mp_rom_map_elem_t module_globals_table[] = {")
318+
print(f" {{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_{self.modname}) }},")
319+
for row in self.locals:
320+
print(f" {row}")
321+
print("};")
322+
print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);")
323+
print(
324+
textwrap.dedent(f"""
325+
const mp_obj_module_t {self.modname}_module = {{
326+
.base = {{ &mp_type_module }},
327+
.globals = (mp_obj_dict_t *)&module_globals,
328+
}};
329+
330+
MP_REGISTER_MODULE(MP_QSTR_{self.modname}, {self.modname}_module);""")
331+
)
332+
333+
for row in self.info:
334+
print(row, file=sys.stderr)
335+
336+
337+
p = Processor(modname)
338+
p.process(defs)
339+
p.emit()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy