m68kmac: Wrap many core parts of the OS.

This is enough to create a window & draw to it! 🎉

Signed-off-by: Jeff Epler <jepler@gmail.com>
This commit is contained in:
Jeff Epler 2025-07-17 20:44:57 -05:00
parent 7ff5b6cf59
commit 1d3bf9f19b
9 changed files with 868 additions and 177 deletions

View file

@ -22,7 +22,7 @@ jobs:
run: |
apt update
apt install -y python3-pip
pip install pyyaml
pip install pyyaml click
git config --global --add safe.directory $(pwd)
make -C mpy-cross -j$(nproc)
make -C ports/m68kmac submodules

View file

@ -23,6 +23,7 @@ UNAME_S := $(shell uname -s)
LD = $(CXX)
CFLAGS += $(INC) -Wall -Wdouble-promotion -Wfloat-conversion -std=c99 $(COPT)
CFLAGS += --param=min-pagesize=0
CFLAGS += -save-temps=obj
CSUPEROPT = -Os # save some code space
# Tune for Debugging or Optimization
@ -43,7 +44,7 @@ SRC_C = \
main.c \
vfs_mac.c \
macutil.c \
$(BUILD)/modqd.c \
multiverse_support.c \
SRC_C += \
shared/readline/readline.c \
@ -66,21 +67,35 @@ SRC_QSTR += \
vfs_mac.c \
shared/readline/readline.c \
shared/runtime/pyexec.c \
$(BUILD)/modqd.c \
.PHONY: mkapi
DEFS = lib/multiversal/defs/MacTypes.yaml
TDEFS = $(patsubst %, -t %, $(DEFS))
# $(eval $(call multiversal_module, module name, defs files))
define multiversal_module
$$(BUILD)/mod$(1).c: tools/mkapi.py $(filter-out -t,$(2)) $$(DEFS)
$$(ECHO) "MKAPI $(1)"
$$(Q)$$(MKDIR) -p $$(BUILD)
$$(Q)$$(PYTHON) tools/mkapi.py -o $$@ -m $(1) $$(TDEFS) $(2)
SRC_C += $(BUILD)/mod$(1).c
SRC_QSTR += $(BUILD)/mod$(1).c
mkapi:: $$(BUILD)/mod$(1).c
endef
$(eval $(call multiversal_module,qd,lib/multiversal/defs/QuickDraw.yaml etc/QuickDrawExtras.yaml))
$(eval $(call multiversal_module,windowmgr,lib/multiversal/defs/WindowMgr.yaml -t lib/multiversal/defs/QuickDraw.yaml))
$(eval $(call multiversal_module,toolboxevent,lib/multiversal/defs/ToolboxEvent.yaml -t lib/multiversal/defs/QuickDraw.yaml))
$(eval $(call multiversal_module,toolboxutil,lib/multiversal/defs/ToolboxUtil.yaml -t lib/multiversal/defs/EventMgr.yaml -t lib/multiversal/defs/QuickDraw.yaml))
$(eval $(call multiversal_module,mactypes,lib/multiversal/defs/MacTypes.yaml etc/MacTypesExtras.yaml))
$(eval $(call multiversal_module,eventmgr,lib/multiversal/defs/EventMgr.yaml -t lib/multiversal/defs/QuickDraw.yaml))
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))
.PHONY: mkapi
mkapi: $(BUILD)/modqd.c
$(BUILD)/modqd.c: tools/mkapi.py
$(ECHO) "MKAPI $@"
$(Q)$(MKDIR) -p $(BUILD)
$(Q)$(PYTHON) tools/mkapi.py lib/multiversal/defs/QuickDraw.yaml qd > $@
all: $(BUILD)/micropython.bin
$(BUILD)/micropython.code.bin: $(OBJ)
@ -110,6 +125,8 @@ run:
cp $(BASE_IMG) $(BUILD)/run.dsk
hmount $(BUILD)/run.dsk
hcopy $(BUILD)/micropython.bin ":Desktop Folder"
hcopy code.py ":Desktop Folder"
hattrib -t mupy -c mupy -t text ":Desktop Folder:code.py"
$(UMAC) -d build/run.dsk
include $(TOP)/py/mkrules.mk

45
ports/m68kmac/code.py Normal file
View file

@ -0,0 +1,45 @@
# m68k-micropython window creation demo
import eventmgr
import mactypes
import qd
import uctypes
import windowmgr
def let(dst, src):
memoryview(dst)[:] = memoryview(src)[:]
def pstr(s):
b = mactypes.Str255()
b[0] = len(s)
for i in range(len(s)):
b[i + 1] = ord(s[i])
return b
ev = eventmgr.EventRecord()
NIL_WINDOW = uctypes.struct(0, qd.GrafPort)
ABOVE_ALL_WINDOWS = uctypes.struct(-1, qd.GrafPort)
title = pstr("Hello World")
r = mactypes.Rect()
scrn = qd.qdGlobals().screenBits
let(r, scrn.bounds)
r.top += 80
qd.InsetRect(r, 25, 25)
w = windowmgr.NewWindow(NIL_WINDOW, r, title, True, 0, ABOVE_ALL_WINDOWS, True, 0)
let(r, w.portRect)
qd.SetPort(w)
mid_x = (r.left + r.right) // 2
mid_y = (r.top + r.bottom) // 2
for i in range(r.left, r.right, 2):
qd.MoveTo(mid_x, r.bottom)
qd.LineTo(i, r.top)
qd.MoveTo(mid_x, mid_y)
input("hit enter to exit")

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,22 @@
- pyverbatim:
content: |
#include "uart_core.h"
mp_obj_t __init___fn(void) {
mp_hal_stdout_tx_strn(NULL, 0); // this suffices to create the console
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_0(__init___obj, __init___fn);
name: __init__
- pyverbatim:
typedef_content: |
// because we fix the constness of DrawText in our multiversal fork but build
// against retro68's flawed multiversal ...
#define DrawText(a, b, c) DrawText((char *)a, b, c)
- pyverbatim:
typedef_content: |
static QDGlobals *qdGlobals() {
return &qd;
}
- function:
name: qdGlobals
return: QDGlobals*

View file

@ -0,0 +1,101 @@
#include "multiverse_support.h"
MP_DECLARE_CTYPES_STRUCT(Point_obj);
#define mp_obj_get_type_qstr(o_in) ((qstr)(mp_obj_get_type((o_in))->name))
void *to_struct_helper(mp_obj_t obj, const mp_obj_type_t *struct_type, bool is_const) {
if (obj == mp_const_none) {
return NULL;
}
if (struct_type && !mp_obj_is_type(obj, struct_type)) {
mp_raise_msg_varg(&mp_type_TypeError,
MP_ERROR_TEXT("Expected %q, got %q"), (qstr)struct_type->name, mp_obj_get_type_qstr(obj));
}
mp_buffer_info_t bufinfo = {0};
mp_get_buffer_raise(obj, &bufinfo, is_const ? MP_BUFFER_READ : MP_BUFFER_READ | MP_BUFFER_WRITE);
return bufinfo.buf;
}
mp_obj_t from_struct_helper(void *buf, const mp_obj_type_t *type) {
mp_obj_t args[] = { mp_obj_new_int((mp_uint_t)buf), MP_OBJ_FROM_PTR(type) };
return uctypes_struct_make_new(type, 2, 0, args);
}
void *to_scalar_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 (objsize > 1 && bufinfo.len != objsize) {
mp_raise_ValueError(MP_ERROR_TEXT("buffer has wrong length"));
}
return bufinfo.buf;
}
mp_obj_t from_scalar_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);
}
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_struct_helper(obj, (const mp_obj_type_t *)&Point_obj, true);
}
return result;
}
pascal LONGINT GetDblTime(void) {
return LMGetDoubleTime();
}
pascal LONGINT GetCaretTime(void) {
return LMGetCaretTime();
}
typedef struct {
INTEGER count;
Byte data[0];
} patternlist;
pascal void GetIndPattern(Byte *pat, INTEGER patListId, INTEGER index) {
Handle h = (Handle)GetResource('PAT#', patListId);
if (!h) {
return;
}
LoadResource(h);
if (ResError() != noErr) {
return;
}
patternlist *patterns = (patternlist *)*h;
if (index < 0 || index >= patterns->count) {
return;
}
memcpy(pat, patterns->data + 8 * index, 8);
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "py/obj.h"
#include "py/runtime.h"
#include "extmod/moductypes.h"
#include <Multiverse.h>
// 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); })
#define ROM_TUPLE(...) \
{{&mp_type_tuple}, MP_ARRAY_SIZE(((mp_obj_t[]) {__VA_ARGS__})), {__VA_ARGS__}}
void *to_struct_helper(mp_obj_t obj, const mp_obj_type_t *struct_type, bool is_const);
mp_obj_t from_struct_helper(void *buf, const mp_obj_type_t *type);
void *to_scalar_helper(mp_obj_t obj, size_t objsize, bool is_const);
mp_obj_t from_scalar_helper(void *buf, size_t objsize, bool is_signed_hint);
mp_obj_t LMGet_common(long address, size_t objsize, mp_obj_t arg);
void LMSet_common(long address, size_t objsize, mp_obj_t arg);
Point Point_to_c(mp_obj_t obj);

View file

@ -1,18 +1,313 @@
import pathlib
import sys
import subprocess
import textwrap
import yaml
import dataclasses
import click
from functools import singledispatchmethod
from dataclasses import dataclass, Field
from typing import Any, get_args, get_origin, Union
with open(sys.argv[1]) as f:
defs = yaml.safe_load(f)
if len(sys.argv) > 2:
modname = sys.argv[2]
if sys.version_info >= (3, 10):
from types import UnionType
else:
modname = pathlib.Path(sys.argv[1]).stem
UnionType = type(Union[int, float])
signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t', 'Fixed', 'GrafVerb', 'CharParameter'}
unsigned_integer_types = {'int8_t', 'int16_t', 'int32_t'}
@dataclass(frozen=True)
class Ptr:
pointee: Any
is_const: bool = False
def __str__(self):
return f"{self.pointee}*"
@dataclass(frozen=True)
class Array:
pointee: Any
bound: int
is_const: bool = False
@dataclass
class CommonFields:
comment: str | None = None
only_for: str | None = None
not_for: str | None = None
api: str | None = None
doc: str | None = None
emit: bool = True
def toplevel(cls):
for f in dataclasses.fields(CommonFields):
setattr(cls, f.name, f)
cls.__annotations__[f.name] = CommonFields.__annotations__[f.name]
return dataclass(cls)
@dataclass
class StructMember:
name: str
type: str
comment: str | None = None
@toplevel
class Union:
name: str
members: list[StructMember] = dataclasses.field(default_factory=list)
size: int | None = None
@toplevel
class Struct:
name: str
members: list[StructMember] = dataclasses.field(default_factory=list)
size: int | None = None
@dataclass
class EnumMember:
name: str
value: int | str | None = None # a number or a 4 character literal
@toplevel
class Enum:
values: list[EnumMember]
name: str | None = None
@toplevel
class Typedef:
name: str
type: str
size: int | None = None # typedef AE_hdlr_t has this .. why?
fulltype: any = None
@toplevel
class LowMem:
name: str
type: str
address: int
@toplevel
class PyVerbatim:
content: str | None = None
typedef_content: str | None = None
name: str | None = None
@toplevel
class Verbatim:
verbatim: str
@dataclass
class Argument:
type: str
name: str = ""
register: str | None = None
@toplevel
class Function:
name: str
trap: int | None = None
args: list[Argument] = dataclasses.field(default_factory=list)
executor: str | bool | None = None
return_: str | None = None
inline: str | None = None
noinline: str | None = None
dispatcher: str | None = None
selector: int | None = None
returnreg: str | None = None
@toplevel
class FunPtr:
name: str
args: list[Argument] = dataclasses.field(default_factory=list)
return_: str | None = None
@toplevel
class Dispatcher:
name: str
trap: int
selector_location: str | None = None
yaml_types = {
'function': Function,
'funptr': FunPtr,
'verbatim': Verbatim,
'pyverbatim': PyVerbatim,
'lowmem': LowMem,
'typedef': Typedef,
'enum': Enum,
'struct': Struct,
'union': Union,
'dispatcher': Dispatcher,
}
def fix_key(k):
k = k.replace('-', '_')
if k == 'return':
k += "_"
return k
def identify(y):
for k in y.keys():
if r := yaml_types.get(k):
result = dict(y)
if k != 'verbatim':
# Verbatim just has a str value, it's not a namespace
result.update(result.pop(k, {}))
return result, r
raise RuntimeError(f"Could not identify field type for {y!r}")
@dataclass
class OneOf:
types: tuple[type]
def __call__(self, value):
for t in self.types:
try:
return t(value)
except TypeError:
continue
else:
raise TypeError(f"Could not convert {value} to any of {self.types}")
def get_field_type(field: Field[Any] | type) -> Any:
if isinstance(field, type):
return field
field_type = field.type
if isinstance(field_type, str):
raise RuntimeError("parameters dataclass may not use 'from __future__ import annotations")
origin = get_origin(field_type)
if origin in (Union, UnionType):
return OneOf(tuple(a for a in get_args(field_type) if a is not None))
if origin is list:
return [get_field_type(get_args(field_type)[0])]
return field_type
def yaml_to_type(y, t=None):
if t is None:
y, t = identify(y)
if isinstance(t, OneOf):
if isinstance(y, t.types):
return y
else:
if isinstance(y, t):
return y
try:
kwargs = {}
fields = {f.name: f for f in dataclasses.fields(t)}
for k, v in y.items():
k1 = fix_key(k)
field = fields[k1]
field_type = get_field_type(field)
if isinstance(v, list):
kwargs[k1] = [yaml_to_type(vi, field_type[0]) for vi in v]
else:
kwargs[k1] = yaml_to_type(v, field_type)
return t(**kwargs)
except (KeyError, TypeError, AttributeError) as e:
raise TypeError(f"Converting node {y} to {t}") from e
def load_defs(path):
with open(path) as f:
defs = yaml.safe_load(f)
return [yaml_to_type(d) for d in defs]
signed_integer_types = {'uint8_t', 'uint16_t', 'uint32_t'}
unsigned_integer_types = {'bool', 'char', 'int8_t', 'int16_t', 'int32_t'}
all_integer_types = signed_integer_types | unsigned_integer_types
all_float_types = {"float", "double"}
all_scalar_types = all_integer_types | all_float_types
@dataclass
class descr_maker_scalar:
tag: str
def __call__(self, emitter, offset):
return f"MP_ROM_INT(UCTYPE_TYPE({self.tag}) | {offset})"
@dataclass
class descr_maker_struct:
tag: str
def __call__(self, emitter, offset):
obj = emitter.common_definition(
"mp_rom_obj_tuple_t", f"ROM_TUPLE(MP_ROM_INT({offset}), MP_ROM_PTR(&{self.tag}_obj))"
)
return f"MP_ROM_PTR(&{obj})"
@dataclass
class descr_maker_arr_scalar:
tag: str
size: int
def __call__(self, emitter, offset):
obj = emitter.common_definition(
"mp_rom_obj_tuple_t",
f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(ARRAY)), MP_ROM_INT({self.tag} | {self.size}))",
)
return f"MP_ROM_PTR(&{obj})"
@dataclass
class descr_maker_arr_struct:
tag: str
size: int
def __call__(self, emitter, offset):
obj = emitter.common_definition(
"mp_rom_obj_tuple_t",
f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(ARRAY)), MP_ROM_INT({self.size}), MP_ROM_PTR(&{self.tag}_obj))",
)
return f"MP_ROM_PTR(&{obj})"
@dataclass
class descr_maker_ptr_scalar:
tag: str
def __call__(self, emitter, offset):
obj = emitter.common_definition(
"mp_rom_obj_tuple_t",
f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(PTR)), MP_ROM_INT({self.tag}))",
)
return f"MP_ROM_PTR(&{obj})"
class descr_maker_ptr_struct:
def __init__(self, tag):
self.tag = tag
def __call__(self, emitter, offset):
obj = emitter.common_definition(
"mp_rom_obj_tuple_t",
f"ROM_TUPLE(MP_ROM_INT({offset} | UCTYPE_AGG(PTR)), MP_ROM_PTR(&{self.tag}_obj))",
)
return f"MP_ROM_PTR(&{obj})"
class PointConverter:
@ -20,7 +315,7 @@ class PointConverter:
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)"
raise RuntimeError("not implemented")
converters = {
@ -28,7 +323,7 @@ converters = {
}
class SimpleConverter:
class ScalarConverter:
def __init__(self, type_c, to_c, to_py):
self.type_c = type_c
self.to_c = to_c
@ -43,30 +338,58 @@ class SimpleConverter:
def emit_to_py(self, name_c):
return f"{self.to_py}({name_c})"
def emit_call_arg(self, name_c):
return name_c
class PtrConverter:
def __init__(self, type_c):
def __init__(self, type_c, type_obj, *, deref=False, is_const=False):
print(f"PtrConverter({type_c} {type_obj}")
self.type_c = type_c
self.type_obj = type_obj
self.deref = deref
self.is_const = is_const
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"
is_const = +self.is_const # to get 0/1, not True/False
return (
f"{self.type_c} {name_c} = to_struct_helper({name_py}, {self.type_obj}, {is_const});"
)
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})"
return f"from_struct_helper({name_c}, {self.type_obj});"
def emit_call_arg(self, name_c):
if self.deref:
return f"*{name_c}"
return name_c
def make_converter(type_c):
def make_converter(emitter, 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}")
resolved_type = emitter.parse_type(type_c)
print(f"{type_c} -> {resolved_type}")
if resolved_type in signed_integer_types:
return ScalarConverter(resolved_type, "mp_obj_get_int", "mp_obj_new_int")
if resolved_type in emitter.funptrs:
return ScalarConverter(
resolved_type, f"({type_c})mp_obj_get_int_truncated", "mp_obj_new_int_from_ptr"
)
if resolved_type in unsigned_integer_types:
return ScalarConverter(
resolved_type, "mp_obj_get_int_truncated", "mp_obj_new_int_from_uint"
)
if isinstance(resolved_type, Ptr):
base_type = resolved_type.pointee
if base_type in emitter.structs:
return PtrConverter(
type_c, f"(const mp_obj_type_t*)&{base_type}_obj", is_const=resolved_type.is_const
)
emitter.info.append(f"confused about {base_type} from {resolved_type} from {type_c}")
return PtrConverter(type_c, "NULL", is_const=resolved_type.is_const)
print(f"note: {emitter.funptrs}")
raise ValueError(f"no converter possible for {type_c} ({resolved_type})")
class Processor:
@ -75,132 +398,239 @@ class Processor:
self.decls = []
self.body = []
self.locals = []
self.definitions = {}
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 <Multiverse.h>
self.types = {}
self.typedef_objs = set()
self.structs = {}
self.funptrs = set(("ProcPtr",))
self.decls_dedent("""
#include "multiverse_support.h"
// 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); })
static 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 (objsize > 1 && 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);
}
""")
self.add_local("__name__", f"MP_ROM_QSTR(MP_QSTR_{self.modname})")
def resolve_type(self, typename):
is_const = self.is_const(typename)
typename = typename.removeprefix("const ")
def common_definition(self, c_type: str, c_value: str) -> str:
k = (c_type, c_value)
if k not in self.definitions:
i = len(self.definitions)
self.definitions[k] = i
self.decls_dedent(f"static {c_type} common_{i} = {c_value};")
return f"common_{self.definitions[k]}"
while typename in self.types:
typename = self.types[typename]
def is_array(self, typename):
if typename.count(']') + ("*" in typename) > 1:
raise ValueError(
f"array-of-array or pointer-to-array or array-of-pointers NYI {typename}"
)
return typename.endswith("]")
if is_const and self.is_ptr(typename):
return f"const {typename}"
def remove_array(self, typename):
return typename.partition("[")[0]
return typename
def remove_ptr(self, typename):
return typename.removesuffix("*")
def remove_const(self, typename):
return typename.removeprefix("const ")
def array_size(self, typename):
return int(typename.partition("[")[2].removesuffix("]"))
def is_ptr(self, typename):
return typename.endswith("*") or typename.endswith("Handle") or typename.endswith("Ptr")
return typename.endswith("*")
def is_scalar(self, typename):
if hasattr(typename, 'pointee'):
typename = typename.pointee
return typename in all_scalar_types
def is_const(self, typename):
return typename.startswith("const ")
def decls_dedent(self, text):
self.decls.append(textwrap.dedent(text.rstrip()))
def body_dedent(self, text):
self.body.append(textwrap.dedent(text.rstrip()))
def process(self, defs):
def parse_type(self, typestr):
print("parse_type", typestr)
if typestr == 'RgnHandle':
print(self.types)
while typestr in self.types:
typestr = self.types[typestr]
is_const = self.is_const(typestr)
base_type = self.remove_const(typestr)
if self.is_array(base_type):
bound = self.array_size(base_type)
base_type = self.parse_type(self.remove_array(base_type))
return Array(base_type, bound, is_const=is_const)
elif self.is_ptr(base_type):
print(base_type, self.remove_ptr(base_type))
base_type = self.parse_type(self.remove_ptr(base_type))
return Ptr(base_type, is_const=is_const)
else:
if is_const:
base_type = self.parse_type(self.remove_const(base_type))
return dataclasses.replace(base_type, is_const=True)
return typestr
def typedefs(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 isinstance(d, Typedef):
d.fulltype = self.parse_type(d.type)
print(f"full type of {d.name} is {d.fulltype}")
self.types[d.name] = d.type
self.decls_dedent(f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj);")
if isinstance(d, Struct) and d.members:
self.structs[d.name] = d
self.decls_dedent(f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj);")
if isinstance(d, Union) and d.members:
self.structs[d.name] = d
self.decls_dedent(f"MP_DECLARE_CTYPES_STRUCT({d.name}_obj);")
if isinstance(d, PyVerbatim) and d.typedef_content:
self.decls_dedent(d.typedef_content)
if isinstance(d, FunPtr):
self.funptrs.add(d.name)
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 emit(self, defs):
for d in defs:
try:
self.emit_node(d)
except Exception as e:
raise RuntimeError(f"failed to convert {d}") from e
def handle_unknown(self, k, v):
if k in self.unknowns:
@singledispatchmethod
def emit_node(self, node):
if type(node) in self.unknowns:
return
self.unknowns.add(k)
self.info.append(f"# Unknown {k} {v!r:.55s}...")
self.unknowns.add(type(node))
self.info.append(f"# Unknown {node!r:.68s}...")
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']}) }},"
)
@emit_node.register
def emit_typedef(self, typedef: Typedef):
name = typedef.name
type = typedef.type
if type.endswith("*") or type.endswith("]"):
make_descr = self.type_details(typedef.type)
if make_descr is None:
return
offset = 0
self.body_dedent(f"""
MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, {make_descr(self, offset)}, LAYOUT_NATIVE);
""")
self.typedef_objs.add(name)
self.add_local(name)
offset = 0
def type_details(self, typename):
if typename in self.funptrs:
return None
fulltype = self.parse_type(typename)
is_ptr = isinstance(fulltype, Ptr)
is_array = isinstance(fulltype, Array)
basetypename = fulltype if isinstance(fulltype, str) else fulltype.pointee
if basetypename in all_scalar_types:
u = "U" if basetypename.startswith("u") else ""
if basetypename == "bool":
type_str = "UINT8"
elif basetypename == "char":
type_str = "INT8"
elif basetypename == "double":
type_str = "FLOAT64"
elif basetypename == "float":
type_str = "FLOAT32"
elif basetypename.endswith(("32", "32_t")):
type_str = f"{u}INT32"
elif basetypename.endswith(("16", "16_t")):
type_str = f"{u}INT16"
elif basetypename.endswith(("8", "8_t")):
type_str = f"{u}INT8"
else:
raise RuntimeError(f"teach me about {basetypename}")
if is_ptr:
descr = descr_maker_ptr_scalar(type_str)
elif is_array:
descr = descr_maker_arr_scalar(type_str, fulltype.bound)
else:
descr = descr_maker_scalar(type_str)
else:
if is_ptr:
if basetypename in self.structs:
descr = descr_maker_ptr_struct(basetypename)
else:
descr = descr_maker_ptr_scalar("UINT8")
elif is_array:
descr = descr_maker_arr_struct(basetypename, fulltype.bound)
else:
descr = descr_maker_struct(basetypename)
return descr
def struct_make_table(self, struct):
rows = []
for member in struct.members:
make_descr = self.type_details(member.type)
if make_descr is None:
continue
offset = f"offsetof({struct.name}, {member.name})"
rows.append(f"{{ MP_ROM_QSTR(MP_QSTR_{member.name}), {make_descr(self, offset)} }},")
return "\n".join(rows)
def union_make_table(self, union):
rows = []
for member in union.members:
make_descr = self.type_details(member.type)
if make_descr is None:
continue
rows.append(f"{{ MP_ROM_QSTR(MP_QSTR_{member.name}), {make_descr(self, 0)} }},")
return "\n".join(rows)
@emit_node.register
def emit_union(self, e: Union):
name = e.name
self.body_dedent(f"""
static const mp_rom_map_elem_t {name}_descr_table[] = {{
{self.union_make_table(e)}
}};
static MP_DEFINE_CONST_DICT({name}_descr_dict, {name}_descr_table);
MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, MP_ROM_PTR((void*)&{name}_descr_dict), LAYOUT_NATIVE);
""")
self.add_local(name)
@emit_node.register
def emit_struct(self, e: Struct):
name = e.name
ee = self.structs.get(e.name)
if ee and (e is not ee):
print(f"omitting {e} in favor of {ee}")
return # better defn available
self.body_dedent(f"""
static const mp_rom_map_elem_t {name}_descr_table[] = {{
{self.struct_make_table(e)}
}};
static MP_DEFINE_CONST_DICT({name}_descr_dict, {name}_descr_table);
MP_DEFINE_CTYPES_STRUCT({name}_obj, MP_QSTR_{name}, MP_ROM_PTR((void*)&{name}_descr_dict), LAYOUT_NATIVE);
""")
self.add_local(name)
@emit_node.register
def emit_enum(self, e: Enum):
for v in e.values:
if v.value is not None and v.name is not None:
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']
@emit_node.register
def emit_lowmem(self, lm: LowMem):
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]);
@ -213,19 +643,25 @@ class Processor:
}}
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) }},"
)
self.add_local(f"LMGet{name}")
self.add_local(f"LMSet{name}")
def handle_typedef(self, v):
pass # ignore typedefs for now ??
def add_local(self, name, value=...):
if value is ...:
value = f"MP_ROM_PTR(&{name}_obj)"
self.locals.append(f"{{ MP_ROM_QSTR(MP_QSTR_{name}), {value} }},")
def handle_verbatim(self, v):
@emit_node.register
def emit_verbatim(self, v: Verbatim):
pass # Ignore verbatim blocks
@emit_node.register
def emit_pyverbatim(self, v: PyVerbatim):
if v.content:
self.body.append(v.content)
if v.name is not None:
self.add_local(v.name)
# {'args': [{'name': 'src_bitmap', 'type': 'BitMap*'},
# {'name': 'dst_bitmap', 'type': 'BitMap*'},
# {'name': 'src_rect', 'type': 'const Rect*'},
@ -238,13 +674,14 @@ class Processor:
def fun_declare_args_enum(self, args):
if args:
args = ", ".join(f"ARG_{arg['name']}" for arg in args)
argnames = [arg.name or f"arg{i}" for i, arg in enumerate(args)]
args = ", ".join(f"ARG_{argname}" for argname in argnames)
return f"enum {{ {args} }};"
return ""
@staticmethod
def fun_declare_allowed_arg(arg):
name = arg['name']
name = arg.name
return f"{{ MP_QSTR_{name}, MP_ARG_OBJ | MP_ARG_REQUIRED, {{0}}, }},"
def fun_parse_args(self, args):
@ -259,65 +696,73 @@ class Processor:
return "\n".join(f" {line}" for line in body)
def make_converter(self, typename):
if not typename.endswith('*'):
typename = self.resolve_type(typename)
return make_converter(typename)
return make_converter(self, typename)
def fun_convert_arg(self, idx, arg):
return self.make_converter(arg['type']).emit_to_c(f"args[{idx}].u_obj", arg['name'])
return self.make_converter(arg.type).emit_to_c(
f"args[{idx}].u_obj", arg.name or f"arg{idx}"
)
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});"
return_type = fun.return_
args = fun.args
argnames = [arg.name or f"arg{i}" for i, arg in enumerate(args)]
print(argnames)
fun_args = ", ".join(argnames)
if fun.inline:
funcall = f"{fun.inline};"
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)
return_type = fun.return_
if return_type:
converter = self.make_converter(return_type)
return f" return {converter.emit_to_py('retval')};// TODO"
return f" return {converter.emit_to_py('retval')};"
else:
return " return mp_const_none;"
def handle_function(self, fun):
name = fun['name']
args = fun.get('args', [])
if self.api == 'carbon':
@emit_node.register
def emit_function(self, node: Function):
name = node.name
args = node.args
if node.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)}
{self.fun_call_fun(node)}
{self.fun_convert_return(node)}
}}
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) }},")
self.add_local(name)
def handle_funptr(self, fun):
@emit_node.register
def emit_funptr(self, node: FunPtr):
pass # Ignore function pointers for now
def emit(self):
def make_output(self, target):
def do_print(*args):
print(*args, file=target)
for row in self.decls:
print(row)
do_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}) }},")
do_print(row)
do_print()
do_print("static const mp_rom_map_elem_t module_globals_table[] = {")
for row in self.locals:
print(f" {row}")
print("};")
print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);")
print(
do_print(f" {row}")
do_print("};")
do_print("static MP_DEFINE_CONST_DICT(module_globals, module_globals_table);")
do_print(
textwrap.dedent(f"""
const mp_obj_module_t {self.modname}_module = {{
.base = {{ &mp_type_module }},
@ -331,6 +776,31 @@ class Processor:
print(row, file=sys.stderr)
p = Processor(modname)
p.process(defs)
p.emit()
@click.command
@click.argument("defs_files", type=click.Path(path_type=pathlib.Path, exists=True), nargs=-1)
@click.option("-o", "--output", type=click.Path(path_type=pathlib.Path), default=None)
@click.option(
"-t", "--typedefs", multiple=True, type=click.Path(path_type=pathlib.Path, exists=True)
)
@click.option("-m", "--modname", type=str)
def main(defs_files, output, modname, typedefs):
if output is None:
output = pathlib.Path(f"mod{modname}.c")
processor = Processor(modname)
for t in typedefs:
defs = load_defs(t)
processor.typedefs(defs)
defs = []
for f in defs_files:
defs.extend(load_defs(f))
processor.typedefs(defs)
processor.emit(defs)
with open(output, "w") as f:
processor.make_output(f)
if __name__ == '__main__':
main()

View file

@ -55,6 +55,10 @@
#include "genhdr/mpversion.h"
#include "input.h"
#if defined(MICROPY_UNIX_COVERAGE)
#include "extmod/moductypes.h"
#endif
// Command line options, with their defaults
static bool compile_only = false;
static uint emit_opt = MP_EMIT_OPT_NONE;
@ -609,10 +613,21 @@ MP_NOINLINE int main_(int argc, char **argv) {
#if defined(MICROPY_UNIX_COVERAGE)
{
static const mp_rom_map_elem_t rect_descr_table[] = {
{ MP_ROM_QSTR(MP_QSTR_top), MP_ROM_INT(UCTYPE_TYPE(INT16) | 0) },
{ MP_ROM_QSTR(MP_QSTR_left), MP_ROM_INT(UCTYPE_TYPE(INT16) | 2) },
{ MP_ROM_QSTR(MP_QSTR_bottom), MP_ROM_INT(UCTYPE_TYPE(INT16) | 4) },
{ MP_ROM_QSTR(MP_QSTR_right), MP_ROM_INT(UCTYPE_TYPE(INT16) | 6) },
};
static MP_DEFINE_CONST_DICT(rect_descr_dict, rect_descr_table);
static MP_DEFINE_CTYPES_STRUCT(rect_type_obj, MP_QSTR_Rect, MP_ROM_PTR((void *)&rect_descr_dict), LAYOUT_NATIVE);
MP_DECLARE_CONST_FUN_OBJ_0(extra_coverage_obj);
MP_DECLARE_CONST_FUN_OBJ_0(extra_cpp_coverage_obj);
mp_store_global(MP_QSTR_extra_coverage, MP_OBJ_FROM_PTR(&extra_coverage_obj));
mp_store_global(MP_QSTR_extra_cpp_coverage, MP_OBJ_FROM_PTR(&extra_cpp_coverage_obj));
mp_store_global(MP_QSTR_Rect, MP_OBJ_FROM_PTR(&rect_type_obj));
}
#endif