Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 98 additions & 12 deletions tools/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,80 @@
"uint32": "uint",
"int64": "long",
"uint64": "ulong",
"int8_t": "sbyte",
"int16_t": "short",
"int32_t": "int",
"int64_t": "long",
"uint16_t": "ushort",
"uint32_t": "uint",
"uint64_t": "ulong",
"signed char": "sbyte",
"unsigned char": "byte",
"unsigned short": "ushort",
"size_t": "ulong",
"ssize_t": "long",
"meosType": "int",
"MeosType": "int",
"interpType": "int",
"tempSubtype": "int",
"spanType": "int",
}

# The names of the catalog's own enums, filled in by ``configure``. A C enum is
# an int at the ABI, and taking the set from the catalog is what keeps a newly
# added enum from arriving as an opaque pointer the way a hand-list leaves it.
ENUM_TYPES: set[str] = set()

# The catalog's structs by name, filled in by ``configure``. An array of struct
# values is walked at the struct's own stride, which the field layout the catalog
# records is what gives.
STRUCTS: dict[str, dict] = {}


def configure(idl: dict) -> None:
"""Take from the catalog the type facts the mapping below reads."""
ENUM_TYPES.clear()
ENUM_TYPES.update(e["name"] for e in idl.get("enums", []) if e.get("name"))
STRUCTS.clear()
STRUCTS.update({s["name"]: s for s in idl.get("structs", []) if s.get("fields")})


# Size and alignment in bytes of the scalar C types a struct field can have, on
# the 64-bit targets the binding ships for.
_SCALAR_BYTES: dict[str, int] = {
"bool": 1, "char": 1, "int8": 1, "int8_t": 1, "uint8": 1, "uint8_t": 1,
"short": 2, "int16": 2, "int16_t": 2, "uint16": 2, "uint16_t": 2,
"int": 4, "int32": 4, "int32_t": 4, "uint32": 4, "uint32_t": 4,
"float": 4, "Oid": 4, "DateADT": 4,
"long": 8, "int64": 8, "int64_t": 8, "uint64": 8, "uint64_t": 8,
"double": 8, "float8": 8, "Datum": 8, "Timestamp": 8, "TimestampTz": 8,
"TimeADT": 8, "size_t": 8, "uintptr_t": 8,
}


def _type_layout(c_type: str) -> tuple[int, int]:
"""``(size, alignment)`` in bytes of a struct field's type: a scalar, a
pointer, a fixed-size array, or a nested catalog struct."""
t = c_type.replace("const ", "").strip()
if t.endswith("*"):
return (8, 8)
if "[" in t:
base, count = t[:t.index("[")].strip(), int(t[t.index("[") + 1:t.index("]")])
size, align = _type_layout(base)
return (size * count, align)
if t in STRUCTS:
return struct_layout(t)
if t not in _SCALAR_BYTES:
raise KeyError(f"struct field type {t!r} has no known layout")
return (_SCALAR_BYTES[t], _SCALAR_BYTES[t])


def struct_layout(name: str) -> tuple[int, int]:
"""``(size, alignment)`` of a catalog struct: each field sits at the offset the
catalog records, and the whole is padded to a multiple of its widest field."""
size = align = 1
for field in STRUCTS[name]["fields"]:
fsize, falign = _type_layout(field["cType"])
align = max(align, falign)
size = max(size, field["offset_bits"] // 8 + fsize)
return ((size + align - 1) // align * align, align)


# C pointer-to-char marshalled as managed string when StringMarshalling.Utf8 is on.
def is_string_pointer(c_type: str) -> bool:
t = c_type.replace("const ", "").strip()
Expand All @@ -77,6 +142,8 @@ def csharp_type_for(canonical: str) -> str:
# Pointer of any depth -> IntPtr (we don't propagate pointer types into C# semantics).
if "*" in t or t.endswith("[]"):
return "IntPtr"
if t in ENUM_TYPES:
return "int"
return SCALAR_MAP.get(t, "IntPtr") # unknown scalar -> opaque pointer is safer than guessing


Expand Down Expand Up @@ -197,6 +264,11 @@ def _csharp_array_element(c_type: str, canonical: str) -> tuple[str, str]:
return (elem, "Marshal.Copy")
if base == "uint8_t":
return ("byte", "ByteBuffer")
if stars == 1 and base in STRUCTS:
# A single pointer to a catalog struct is an array of struct VALUES, not
# of pointers: element i sits at the struct's own stride, and reading it
# as a pointer reads the first eight bytes of the value itself.
return ("IntPtr", f"StructArray:{struct_layout(base)[0]}")
return ("IntPtr", "IntPtrArray")


Expand Down Expand Up @@ -228,7 +300,8 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
ext_call_args: list[str] = []
setup: list[str] = []
teardown: list[str] = []
output_locals: list[tuple[str, dict, str]] = [] # (output_param_local, oa_dict, elem_csharp_type)
# (output_param_local, oa_dict, elem_csharp_type, marshal_strategy)
output_locals: list[tuple[str, dict, str, str]] = []

for p in f.get("params", []):
pname = csharp_param_name(p["name"]) if p["name"] else "arg"
Expand All @@ -245,8 +318,8 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
teardown.append(f" Marshal.FreeHGlobal({local});")
ext_call_args.append(local)
oa = next(oa for oa in output_params if oa["param"] == p["name"])
elem, _strategy = _csharp_array_element(p["cType"], p["canonical"])
output_locals.append((local, oa, elem))
elem, strategy = _csharp_array_element(p["cType"], p["canonical"])
output_locals.append((local, oa, elem, strategy))
continue
sig_params.append(f"{ptype} {pname}")
ext_call_args.append(pname)
Expand All @@ -256,7 +329,7 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
if array_ret:
ret_elem, _ = _csharp_array_element(f["returnType"]["c"], f["returnType"]["canonical"])
ret_pieces.append(f"{ret_elem}[]")
for _, _oa, elem in output_locals:
for _, _oa, elem, _strategy in output_locals:
ret_pieces.append(f"{elem}[]")
if not ret_pieces:
return [] # nothing to type; fall back.
Expand Down Expand Up @@ -289,13 +362,21 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
lines.append(f" {ret_elem}[] _resultArr = new {ret_elem}[_n];")
if ret_strategy == "Marshal.Copy":
lines.append(" Marshal.Copy(_resultPtr, _resultArr, 0, _n);")
elif ret_strategy.startswith("StructArray:"):
stride = ret_strategy.split(":", 1)[1]
lines.append(" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _resultArr[_i] = IntPtr.Add(_resultPtr, _i * {stride}); }}")
else:
lines.append(" for (int _i = 0; _i < _n; _i++)")
lines.append(" { _resultArr[_i] = Marshal.ReadIntPtr(_resultPtr, _i * IntPtr.Size); }")
for local, oa, elem in output_locals:
for local, oa, elem, strategy in output_locals:
lines.append(f" IntPtr _{local}_arr = Marshal.ReadIntPtr({local});")
lines.append(f" {elem}[] _{local}_out = new {elem}[_n];")
if elem == "IntPtr":
if strategy.startswith("StructArray:"):
stride = strategy.split(":", 1)[1]
lines.append(f" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _{local}_out[_i] = IntPtr.Add(_{local}_arr, _i * {stride}); }}")
elif elem == "IntPtr":
lines.append(f" for (int _i = 0; _i < _n; _i++)")
lines.append(f" {{ _{local}_out[_i] = Marshal.ReadIntPtr(_{local}_arr, _i * IntPtr.Size); }}")
else:
Expand All @@ -305,7 +386,7 @@ def _emit_outputs_wrapper(f: dict) -> list[str]:
return_pieces: list[str] = []
if array_ret:
return_pieces.append("_resultArr")
for local, _, _ in output_locals:
for local, _, _, _ in output_locals:
return_pieces.append(f"_{local}_out")
if len(return_pieces) == 1:
lines.append(f" return {return_pieces[0]};")
Expand Down Expand Up @@ -337,6 +418,10 @@ def _copy(indent: str) -> list[str]:
out = [f"{indent}{elem}[] _out = new {elem}[_n];"]
if strategy == "Marshal.Copy":
out.append(f"{indent}Marshal.Copy(_p, _out, 0, _n);")
elif strategy.startswith("StructArray:"):
stride = strategy.split(":", 1)[1]
out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
out.append(f"{indent}{{ _out[_i] = IntPtr.Add(_p, _i * {stride}); }}")
else:
out.append(f"{indent}for (int _i = 0; _i < _n; _i++)")
out.append(f"{indent}{{ _out[_i] = Marshal.ReadIntPtr(_p, _i * IntPtr.Size); }}")
Expand Down Expand Up @@ -453,6 +538,7 @@ def main(idl_path: str, dll_path: str = DLL_PATH) -> None:
DLL_PATH = dll_path
with open(idl_path) as fh:
idl = json.load(fh)
configure(idl)
funcs = idl["functions"]
repo_root = Path(__file__).resolve().parent.parent
out_dir = repo_root / "MEOS.NET" / "Internal"
Expand Down
Loading