diff --git a/pyosys/.gitignore b/pyosys/.gitignore index f9fbdf4f6..925a2bd84 100644 --- a/pyosys/.gitignore +++ b/pyosys/.gitignore @@ -1 +1,2 @@ wrappers.cc +wrappers.inc.cc diff --git a/pyosys/__init__.py b/pyosys/__init__.py index 4622464da..d74e3f5bd 100644 --- a/pyosys/__init__.py +++ b/pyosys/__init__.py @@ -1,4 +1,3 @@ - import os import sys diff --git a/pyosys/generator.py b/pyosys/generator.py index ebe89737e..b8a7aa8dc 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -15,2025 +15,712 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -# Author: Benedikt Tutzer -# Modified for pybind11 by: Mohamed Gaber +# Written by Mohamed Gaber +# +# Inspired by py_wrap_generator.py by Benedikt Tutzer -import argparse -import copy -from enum import Enum -from io import StringIO +import io +import shutil from pathlib import Path -from functools import wraps -from typing import ClassVar, Optional +from sysconfig import get_paths +from dataclasses import dataclass, field +from typing import Any, Dict, FrozenSet, Iterable, Tuple, Union, Optional, List + +import pybind11 +import argparse +from cxxheaderparser.simple import parse_file, ClassScope, NamespaceScope, EnumDecl +from cxxheaderparser.options import ParserOptions +from cxxheaderparser.preprocessor import make_gcc_preprocessor +from cxxheaderparser.types import ( + PQName, + Type, + Pointer, + Reference, + MoveReference, + AnonymousName, + Method, + Function, + Field, + Variable, + Array, + FundamentalSpecifier, +) __file_dir__ = Path(__file__).absolute().parent +__yosys_root__ = __file_dir__.parent -def autostring(fn): - @wraps(fn) - def wrapper(*args, **kwargs): - if "stream" not in kwargs: - stream = StringIO() - fn(*args, stream=stream, **kwargs) - return stream.getvalue() - else: - fn(*args, **kwargs) +@dataclass +class PyosysClass: + """ + Metadata about classes or structs intended to be wrapped using Pyosys. - return wrapper + :param name: The base name of the class (without extra qualifiers) + :param ref_only: Whether this class can be copied to Python, or only + referenced. + :param string_expr: A C++ expression that will be used for the __str__ method in Python + :param hash_expr: A C++ expression that will be fed to ``run_hash`` to declare a __hash__ method for Python + :param denylist: If specified, one or more methods can be excluded from + wrapping. + """ + name: str + ref_only: bool = False + + # in the format s.(method or property) + string_expr: Optional[str] = None + hash_expr: Optional[str] = None + + denylist: FrozenSet[str] = frozenset({}) -# Map c++ operator Syntax to Python functions -wrappable_operators = { - "<": "__lt__", - "==": "__eq__", - "!=": "__ne__", - "+": "__add__", - "-": "__sub__", - "*": "__mul__", - "/": "__div__", - "()": "__call__", -} +@dataclass +class PyosysHeader: + """ + :param name: The name of the header, i.e., its relative path to the Yosys root + :param classes: A list of ``PyosysClass`` classes to be wrapped + :param enums: A list of enums to be wrapped + """ + name: str + classes: List[PyosysClass] = field(default_factory=lambda: []) + + def __post_init__(self): + self.classes_by_name = {} + if classes := self.classes: + for cls in classes: + self.classes_by_name[cls.name] = cls + +""" +Add headers and classes here! +""" +global_denylist = frozenset( + { + # deprecated + "builtin_ff_cell_types", + # no implementation + "set_verific_logging", + # can't bridge to python cleanly + ## std::regex + "log_warn_regexes", + "log_nowarn_regexes", + "log_werror_regexes", + ## function pointers + "log_error_atexit", + "log_verific_callback", + } +) +pyosys_headers = [ + # Headers for incomplete types + PyosysHeader("kernel/binding.h"), + PyosysHeader("libs/sha1/sha1.h"), + # Headers for globals + PyosysHeader("kernel/log.h"), + PyosysHeader("kernel/yosys.h"), + PyosysHeader("kernel/cost.h"), + # Headers with classes + PyosysHeader( + "kernel/celltypes.h", + [PyosysClass("CellType", hash_expr="s.type"), PyosysClass("CellTypes")], + ), + PyosysHeader("kernel/consteval.h", [PyosysClass("ConstEval")]), + PyosysHeader( + "kernel/register.h", + [ + # PyosysClass("Pass") # Virtual methods, manually bridged + ], + ), + PyosysHeader( + "kernel/rtlil.h", + [ + PyosysClass( + "IdString", + string_expr="s.str()", + hash_expr="s.str()", + denylist=frozenset( + # shouldn't be messed with from python in general + { + "global_id_storage_", + "global_id_index_", + "global_refcount_storage_", + "global_free_idx_list_", + "last_created_idx_ptr_", + "last_created_idx_", + "builtin_ff_cell_types", + } + ), + ), + PyosysClass("Const", string_expr="s.as_string()", denylist=frozenset({"bits", "bitvectorize"})), + PyosysClass("AttrObject", denylist=frozenset({"get_blackbox_attribute"})), + PyosysClass("NamedObject", denylist=frozenset({"get_blackbox_attribute"})), + PyosysClass("Selection"), + # PyosysClass("Monitor"), # Virtual methods, manually bridged + PyosysClass("CaseRule", denylist=frozenset({"get_blackbox_attribute"})), + PyosysClass("SwitchRule", denylist=frozenset({"get_blackbox_attribute"})), + PyosysClass("SyncRule"), + PyosysClass( + "Process", + ref_only=True, + string_expr="s.name.c_str()", + hash_expr="s.name", + ), + PyosysClass("SigChunk"), + PyosysClass("SigBit", hash_expr="s"), + PyosysClass("SigSpec", hash_expr="s"), + PyosysClass( + "Cell", + ref_only=True, + string_expr="s.name.c_str()", + hash_expr="s", + ), + PyosysClass( + "Wire", + ref_only=True, + string_expr="s.name.c_str()", + hash_expr="s", + ), + PyosysClass( + "Memory", + ref_only=True, + string_expr="s.name.c_str()", + hash_expr="s", + ), + PyosysClass( + "Module", + ref_only=True, + string_expr="s.name.c_str()", + hash_expr="s", + denylist=frozenset({"Pow"}), # has no implementation + ), + PyosysClass( + "Design", + ref_only=True, + string_expr="s.hashidx_", + hash_expr="s", + denylist=frozenset({"selected_whole_modules"}), # deprecated + ), + ], + ), +] + + +@dataclass(frozen=True) # hashable +class PyosysType: + """ + Bit of a hacky object all-around: this is more or less used to encapsulate + container types so they can be later made opaque using pybind. + """ + base: str + specialization: Tuple["PyosysType", ...] + const: bool = False + + @classmethod + def from_type(Self, type_obj, drop_const=False) -> "PyosysType": + const = type_obj.const and not drop_const + if isinstance(type_obj, Pointer): + ptr_to = Self.from_type(type_obj.ptr_to) + return Self("ptr", (ptr_to,), const) + elif isinstance(type_obj, Reference): + ref_to = Self.from_type(type_obj.ref_to) + return Self("ref", (ref_to,), const) + assert isinstance( + type_obj, Type + ), f"unexpected c++ type object of type {type(type_obj)}" + last_segment = type_obj.typename.segments[-1] + base = last_segment.name + specialization = tuple() + if ( + hasattr(last_segment, "specialization") + and last_segment.specialization is not None + ): + for template_arg in last_segment.specialization.args: + specialization = (*specialization, Self.from_type(template_arg.arg)) + return Self(base, specialization, const) + + def generate_identifier(self): + title = self.base.title() + if len(self.specialization) == 0: + return title + + if title == "Dict": + key, value = self.specialization + return f"{key.generate_identifier()}To{value.generate_identifier()}{title}" + + return ( + "".join(spec.generate_identifier() for spec in self.specialization) + title + ) + + def generate_cpp_name(self): + const_prefix = "const " * self.const + if len(self.specialization) == 0: + return const_prefix + self.base + elif self.base == "ptr": + return const_prefix + f"{self.specialization[0].generate_cpp_name()} *" + elif self.base == "ref": + return const_prefix + f"{self.specialization[0].generate_cpp_name()} &" + else: + return ( + const_prefix + + f"{self.base}<{', '.join(spec.generate_cpp_name() for spec in self.specialization)}>" + ) + + +class PyosysWrapperGenerator(object): + def __init__( + self, + from_headers: Iterable[PyosysHeader], + wrapper_stream: io.TextIOWrapper, + header_stream: io.TextIOWrapper, + ): + self.headers = from_headers + self.f = wrapper_stream + self.f_inc = header_stream + self.found_containers: Dict[PyosysType, Any] = {} + self.class_registry: Dict[str, ClassScope] = {} + + # entry point + def generate(self): + tpl = __file_dir__ / "wrappers_tpl.cc" + preprocessor_opts = self.make_preprocessor_options() + with open(tpl, encoding="utf8") as f: + do_line_directive = True + for i, line in enumerate(f): + if do_line_directive: + self.f.write(f'#line {i + 1} "{tpl}"\n') + do_line_directive = False + if "" in line: + for header in self.headers: + self.f.write(f'#include "{header.name}"\n') + do_line_directive = True + elif "" in line: + for header in self.headers: + header_path = __yosys_root__ / header.name + parsed = parse_file(header_path, options=preprocessor_opts) + global_namespace = parsed.namespace + self.process_namespace(header, global_namespace) + else: + self.f.write(line) + + for container, _ in self.found_containers.items(): + identifier = container.generate_identifier() + print( + f"using {identifier} = {container.generate_cpp_name()};", + file=self.f_inc, + ) + print(f"PYBIND11_MAKE_OPAQUE({identifier})", file=self.f_inc) + + print( + f"static void bind_autogenerated_opaque_containers(py::module &m) {{", + file=self.f_inc, + ) + for container, _ in self.found_containers.items(): + identifier = container.generate_identifier() + cxx = container.generate_cpp_name() + tpl_args = [cxx] + for spec in container.specialization: + tpl_args.append(spec.generate_cpp_name()) + print( + f'\tpy::hashlib::bind_{container.base}<{", ".join(tpl_args)}>(m, "{container.generate_identifier()}");', + file=self.f_inc, + ) + print(f"}}", file=self.f_inc) + + # helpers + def make_preprocessor_options(self): + py_include = get_paths()["include"] + preprocessor_bin = shutil.which("clang++") or "g++" + return ParserOptions( + preprocessor=make_gcc_preprocessor( + defines=["_YOSYS_", "WITH_PYTHON"], + gcc_args=[preprocessor_bin, "-fsyntax-only"], + include_paths=[str(__yosys_root__), py_include, pybind11.get_include()], + ), + ) + + @staticmethod + def find_containers( + containers: Iterable[str], type_info: Any + ) -> Dict[PyosysType, Any]: + if isinstance(type_info, Pointer): + return PyosysWrapperGenerator.find_containers(containers, type_info.ptr_to) + if isinstance(type_info, MoveReference): + return PyosysWrapperGenerator.find_containers( + containers, type_info.moveref_to + ) + if isinstance(type_info, Reference): + return PyosysWrapperGenerator.find_containers(containers, type_info.ref_to) + if not isinstance(type_info, Type): + return () + segments = type_info.typename.segments + containers_found = {} + for segment in segments: + if isinstance(segment, FundamentalSpecifier): + continue + if segment.name in containers: + containers_found.update( + {PyosysType.from_type(type_info, drop_const=True): type_info} + ) + if segment.specialization is not None: + for arg in segment.specialization.args: + sub_containers = PyosysWrapperGenerator.find_containers( + containers, arg.arg + ) + containers_found.update(sub_containers) + return containers_found + + @staticmethod + def find_anonymous_union(cls: ClassScope): + if cls.class_decl.typename.classkey != "union": + return None + for s in cls.class_decl.typename.segments: + if isinstance(s, AnonymousName): + return s + return None # named union + + @staticmethod + def get_parameter_types(function: Function) -> str: + return ", ".join(p.type.format() for p in function.parameters) + + def register_containers(self, target: Union[Function, Field, Variable]): + supported = ("dict", "idict", "pool", "set", "vector") + if isinstance(target, Function): + self.found_containers.update( + self.find_containers(supported, target.return_type) + ) + + for parameter in target.parameters: + self.found_containers.update( + self.find_containers(supported, parameter.type) + ) + else: + self.found_containers.update(self.find_containers(supported, target.type)) + + # processors + def get_overload_cast(self, function: Function, class_basename: Optional[str]) -> str: + is_method = isinstance(function, Method) + function_return_type = function.return_type.format() + if class_basename == "Const" and function_return_type in {"iterator", "const_iterator"}: + # HACK: qualify Const's iterators + function_return_type = f"{class_basename}::{function_return_type}" + + pointer_kind = f"{class_basename}::*" if (is_method and not function.static) else "*" + + retval = f"static_cast <" + retval += function_return_type + retval += f"({pointer_kind})" + retval += f"({self.get_parameter_types(function)})" + if is_method and function.const: + retval += " const" + retval += ">" + retval += "(&" + if is_method: + retval += f"{class_basename}::" + retval += function.name.segments[-1].format() + retval += ")" + return retval + + def get_definition_args(self, function: Function, class_basename: Optional[str], python_name_override: Optional[str] = None) -> List[str]: + function_basename = function.name.segments[-1].format() + + python_function_basename = python_name_override or keyword_aliases.get(function_basename, function_basename) + + def_args = [f'"{python_function_basename}"'] + def_args.append(self.get_overload_cast(function, class_basename)) + for parameter in function.parameters: + # ASSUMPTION: there are no unnamed parameters in the yosys codebase + parameter_arg = f'py::arg("{parameter.name}")' + if parameter.default is not None: + parameter_arg += f" = {parameter.default.format()}" + def_args.append(parameter_arg) + + return def_args + + def process_method(self, function: Method, class_basename: str): + if ( + function.deleted + or function.template + or function.vararg + or function.access != "public" + or function.pure_virtual + or function.destructor + ): + return + + if any(isinstance(p.type, MoveReference) for p in function.parameters): + # skip move constructors + return + + if len(function.name.segments) > 1: + # can't handle, skip + return + + if function.constructor: + print( + f"\t\t\t.def(py::init<{self.get_parameter_types(function)}>())", + file=self.f, + ) + return + + python_name_override = None + if function.operator is not None: + if function.operator == "==": + python_name_override = "__eq__" + elif function.operator == "!=": + python_name_override = "__ne__" + elif function.operator == "<": + python_name_override = "__lt__" + else: + return + + self.register_containers(function) + + definition_fn = "def" + if function.static: + definition_fn = "def_static" + + print(f"\t\t\t.{definition_fn}({", ".join(self.get_definition_args(function, class_basename, python_name_override))})", file=self.f) + + def process_function(self, function: Function): + if ( + function.deleted + or function.template + or function.vararg + or function.static + ): + return + + if function.operator is not None: + # Python doesn't do global operators + return + + if function.name.segments[-1].format() in global_denylist: + return + + self.register_containers(function) + + print(f"\t\t\tm.def({", ".join(self.get_definition_args(function, None))});", file=self.f) + + def process_field(self, field: Field, class_basename: str): + if field.access != "public": + return + + if field.name is None: + # anonymous structs and unions + # unions handled in calling function + # ASSUMPTION: No anonymous structs in the yosys codebase + # (they're not in the C++ standard anyway) + return + + unique_ptrs = self.find_containers(("unique_ptr",), field.type) + if len(unique_ptrs): + # TODO: figure out how to bridge unique pointers + return + + self.register_containers(field) + + definition_fn = f"def_{'readonly' if field.type.const else 'readwrite'}" + if field.static: + definition_fn += "_static" + + field_python_basename = keyword_aliases.get(field.name, field.name) + + print( + f'\t\t\t.{definition_fn}("{field_python_basename}", &{class_basename}::{field.name})', + file=self.f, + ) + + def process_variable(self, variable: Variable): + if isinstance(variable.type, Array): + return + + variable_basename = variable.name.segments[-1].format() + if variable_basename in global_denylist: + return + + self.register_containers(variable) + + definition_fn = f"def_{'readonly' if variable.type.const else 'readwrite'}_static" + + variable_python_basename = keyword_aliases.get(variable_basename, variable_basename) + variable_name = variable.name.format() + + print( + f'\t\t\tglobal_variables.{definition_fn}("{variable_python_basename}", &{variable_name});', + file=self.f, + ) + + def process_class_members( + self, + metadata: PyosysClass, + cls: ClassScope, + basename: str + ): + for method in cls.methods: + if method.name.segments[-1].name in metadata.denylist: + continue + self.process_method(method, basename) + + visited_anonymous_unions = set() + for field in cls.fields: + if field.name in metadata.denylist: + continue + self.process_field(field, basename) + + # Handle anonymous unions + for subclass in cls.classes: + if subclass.class_decl.access != "public": + continue + if au := self.find_anonymous_union(subclass): + if au.id in visited_anonymous_unions: + continue + visited_anonymous_unions.add(au.id) + for subfield in subclass.fields: + self.process_field(subfield, basename) + + def process_class( + self, + metadata: PyosysClass, + cls: ClassScope, + namespace_components: Tuple[str, ...], + ): + pqname: PQName = cls.class_decl.typename + full_path = list(namespace_components) + [ + segment.format() for segment in pqname.segments + ] + basename = full_path.pop() + self.class_registry[basename] = cls + + declaration_namespace = "::".join(full_path) + tpl_args = [basename] + if metadata.ref_only: + tpl_args.append(f"std::unique_ptr<{basename}, py::nodelete>") + print( + f'\t\t{{using namespace {declaration_namespace}; py::class_<{", ".join(tpl_args)}>(m, "{basename}")', + file=self.f, + ) + + self.process_class_members(metadata, cls, basename) + for base in cls.class_decl.bases: + if base.access != "public": + continue + name = base.typename.segments[-1].format() + if base_scope := self.class_registry.get(name): + self.process_class_members(metadata, base_scope, basename) + + if expr := metadata.string_expr: + print(f'\t\t.def("__str__", [](const {basename} &s) {{ return {expr}; }})', file=self.f) + + if expr := metadata.hash_expr: + print(f'\t\t.def("__hash__", [](const {basename} &s) {{ return run_hash({expr}); }})', file=self.f) + + print(f"\t\t;}}", file=self.f) + + def process_enum( + self, + enum: EnumDecl, + namespace_components: Tuple[str, ...], + ): + pqname: PQName = enum.typename + full_path = list(namespace_components) + [ + segment.format() for segment in pqname.segments + ] + basename = full_path.pop() + + declaration_namespace = "::".join(full_path) + print( + f'\t\t{{using namespace {declaration_namespace}; py::native_enum<{basename}>(m, "{basename}", "enum.Enum")', + file=self.f, + ) + enum_class = enum.typename.classkey == "enum class" + for value in enum.values: + enum_class_qualifier = f"{basename}::" * enum_class + print(f'\t\t\t.value("{value.name}", {enum_class_qualifier}{value.name})', file=self.f) + print(f"\t\t\t.finalize();}}", file=self.f) + + + def process_namespace( + self, + header: PyosysHeader, + ns: NamespaceScope, + namespace_components: Tuple[str, ...] = (), + ): + for name, subns in ns.namespaces.items(): + self.process_namespace(header, subns, (*namespace_components, name)) + if len(namespace_components) and (len(ns.functions) + len(ns.variables)): + # TODO: Not essential but maybe move namespace usage into + # process_function for consistency? + print(f"\t\t{{ using namespace {'::'.join(namespace_components)};", file=self.f) + for function in ns.functions: + self.process_function(function) + for variable in ns.variables: + self.process_variable(variable) + print(f"\t\t}}", file=self.f) + for enum in ns.enums: + self.process_enum(enum, namespace_components) + for cls in ns.classes: + pqname = cls.class_decl.typename + declared_name_str = pqname.segments[-1].format() + if cls_metadata := header.classes_by_name.get(declared_name_str): + self.process_class(cls_metadata, cls, namespace_components) + -# Restrict certain strings from being function names in Python keyword_aliases = { - "in": "in_", - "False": "False_", - "None": "None_", - "True": "True_", - "and": "and_", - "as": "as_", - "assert": "assert_", - "break": "break_", - "class": "class_", - "continue": "continue_", - "def": "def_", - "del": "del_", - "elif": "elif_", - "else": "else_", - "except": "except_", - "for": "for_", - "from": "from_", - "global": "global_", - "if": "if_", - "import": "import_", - "in": "in_", - "is": "is_", - "lambda": "lambda_", - "nonlocal": "nonlocal_", - "not": "not_", - "or": "or_", - "pass": "pass_", - "raise": "raise_", - "return": "return_", - "try": "try_", - "while": "while_", - "with": "with_", - "yield": "yield_", + "in": "in_", + "False": "False_", + "None": "None_", + "True": "True_", + "and": "and_", + "as": "as_", + "assert": "assert_", + "break": "break_", + "class": "class_", + "continue": "continue_", + "def": "def_", + "del": "del_", + "elif": "elif_", + "else": "else_", + "except": "except_", + "for": "for_", + "from": "from_", + "global": "global_", + "if": "if_", + "import": "import_", + "in": "in_", + "is": "is_", + "lambda": "lambda_", + "nonlocal": "nonlocal_", + "not": "not_", + "or": "or_", + "pass": "pass_", + "raise": "raise_", + "return": "return_", + "try": "try_", + "while": "while_", + "with": "with_", + "yield": "yield_", } -# These can be used without any explicit conversion -autocast_types = { - "void": "void", - "bool": "bool", - "int": "int", - "double": "double", - "size_t": "size_t", - "std::string": "std::string", - "string": "string", - "char_p": "char_p", - "std::source_location": "std::source_location", - "source_location": "source_location", - "State": "RTLIL::State", - # trampoline types - "Pass": "RTLIL::Pass", - "Monitor": "RTLIL::Monitor", -} - -def strip_std_prefix(str_in): - # removesuffix is python 3.9+ - std_namespace = "std::" - if str_in.startswith(std_namespace): - return str_in[len(std_namespace):] - return str_in - - -# Ways to link between Python- and C++ Objects -class link_types(Enum): - global_list = 1 # Identical to pointer, kept for historical reasons - ref_copy = 2 # Copy - pointer = 3 # The Python Object contains a pointer to its C++ - # counterpart - derive = 4 # Identical to ref_copy, kept for historical reasons - - -class attr_types(Enum): - star = "*" - amp = "&" - ampamp = "&&" - default = "" - - -# For source-files -class Source: - name = "" - classes = [] - - def __init__(self, name, classes): - self.name = name - self.classes = classes - - -# Splits a list by the given delimiter, without splitting strings inside -# pointy-brackets (< and >) -def split_list(str_def, delim): - str_def = str_def.strip() - if len(str_def) == 0: - return [] - if str_def.count(delim) == 0: - return [str_def] - if str_def.count("<") == 0: - return str_def.split(delim) - if str_def.find("<") < str_def.find(" "): - closing = find_closing( - str_def[str_def.find("<") + 1 :], "<", ">" - ) + str_def.find("<") - comma = str_def[closing:].find(delim) - if comma == -1: - return [str_def] - comma = closing + comma - else: - comma = str_def.find(delim) - rest = split_list(str_def[comma + 1 :], delim) - ret = [str_def[:comma]] - if rest != None and len(rest) != 0: - ret.extend(rest) - return ret - - -# Represents a Type -class WType: - name = "" - cont = None - attr_type = attr_types.default - - def __init__(self, name="", cont=None, attr_type=attr_types.default): - self.name = name - self.cont = cont - self.attr_type = attr_type - - # Python type-string - def gen_text(self): - text = self.name - if self.name in enum_names: - text = enum_by_name(self.name).namespace + "::" + self.name - if self.cont != None: - return self.cont.gen_identifier() - return text - - # C++ type-string - def gen_text_cpp(self): - postfix = "" - if self.attr_type == attr_types.star: - postfix = " *" - elif self.attr_type == attr_types.amp: - postfix = " &" - elif self.attr_type == attr_types.ampamp: - postfix = " &&" - if self.name in classnames: - return class_by_name(self.name).namespace + "::" + self.name + postfix - if self.name in enum_names: - return enum_by_name(self.name).namespace + "::" + self.name + postfix - if self.name in autocast_types: - return autocast_types[self.name] + postfix - text = self.name - if self.cont != None: - text += "<" - for a in self.cont.args: - text += a.gen_text_cpp() + ", " - text = text[:-2] - text += ">" - return text - - @staticmethod - def from_string(str_def, containing_file, line_number): - str_def = str_def.strip() - if len(str_def) == 0: - return None - str_def = str_def.replace( - "RTLIL::SigSig", "std::pair" - ).replace("SigSig", "std::pair") - t = WType() - t.name = "" - t.cont = None - t.attr_type = attr_types.default - if str_def.find("<") != -1: # and str_def.find("<") < str_def.find(" "): - str_def = str_def.replace("const ", "") - - candidate = WContainer.from_string(str_def, containing_file, line_number) - if candidate == None: - return None - t.name = str_def[: str_def.find("<")] - - if t.name.count("*") + t.name.count("&") > 1: - return None - - if t.name.count("*") == 1 or str_def[0] == "*" or str_def[-1] == "*": - t.attr_type = attr_types.star - t.name = t.name.replace("*", "") - elif t.name.count("&&") == 1: - t.attr_type = attr_types.ampamp - t.name = t.name.replace("&&", "") - elif t.name.count("&") == 1 or str_def[0] == "&" or str_def[-1] == "&": - t.attr_type = attr_types.amp - t.name = t.name.replace("&", "") - - t.cont = candidate - if t.name not in known_containers: - return None - return t - - prefix = "" - - if str.startswith(str_def, "const "): - if "char_p" in str_def: - prefix = "const " - str_def = str_def[6:] - if str.startswith(str_def, "unsigned "): - prefix = "unsigned " + prefix - str_def = str_def[9:] - while str.startswith(str_def, "long "): - prefix = "long " + prefix - str_def = str_def[5:] - while str.startswith(str_def, "short "): - prefix = "short " + prefix - str_def = str_def[6:] - - str_def = str_def.split("::")[-1] - - if str_def.count("*") + str_def.count("&") >= 2: - return None - - if str_def.count("*") == 1: - t.attr_type = attr_types.star - str_def = str_def.replace("*", "") - elif str_def.count("&&") == 1: - t.attr_type = attr_types.ampamp - str_def = str_def.replace("&&", "") - elif str_def.count("&") == 1: - t.attr_type = attr_types.amp - str_def = str_def.replace("&", "") - - if ( - len(str_def) > 0 - and str_def.split("::")[-1] not in autocast_types - and str_def.split("::")[-1] not in classnames - and str_def.split("::")[-1] not in enum_names - ): - return None - - if str_def.count(" ") == 0: - t.name = (prefix + str_def).replace("char_p", "char *") - t.cont = None - return t - return None - - def gen_identifier(self): - if self.cont: - return self.cont.gen_identifier() - return self.name.title() - - def as_wclass(self) -> Optional["WClass"]: - return class_by_name(self.name) - - def __repr__(self): - return f"{self.__class__.__qualname__}(**{repr(self.__dict__)})" - - -# Associate the "Translators" with their c++ type -known_containers = { - "std::set", - "std::vector", - "std::map", - "std::pair", - "pool", - "idict", - "dict", - "RTLIL::ObjRange" -} - -# Represents a container-type -class WContainer: - name = "" - args = [] - - def from_string(str_def, containing_file, line_number): - if str_def == None or len(str_def) < 4: - return None - cont = WContainer() - cont.name = str_def[: str_def.find("<")] - str_def = str_def[str_def.find("<") + 1 : find_closing(str_def, "<", ">")] - cont.args = [] - for arg in split_list(str_def, ","): - candidate = WType.from_string(arg.strip(), containing_file, line_number) - if candidate == None: - return None - if candidate.name == "void": - return None - cont.args.append(candidate) - return cont - - # generate the c++ type string - def gen_type_cpp(self): - tpl_args = [] - for arg in self.args: - postfix = (arg.attr_type == attr_types.star) * " *" - if arg.name in autocast_types: - tpl_args.append(autocast_types[arg.name] + postfix) - elif arg.name in known_containers: - tpl_args.append(arg.cont.gen_type_cpp()) - else: - tpl_args.append(arg.as_wclass().fully_qualified_name() + postfix) - return f'{self.name}<{ ", ".join(tpl_args) }>' - - def gen_identifier(self): - container = strip_std_prefix(self.name).title() - - if container == "Dict": - assert len(self.args) == 2 - return f"{self.args[0].gen_identifier()}To{self.args[1].gen_identifier()}{container}" - - args = [] - for arg in self.args: - arg_name = arg.name.title() - if arg.cont: - arg_name = arg.cont.gen_identifier() - args.append(arg_name) - args.append(container) - result = "".join(args) - if result == "SigspecSigspecPair": - return "SigSig" - return result - - @autostring - def gen_boost_py(self, *, stream): - bind_fn = "py::bind_" + strip_std_prefix(self.name) - tpl_args = [self.gen_type_cpp()] - if bind_fn != "py::bind_vector": - # using custom bind function, can't use ::value so need more - # template arguments - for arg in self.args: - postfix = (arg.attr_type == attr_types.star) * " *" - if arg.name in autocast_types: - tpl_args.append(autocast_types[arg.name] + postfix) - elif arg.name in known_containers: - tpl_args.append(arg.cont.gen_type_cpp()) - else: - tpl_args.append(arg.as_wclass().fully_qualified_name() + postfix) - if bind_fn == "py::bind_set": - bind_fn = "py::bind_pool" - stream.write(f'\t\t{bind_fn}<{",".join(tpl_args)}>(m, "{self.gen_identifier()}");\n') - - def __repr__(self): - return f"{self.__class__.__qualname__}(**{repr(self.__dict__)})" - - -class Attribute: - wtype = None - varname = None - is_const = False - default_value = None - pos = None - pos_counter = 0 - - def __init__(self, wtype, varname, is_const=False, default_value=None): - self.wtype = wtype - self.varname = varname - self.is_const = is_const - self.default_value = None - self.container = None - - @staticmethod - def from_string(str_def, containing_file, line_number): - if len(str_def) < 3: - return None - orig = str_def - arg = Attribute(None, None) - prefix = "" - arg.wtype = None - arg.varname = None - arg.is_const = False - arg.default_value = None - arg.container = None - if str.startswith(str_def, "const "): - arg.is_const = True - str_def = str_def[6:] - if str.startswith(str_def, "unsigned "): - prefix = "unsigned " - str_def = str_def[9:] - while str.startswith(str_def, "long "): - prefix = "long " + prefix - str_def = str_def[5:] - while str.startswith(str_def, "short "): - prefix = "short " + prefix - str_def = str_def[6:] - - if str_def.find("<") != -1 and str_def.find("<") < str_def.find(" "): - closing = ( - find_closing(str_def[str_def.find("<") :], "<", ">") - + str_def.find("<") - + 1 - ) - arg.wtype = WType.from_string( - str_def[:closing].strip(), containing_file, line_number - ) - str_def = str_def[closing + 1 :] - else: - if str_def.count(" ") > 0: - arg.wtype = WType.from_string( - prefix + str_def[: str_def.find(" ")].strip(), - containing_file, - line_number, - ) - str_def = str_def[str_def.find(" ") + 1 :] - else: - arg.wtype = WType.from_string( - prefix + str_def.strip(), containing_file, line_number - ) - str_def = "" - arg.varname = "" - - if arg.wtype == None: - return None - if str_def.count("=") == 0: - arg.varname = str_def.strip() - if arg.varname.find(" ") > 0: - return None - else: - arg.varname = str_def[: str_def.find("=")].strip() - if arg.varname.find(" ") > 0: - return None - str_def = str_def[str_def.find("=") + 1 :].strip() - arg.default_value = str_def[arg.varname.find("=") + 1 :].strip() - if len(arg.varname) == 0: - arg.varname = None - return arg - if arg.varname[0] == "*": - arg.wtype.attr_type = attr_types.star - arg.varname = arg.varname[1:] - elif arg.varname[0] == "&": - if arg.wtype.attr_type != attr_types.default: - return None - if arg.varname[1] == "&": - arg.wtype.attr_type = attr_types.ampamp - arg.varname = arg.varname[2:] - else: - arg.wtype.attr_type = attr_types.amp - arg.varname = arg.varname[1:] - return arg - - # Generates the varname. If the attribute has no name in the header file, - # a name is generated - def gen_varname(self): - if self.varname != None: - return self.varname - if self.wtype.name == "void": - return "" - if self.pos == None: - self.pos = Attribute.pos_counter - Attribute.pos_counter = Attribute.pos_counter + 1 - return "gen_varname_" + str(self.pos) - - # Generates the test for the function headers with c++ types - def gen_listitem_cpp(self, include_varname=True): - postfix = self.gen_varname() * include_varname - prefix = "" - if self.is_const: - prefix = "const " - infix = "" - if self.wtype.attr_type == attr_types.star: - infix = "*" - elif self.wtype.attr_type == attr_types.amp: - infix = "&" - elif self.wtype.attr_type == attr_types.ampamp: - infix = "&&" - if self.wtype.name in known_containers: - return ( - prefix - + self.wtype.cont.gen_type_cpp() - + " " - + infix - + postfix - ) - if self.wtype.name in classnames: - return ( - prefix - + class_by_name(self.wtype.name).namespace - + "::" - + self.wtype.name - + " " - + infix - + postfix - ) - return prefix + self.wtype.name + " " + infix + postfix - - def gen_listitem_pyarg(self): - default = "" - if self.default_value is not None: - default = f" = {self.default_value}" - return f'py::arg("{self.varname}"){default}' - - # Generates the listitem withtout the varname, so the signature can be - # compared - def gen_listitem_hash(self): - prefix = "" - if self.is_const: - prefix = "const " - if self.wtype.name in classnames: - return prefix + self.wtype.name + "* " - if self.wtype.name in known_containers: - return self.wtype.cont.gen_identifier() - return prefix + self.wtype.name - - -class WClass: - name = None - namespace = None - link_type = None - base_class = None - id_ = None - string_id = None - hash_id = None - needs_clone = False - found_funs = [] - found_vars = [] - found_constrs = [] - - def __init__( - self, - name, - link_type, - *, - id_=None, - string_id=None, - hash_id=None, - needs_clone=False, - ): - self.name = name - self.namespace = None - self.base_class = None - self.link_type = link_type - self.id_ = id_ - self.string_id = string_id - self.hash_id = hash_id - self.needs_clone = needs_clone - self.found_funs = [] - self.found_vars = [] - self.found_constrs = [] - - @autostring - def gen_boost_py(self, *, stream): - name_qualified = f"{self.namespace}::{self.name}" - tpl_args = [name_qualified] - if self.link_type in [link_types.pointer, link_types.global_list]: - tpl_args.append(f"std::unique_ptr<{name_qualified}, py::nodelete>") - stream.write(f'\t\tpy::class_<{", ".join(tpl_args)}>(m, "{self.name}")\n') - for con in self.found_constrs: - # HACK: skip move constructors - if "&&" in con.orig_text: - continue - con.gen_boost_py(stream=stream) - for fun in sorted(self.found_funs, key=lambda f: (f.is_operator, f.name)): - fun.gen_boost_py(stream=stream) - if self.string_id: - stream.write( - f'\t\t\t.def("__str__", [](const {name_qualified} &self){{ return self.{self.string_id}; }})\n' - ) - if self.hash_id: - hash_member = f".{self.hash_id}" if self.hash_id != "" else "" - stream.write( - f'\t\t\t.def("__hash__", [](const {name_qualified} &self){{ return run_hash(self{hash_member}); }})\n' - ) - for var in self.found_vars: - var.gen_boost_py(stream=stream) - stream.write("\t\t;\n") - - def fully_qualified_name(self) -> str: - return f"{self.namespace}::{self.name}" - - def __repr__(self): - return f"{self.__class__.__qualname__}({repr(self.__dict__)})" - - -# CONFIGURE HEADER-FILES TO BE PARSED AND CLASSES EXPECTED IN THEM HERE - -sources = [ - Source( - "kernel/celltypes", - [ - WClass("CellType", link_types.ref_copy, hash_id="type", needs_clone=True), - WClass("CellTypes", link_types.ref_copy, needs_clone=True), - ], - ), - Source( - "kernel/consteval", [WClass("ConstEval", link_types.ref_copy, needs_clone=True)] - ), - Source("kernel/log", []), - Source( - "kernel/register", - [ - # WClass("Pass", link_types.derive, needs_clone=True), # Manual mapping because of virtual method - ], - ), - Source( - "kernel/rtlil", - [ - WClass("IdString", link_types.ref_copy, string_id="str()", hash_id="str()"), - WClass("Const", link_types.ref_copy, string_id="as_string()"), - WClass("AttrObject", link_types.ref_copy), - WClass("NamedObject", link_types.ref_copy), - WClass("Selection", link_types.ref_copy), - #WClass("Monitor", link_types.derive), # Moved to tpl for virtual methods - WClass("CaseRule", link_types.ref_copy, needs_clone=True), - WClass("SwitchRule", link_types.ref_copy, needs_clone=True), - WClass("SyncRule", link_types.ref_copy, needs_clone=True), - WClass( - "Process", link_types.pointer, string_id="name.c_str()", hash_id="name" - ), - WClass("SigChunk", link_types.ref_copy), - WClass("SigBit", link_types.ref_copy, hash_id=""), - WClass("SigSpec", link_types.ref_copy, hash_id=""), - WClass( - "Cell", - link_types.global_list, - id_=Attribute(WType("unsigned int"), "hashidx_"), - string_id="name.c_str()", - hash_id="", - ), - WClass( - "Wire", - link_types.global_list, - id_=Attribute(WType("unsigned int"), "hashidx_"), - string_id="name.c_str()", - hash_id="", - ), - WClass( - "Memory", - link_types.global_list, - id_=Attribute(WType("unsigned int"), "hashidx_"), - string_id="name.c_str()", - hash_id="", - ), - WClass( - "Module", - link_types.global_list, - id_=Attribute(WType("unsigned int"), "hashidx_"), - string_id="name.c_str()", - hash_id="", - ), - WClass( - "Design", - link_types.ref_copy, - id_=Attribute(WType("unsigned int"), "hashidx_"), - string_id="hashidx_", - hash_id="", - ), - ], - ), - # Source("kernel/satgen",[ - # ] - # ), - # Source("libs/ezsat/ezsat",[ - # ] - # ), - # Source("libs/ezsat/ezminisat",[ - # ] - # ), - Source( - "kernel/sigtools", [WClass("SigMap", link_types.ref_copy, needs_clone=True)] - ), - Source("kernel/yosys", []), - Source("kernel/cost", []), -] - -blacklist_methods = [ - "YOSYS_NAMESPACE::Pass::run_register", - "YOSYS_NAMESPACE::Module::Pow", - "YOSYS_NAMESPACE::RTLIL::Design::selected_whole_modules", - "YOSYS_NAMESPACE::RTLIL::AttrObject::get_blackbox_attribute" -] - -enum_names = ["State", "SyncType", "ConstFlags"] - -enums = [] # Do not edit -glbls = [] - -unowned_functions = [] - -classnames = [] -for source in sources: - for wclass in source.classes: - classnames.append(wclass.name) - - -def class_by_name(name): - for source in sources: - for wclass in source.classes: - if wclass.name == name: - return wclass - return None - - -def enum_by_name(name): - for e in enums: - if e.name == name: - return e - return None - - -def find_closing(text, open_tok, close_tok): - if text.find(open_tok) == -1 or text.find(close_tok) <= text.find(open_tok): - return text.find(close_tok) - return ( - text.find(close_tok) - + find_closing(text[text.find(close_tok) + 1 :], open_tok, close_tok) - + 1 - ) - - -def unpretty_string(s): - s = s.strip() - while s.find(" ") != -1: - s = s.replace(" ", " ") - while s.find("\t") != -1: - s = s.replace("\t", " ") - s = s.replace(" (", "(") - return s - - -class WEnum: - name = None - namespace = None - values = [] - - def from_string(str_def, namespace, line_number): - str_def = str_def.strip() - if not str.startswith(str_def, "enum "): - return None - if str_def.count(";") != 1: - return None - str_def = str_def[5:] - enum = WEnum() - split = str_def.split(":") - if len(split) != 2: - return None - enum.name = split[0].strip() - if enum.name not in enum_names: - return None - str_def = split[1] - if str_def.count("{") != str_def.count("}") != 1: - return None - if ( - len(str_def) < str_def.find("}") + 2 - or str_def[str_def.find("}") + 1] != ";" - ): - return None - str_def = str_def.split("{")[-1].split("}")[0] - enum.values = [] - for val in str_def.split(","): - enum.values.append(val.strip().split("=")[0].strip()) - enum.namespace = namespace - return enum - - @autostring - def gen_boost_py(self, *, stream): - stream.write( - f'\t\tpy::native_enum<{self.namespace}::{self.name}>(m, "{self.name}", "enum.Enum")\n' - ) - for value in self.values: - stream.write(f'\t\t\t.value("{value}", {self.namespace}::{value})\n') - stream.write("\t\t\t.finalize();\n") - - def __str__(self): - ret = "Enum " + self.namespace + "::" + self.name + "(\n" - for val in self.values: - ret = ret + "\t" + val + "\n" - return ret + ")" - - -class WConstructor: - orig_text = None - args = [] - containing_file = None - member_of = None - duplicate = False - protected = False - - def __init__(self, containing_file, class_): - self.orig_text = "Auto generated default constructor" - self.args = [] - self.containing_file = containing_file - self.member_of = class_ - self.protected = False - - def from_string(str_def, containing_file, class_, line_number, protected=False): - if class_ == None: - return None - if str_def.count("delete;") > 0: - return None - con = WConstructor(containing_file, class_) - con.orig_text = str_def - con.args = [] - con.duplicate = False - con.protected = protected - if str.startswith(str_def, "inline "): - str_def = str_def[7:] - if not str.startswith(str_def, class_.name + "("): - return None - str_def = str_def[len(class_.name) + 1 :] - found = find_closing(str_def, "(", ")") - if found == -1: - return None - str_def = str_def[0:found].strip() - if len(str_def) == 0: - return con - args = split_list(str_def, ",") - for i, arg in enumerate(args): - parsed = Attribute.from_string(arg.strip(), containing_file, line_number) - if parsed == None: - return None - # Only allow std::source_location as defaulted last argument, and - # don't append so it takes default value - if parsed.wtype.name in ["std::source_location", "source_location"]: - if parsed.default_value is None or i != len(args) - 1: - debug( - "std::source_location not defaulted last arg of " - + class_.name - + " is unsupported", - 2, - ) - return None - continue - con.args.append(parsed) - return con - - def gen_decl_hash_py(self): - text = self.member_of.name + "(" - for arg in self.args: - text += arg.gen_listitem_hash() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ");" - return text - - def overload_params(self): - return ", ".join([a.gen_listitem_cpp(include_varname=False) for a in self.args]) - - @autostring - def gen_boost_py(self, *, stream): - if self.duplicate or self.protected: - return - stream.write(f"\t\t\t.def(py::init<{self.overload_params()}>())\n") - - -class WFunction: - orig_text = None - is_static = False - is_inline = False - is_virtual = False - is_const = False - ret_attr_type = attr_types.default - is_operator = False - ret_type = None - name = None - alias = None - args = [] - containing_file = None - member_of = None - duplicate = False - namespace = "" - - def from_string(str_def, containing_file, class_, line_number, namespace): - if str_def.count("delete;") > 0: - return None - func = WFunction() - func.is_static = False - func.is_inline = False - func.is_virtual = False - func.is_const = False - func.ret_attr_type = attr_types.default - func.is_operator = False - func.member_of = None - func.orig_text = str_def - func.args = [] - func.containing_file = containing_file - func.member_of = class_ - func.duplicate = False - func.namespace = namespace - str_def = str_def.replace("operator ", "operator") - - # remove attributes from the start - if str.startswith(str_def, "[[") and "]]" in str_def: - str_def = str_def[str_def.find("]]") + 2 :] - - if str.startswith(str_def, "static "): - func.is_static = True - str_def = str_def[7:] - else: - func.is_static = False - if str.startswith(str_def, "inline "): - func.is_inline = True - str_def = str_def[7:] - else: - func.is_inline = False - if str.startswith(str_def, "virtual "): - func.is_virtual = True - str_def = str_def[8:] - else: - func.is_virtual = False - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short", "const"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - func.ret_type = WType.from_string( - prefix + parts[0], containing_file, line_number - ) - - if func.ret_type == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - found = str_def.find("(") - if found == -1 or (str_def.find(" ") != -1 and found > str_def.find(" ")): - return None - func.name = str_def[:found] - str_def = str_def[found:] - if func.name.find("operator") != -1 and str.startswith(str_def, "()("): - func.name += "()" - str_def = str_def[2:] - str_def = str_def[1:] - if func.name.find("operator") != -1: - func.is_operator = True - if func.name.find("*") == 0: - func.name = func.name.replace("*", "") - func.ret_type.attr_type = attr_types.star - if func.name.find("&&") == 0: - func.name = func.name.replace("&&", "") - func.ret_type.attr_type = attr_types.ampamp - if func.name.find("&") == 0: - func.name = func.name.replace("&", "") - func.ret_type.attr_type = attr_types.amp - - found = find_closing(str_def, "(", ")") - if found == -1: - return None - - post_qualifiers = str_def[found + 1 :].lstrip() - if post_qualifiers.startswith("const"): - func.is_const = True - - str_def = str_def[0:found] - if func.name in blacklist_methods: - return None - if func.namespace != None and func.namespace != "": - if (func.namespace + "::" + func.name) in blacklist_methods: - return None - if func.member_of != None: - if ( - func.namespace + "::" + func.member_of.name + "::" + func.name - ) in blacklist_methods: - return None - if ( - func.is_operator - and func.name.replace(" ", "").replace("operator", "").split("::")[-1] - not in wrappable_operators - ): - return None - - testname = func.name - if func.is_operator: - testname = testname[: testname.find("operator")] - if ( - testname.count(")") != 0 - or testname.count("(") != 0 - or testname.count("~") != 0 - or testname.count(";") != 0 - or testname.count(">") != 0 - or testname.count("<") != 0 - or testname.count("throw") != 0 - ): - return None - - func.alias = func.name - if func.name in keyword_aliases: - func.alias = keyword_aliases[func.name] - str_def = str_def[:found].strip() - if len(str_def) == 0: - return func - args = split_list(str_def, ",") - for i, arg in enumerate(args): - if arg.strip() == "...": - continue - parsed = Attribute.from_string(arg.strip(), containing_file, line_number) - if parsed == None: - return None - # Only allow std::source_location as defaulted last argument, and - # don't append so it takes default value - if parsed.wtype.name in ["std::source_location", "source_location"]: - if parsed.default_value is None or i != len(args) - 1: - debug( - "std::source_location not defaulted last arg of " - + func.name - + " is unsupported", - 2, - ) - return None - continue - func.args.append(parsed) - # handle (void) parameter declarations - if len(func.args) == 1 and func.args[0].wtype.name == "void": - func.args = [] - return func - - @property - def mangled_name(self): - mangled_typename = ( - lambda code: code.replace("::", "_") - .replace("<", "_") - .replace(">", "_") - .replace(" ", "") - .replace("*", "") - .replace(",", "") - ) - - return self.name + "".join( - f"__{mangled_typename(arg.wtype.gen_text_cpp())}" for arg in self.args - ) - - def gen_alias(self): - self.alias = self.mangled_name - - def gen_post_qualifiers(self, derived=False): - if ( - self.member_of != None - and self.member_of.link_type == link_types.derive - and self.is_virtual - and derived - ): - # we drop the qualifiers when deriving callbacks to be implemented in Python - return "" - return " const" if self.is_const else "" - - def gen_decl_hash_py(self): - text = self.ret_type.gen_text() + " " + self.alias + "(" - for arg in self.args: - text += arg.gen_listitem_hash() + ", " - if len(self.args) > 0: - text = text[:-2] - text += ");" - return text - - def overload_params(self): - return ", ".join([a.gen_listitem_cpp(False) for a in self.args]) - - def py_args(self): - return ", ".join([a.gen_listitem_pyarg() for a in self.args]) - - def wrapper_params(self): - return ", ".join([a.gen_listitem_cpp() for a in self.args]) - - def wrapper_args(self): - varnames = [] - for a in self.args: - if a.varname == "format": - # HACK: handle format strings (by ignoring the format part) - varnames.extend(['"%s"', "format"]) - else: - varnames.append(a.varname) - return ", ".join(varnames) - - @autostring - def gen_boost_py(self, *, stream): - if self.duplicate: - return - - fully_qualify = False - if self.member_of is not None and ( - (self.member_of.name == "IdString" and self.name == "in") - or (self.member_of.name == "Design" and self.name == "selection") - ): - fully_qualify = True - - # HACK: skip methods with inline-related nonsense - if self.alias in [ - "log_id", - "log_dump_val_worker", - "log_dump_args_worker", - "GetSize", - ]: - return - - prefix = "\t\tm" - ns = self.namespace - if self.member_of: - prefix = "\t\t\t" - ns = self.member_of.fully_qualified_name() - - stream.write(f"{prefix}.def") - if self.member_of and self.is_static: - stream.write("_static") - stream.write("(") - - if self.is_operator: - stream.write(f"py::self {self.name[len('operator'):]} py::self") - else: - stream.write(f'"{self.alias}", ') - # HACK: wrap variadics by only allowing a string - if "..." in self.orig_text: - stream.write( - f"[]({self.wrapper_params()}) {{ {self.namespace}::{self.name}({self.wrapper_args()}); }}" - ) - else: - - # HACK: Some of these needs special handling, i.e., a FULL - # overload disambiguation. Not sure why. Something to do with - # inlines and overloads. - # - # In theory, this disambiguation should work for everything but - # WType doesn't properly retain const return types yet. - if fully_qualify: - stream.write( - f"static_cast < {self.ret_type.gen_text_cpp()} ({ns}::*)({self.overload_params()}) {self.gen_post_qualifiers()} >(" - ) - elif not len(self.args) == 0: - stream.write(f"py::overload_cast<{self.overload_params()}>(") - stream.write(f"&{ns}::{self.name}") - if fully_qualify: - stream.write(")") - elif not len(self.args) == 0: - if self.is_const: - stream.write(f", py::const_") - stream.write(")") - py_args = self.py_args() - if len(py_args): - stream.write(", ") - stream.write(py_args) - stream.write(")\n" if self.member_of is not None else ");\n") - - def __repr__(self): - return f"{self.__class__.__qualname__}({repr(self.__dict__)})" - - -class WMember: - opaque_containers: ClassVar[dict] = dict() - orig_text = None - wtype = attr_types.default - name = None - containing_file = None - member_of = None - namespace = "" - is_const = False - - def from_string(str_def, containing_file, class_, line_number, namespace): - member = WMember() - member.orig_text = str_def - member.wtype = None - member.name = "" - member.containing_file = containing_file - member.member_of = class_ - member.namespace = namespace - member.is_const = False - - if str.startswith(str_def, "const "): - member.is_const = True - str_def = str_def[6:] - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - member.wtype = WType.from_string( - prefix + parts[0], containing_file, line_number - ) - - if member.wtype == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - if ( - str_def.find("(") != -1 - or str_def.find(")") != -1 - or str_def.find("{") != -1 - or str_def.find("}") != -1 - ): - return None - - found = str_def.find(";") - if found == -1: - return None - - found_eq = str_def.find("=") - if found_eq != -1: - found = found_eq - - member.name = str_def[:found] - str_def = str_def[found + 1 :] - if member.name.find("*") == 0: - member.name = member.name.replace("*", "") - member.wtype.attr_type = attr_types.star - if member.name.find("&&") == 0: - member.name = member.name.replace("&&", "") - member.wtype.attr_type = attr_types.ampamp - if member.name.find("&") == 0: - member.name = member.name.replace("&", "") - member.wtype.attr_type = attr_types.amp - - if len(str_def.strip()) != 0: - return None - - if len(member.name.split(",")) > 1: - member_list = [] - for name in member.name.split(","): - name = name.strip() - member_list.append(WMember()) - member_list[-1].orig_text = member.orig_text - member_list[-1].wtype = member.wtype - member_list[-1].name = name - member_list[-1].containing_file = member.containing_file - member_list[-1].member_of = member.member_of - member_list[-1].namespace = member.namespace - member_list[-1].is_const = member.is_const - return member_list - - if member.wtype.cont: - WMember.opaque_containers[member.wtype.gen_identifier()] = member.wtype - - return member - - @autostring - def gen_boost_py(self, *, stream): - if False and self.wtype.attr_type == attr_types.default: - property_type = self.wtype.gen_text_cpp() - stream.write(f'\t\t\t.def_property("{self.name}",\n') - stream.write(f'\t\t\t\t[]({self.member_of.fully_qualified_name()} &o) -> {property_type}& {{ return o.{self.name}; }},\n') - stream.write(f'\t\t\t\t[]({self.member_of.fully_qualified_name()} &o, {property_type} const &p) {{ o.{self.name} = p; }},\n') - stream.write(f'\t\t\t\tpy::return_value_policy::reference_internal\n') - stream.write(f'\t\t\t)\n') - else: - meth = "def_readonly" if self.is_const else "def_readwrite" - stream.write( - f'\t\t\t.{meth}("{self.name}", &{self.member_of.fully_qualified_name()}::{self.name})\n' - ) - - def __repr__(self): - return f"{self.__class__.__qualname__}({repr(self.__dict__)})" - - -class WGlobal: - orig_text = None - wtype = attr_types.default - name = None - containing_file = None - namespace = "" - is_const = False - - def from_string(str_def, containing_file, line_number, namespace): - glbl = WGlobal() - glbl.orig_text = str_def - glbl.wtype = None - glbl.name = "" - glbl.containing_file = containing_file - glbl.namespace = namespace - glbl.is_const = False - - if not str.startswith(str_def, "extern"): - return None - str_def = str_def[7:] - - if str.startswith(str_def, "const "): - glbl.is_const = True - str_def = str_def[6:] - - if str_def.count(" ") == 0: - return None - - parts = split_list(str_def.strip(), " ") - - prefix = "" - i = 0 - for part in parts: - if part in ["unsigned", "long", "short"]: - prefix += part + " " - i += 1 - else: - break - parts = parts[i:] - - if len(parts) <= 1: - return None - - glbl.wtype = WType.from_string(prefix + parts[0], containing_file, line_number) - - if glbl.wtype == None: - return None - - str_def = parts[1] - for part in parts[2:]: - str_def = str_def + " " + part - - if ( - str_def.find("(") != -1 - or str_def.find(")") != -1 - or str_def.find("{") != -1 - or str_def.find("}") != -1 - ): - return None - - found = str_def.find(";") - if found == -1: - return None - - found_eq = str_def.find("=") - if found_eq != -1: - found = found_eq - - glbl.name = str_def[:found] - str_def = str_def[found + 1 :] - if glbl.name.find("*") == 0: - glbl.name = glbl.name.replace("*", "") - glbl.wtype.attr_type = attr_types.star - if glbl.name.find("&&") == 0: - glbl.name = glbl.name.replace("&&", "") - glbl.wtype.attr_type = attr_types.ampamp - if glbl.name.find("&") == 0: - glbl.name = glbl.name.replace("&", "") - glbl.wtype.attr_type = attr_types.amp - - if len(str_def.strip()) != 0: - return None - - if len(glbl.name.split(",")) > 1: - glbl_list = [] - for name in glbl.name.split(","): - name = name.strip() - glbl_list.append(WGlobal()) - glbl_list[-1].orig_text = glbl.orig_text - glbl_list[-1].wtype = glbl.wtype - glbl_list[-1].name = name - glbl_list[-1].containing_file = glbl.containing_file - glbl_list[-1].namespace = glbl.namespace - glbl_list[-1].is_const = glbl.is_const - return glbl_list - - return glbl - - @autostring - def gen_boost_py(self, *, stream): - args = [ - f'"{self.name}"', - ] - meth = "def_readonly_static" - if not self.is_const: - meth = "def_readwrite_static" - args.append(f"&{self.namespace}::{self.name}") - stream.write(f'\t\t\t.{meth}({", ".join(args)})\n') - - -def concat_namespace(tuple_list): - if len(tuple_list) == 0: - return "" - ret = "" - for namespace in tuple_list: - ret += "::" + namespace[0] - return ret[2:] - - -def calc_ident(text): - if len(text) == 0 or text[0] != " ": - return 0 - return calc_ident(text[1:]) + 1 - - -def assure_length(text, length, left=False): - if len(text) > length: - return text[:length] - if left: - return text + " " * (length - len(text)) - return " " * (length - len(text)) + text - - -def nesting_delta(s): - return s.count("{") - s.count("}") - - -def parse_header(source): - debug("Parsing " + source.name + ".pyh", 1) - source_file = open(source.name + ".pyh", "r") - - source_text = [] - in_line = source_file.readline() - - namespaces = [] - - while in_line: - if len(in_line) > 1: - source_text.append( - in_line.replace("char *", "char_p ").replace("char* ", "char_p ") - ) - in_line = source_file.readline() - - i = 0 - - namespaces = [] - classes = [] - private_segment = False - - while i < len(source_text): - line = ( - source_text[i] - .replace( - "YOSYS_NAMESPACE_BEGIN", - " namespace YOSYS_NAMESPACE{", - ) - .replace("YOSYS_NAMESPACE_END", " }") - ) - ugly_line = unpretty_string(line) - debug(f"READ:>> {line}", 2) - - # for anonymous unions, ignore union enclosure by skipping start line and replacing end line with new line - if "union {" in line: - j = i + 1 - while j < len(source_text): - union_line = source_text[j] - if "};" in union_line: - source_text[j] = "\n" - break - j += 1 - if j != len(source_text): - i += 1 - continue - - if str.startswith( - ugly_line, "namespace " - ): # and ugly_line.find("std") == -1 and ugly_line.find("__") == -1: - namespace_name = ugly_line[10:].replace("{", "").strip() - namespaces.append((namespace_name, ugly_line.count("{"))) - debug("-----NAMESPACE " + concat_namespace(namespaces) + "-----", 3) - i += 1 - continue - - if len(namespaces) != 0: - namespaces[-1] = ( - namespaces[-1][0], - namespaces[-1][1] + nesting_delta(ugly_line), - ) - if namespaces[-1][1] == 0: - debug("-----END NAMESPACE " + concat_namespace(namespaces) + "-----", 3) - namespaces.pop() - i += 1 - continue - - if ( - str.startswith(ugly_line, "struct ") or str.startswith(ugly_line, "class") - ) and ugly_line.count(";") == 0: - # Opening a record declaration which isn't a forward declaration - struct_name = ugly_line.split(" ")[1].split("::")[-1] - impl_namespaces = ugly_line.split(" ")[1].split("::")[:-1] - complete_namespace = concat_namespace(namespaces) - for namespace in impl_namespaces: - complete_namespace += "::" + namespace - debug("\tFound " + struct_name + " in " + complete_namespace, 2) - - base_class_name = None - if len(ugly_line.split(" : ")) > 1: # class is derived - deriv_str = ugly_line.split(" : ")[1] - if len(deriv_str.split("::")) > 1: # namespace of base class is given - base_class_name = deriv_str.split("::", 1)[1] - else: - base_class_name = deriv_str.split(" ")[0] - debug("\t " + struct_name + " is derived from " + base_class_name, 2) - base_class = class_by_name(base_class_name) - - c = (class_by_name(struct_name), ugly_line.count("{")) # calc_ident(line)) - debug(f"switch to {struct_name} in namespace {namespaces}", 2) - if struct_name in classnames: - c[0].namespace = complete_namespace - c[0].base_class = base_class - classes.append(c) - i += 1 - continue - - if len(classes): - c = (classes[-1][0], classes[-1][1] + nesting_delta(ugly_line)) - classes[-1] = c - if c[1] == 0: - if c[0] == None: - debug("\tExiting unknown class", 3) - else: - debug("\tExiting class " + c[0].name, 3) - classes.pop() - private_segment = False - i += 1 - continue - - class_ = classes[-1] if classes else None - - if class_ != None and ( - line.find("private:") != -1 or line.find("protected:") != -1 - ): - private_segment = True - i += 1 - continue - if class_ != None and line.find("public:") != -1: - private_segment = False - i += 1 - continue - - candidate = None - - if private_segment and class_ != None and class_[0] != None: - candidate = WConstructor.from_string( - ugly_line, source.name, class_[0], i, True - ) - if candidate != None: - debug( - '\t\tFound constructor of class "' - + class_[0].name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - class_[0].found_constrs.append(candidate) - i += 1 - continue - - if not private_segment and (class_ == None or class_[0] != None): - if class_ != None: - candidate = WFunction.from_string( - ugly_line, source.name, class_[0], i, concat_namespace(namespaces) - ) - else: - candidate = WFunction.from_string( - ugly_line, source.name, None, i, concat_namespace(namespaces) - ) - if candidate != None and candidate.name.find("::") == -1: - if class_ == None: - debug( - '\tFound unowned function "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - unowned_functions.append(candidate) - else: - debug( - '\t\tFound function "' - + candidate.name - + '" of class "' - + class_[0].name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - class_[0].found_funs.append(candidate) - else: - candidate = WEnum.from_string( - ugly_line, concat_namespace(namespaces), i - ) - if candidate != None: - enums.append(candidate) - debug( - '\tFound enum "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - elif class_ != None and class_[1] == 1: - candidate = WConstructor.from_string( - ugly_line, source.name, class_[0], i - ) - if candidate != None: - debug( - '\t\tFound constructor of class "' - + class_[0].name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - class_[0].found_constrs.append(candidate) - else: - candidate = WMember.from_string( - ugly_line, - source.name, - class_[0], - i, - concat_namespace(namespaces), - ) - if candidate != None: - if type(candidate) == list: - for c in candidate: - debug( - '\t\tFound member "' - + c.name - + '" of class "' - + class_[0].name - + '" of type "' - + c.wtype.name - + '"', - 2, - ) - class_[0].found_vars.extend(candidate) - else: - debug( - '\t\tFound member "' - + candidate.name - + '" of class "' - + class_[0].name - + '" of type "' - + candidate.wtype.name - + '"', - 2, - ) - class_[0].found_vars.append(candidate) - if candidate == None and class_ == None: - candidate = WGlobal.from_string( - ugly_line, source.name, i, concat_namespace(namespaces) - ) - if candidate != None: - if type(candidate) == list: - for c in candidate: - glbls.append(c) - debug( - '\tFound global "' - + c.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - else: - glbls.append(candidate) - debug( - '\tFound global "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - - j = i - line = unpretty_string(line) - while ( - candidate == None - and j + 1 < len(source_text) - and line.count(";") <= 1 - and line.count("(") >= line.count(")") - ): - j += 1 - line = line + "\n" + unpretty_string(source_text[j]) - if class_ != None: - candidate = WFunction.from_string( - ugly_line, - source.name, - class_[0], - i, - concat_namespace(namespaces), - ) - else: - candidate = WFunction.from_string( - ugly_line, source.name, None, i, concat_namespace(namespaces) - ) - if candidate != None and candidate.name.find("::") == -1: - if class_ == None: - debug( - '\tFound unowned function "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - unowned_functions.append(candidate) - else: - debug( - '\t\tFound function "' - + candidate.name - + '" of class "' - + class_[0].name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - class_[0].found_funs.append(candidate) - continue - candidate = WEnum.from_string(line, concat_namespace(namespaces), i) - if candidate != None: - enums.append(candidate) - debug( - '\tFound enum "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - continue - if class_ != None: - candidate = WConstructor.from_string( - line, source.name, class_[0], i - ) - if candidate != None: - debug( - '\t\tFound constructor of class "' - + class_[0].name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - class_[0].found_constrs.append(candidate) - continue - if class_ == None: - candidate = WGlobal.from_string( - line, source.name, i, concat_namespace(namespaces) - ) - if candidate != None: - if type(candidate) == list: - for c in candidate: - glbls.append(c) - debug( - '\tFound global "' - + c.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - else: - glbls.append(candidate) - debug( - '\tFound global "' - + candidate.name - + '" in namespace ' - + concat_namespace(namespaces), - 2, - ) - continue - if candidate != None: - while i < j: - i += 1 - line = ( - source_text[i] - .replace( - "YOSYS_NAMESPACE_BEGIN", - " namespace YOSYS_NAMESPACE{", - ) - .replace("YOSYS_NAMESPACE_END", " }") - ) - ugly_line = unpretty_string(line) - if len(namespaces) != 0: - namespaces[-1] = ( - namespaces[-1][0], - namespaces[-1][1] + nesting_delta(ugly_line), - ) - if namespaces[-1][1] == 0: - debug( - "-----END NAMESPACE " - + concat_namespace(namespaces) - + "-----", - 3, - ) - namespaces.pop() - if len(classes): - c = (classes[-1][0], classes[-1][1] + nesting_delta(ugly_line)) - classes[-1] = c - if c[1] == 0: - if c[0] == None: - debug("\tExiting unknown class", 3) - else: - debug("\tExiting class " + c[0].name, 3) - classes.pop() - private_segment = False - i += 1 - else: - i += 1 - - -def debug(message, level): - if level <= debug.debug_level: - print(message) - - -def expand_functions(): - global unowned_functions - new_funs = [] - for fun in unowned_functions: - new_funs.append(fun) - unowned_functions = new_funs - for source in sources: - for class_ in source.classes: - new_funs = [] - for fun in class_.found_funs: - new_funs.append(fun) - class_.found_funs = new_funs - - -def inherit_members(): - for source in sources: - for class_ in source.classes: - if class_.base_class: - base_funs = copy.deepcopy(class_.base_class.found_funs) - for fun in base_funs: - fun.member_of = class_ - fun.namespace = class_.namespace - base_vars = copy.deepcopy(class_.base_class.found_vars) - for var in base_vars: - var.member_of = class_ - var.namespace = class_.namespace - class_.found_funs.extend(base_funs) - class_.found_vars.extend(base_vars) - - -def clean_duplicates(): - for source in sources: - for class_ in source.classes: - known_decls = {} - for fun in class_.found_funs: - if fun.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + fun.gen_decl_hash_py(), 3) - - other = known_decls[fun.gen_decl_hash_py()] - if fun.mangled_name == other.mangled_name: - fun.duplicate = True - debug('Disabled "' + fun.gen_decl_hash_py() + '"', 3) - continue - - other.gen_alias() - fun.gen_alias() - else: - known_decls[fun.gen_decl_hash_py()] = fun - known_decls = [] - for con in class_.found_constrs: - if con.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + con.gen_decl_hash_py(), 3) - con.duplicate = True - else: - known_decls.append(con.gen_decl_hash_py()) - known_decls = [] - for fun in unowned_functions: - if fun.gen_decl_hash_py() in known_decls: - debug("Multiple declarations of " + fun.gen_decl_hash_py(), 3) - fun.duplicate = True - else: - known_decls.append(fun.gen_decl_hash_py()) - - -def gen_wrappers(filename, debug_level_=0): - filename = Path(filename) - - debug.debug_level = debug_level_ - for source in sources: - parse_header(source) - - expand_functions() - inherit_members() - clean_duplicates() - - import shutil - import math - - col = shutil.get_terminal_size((80, 20)).columns - debug("-" * col, 1) - debug( - "-" * math.floor((col - 7) / 2) + "SUMMARY" + "-" * math.ceil((col - 7) / 2), 1 - ) - debug("-" * col, 1) - for source in sources: - for class_ in source.classes: - debug( - "Class " - + assure_length(class_.name, len(max(classnames, key=len)), True) - + " contains " - + assure_length(str(len(class_.found_vars)), 3, False) - + " member variables, " - + assure_length(str(len(class_.found_funs)), 3, False) - + " methods and " - + assure_length(str(len(class_.found_constrs)), 2, False) - + " constructors", - 1, - ) - if len(class_.found_constrs) == 0: - class_.found_constrs.append(WConstructor(source.name, class_)) - debug(str(len(unowned_functions)) + " functions are unowned", 1) - debug(str(len(unowned_functions)) + " global variables", 1) - for enum in enums: - debug( - "Enum " - + assure_length(enum.name, len(max(enum_names, key=len)), True) - + " contains " - + assure_length(str(len(enum.values)), 2, False) - + " values", - 1, - ) - debug("-" * col, 1) - - tpl = __file_dir__ / "wrappers_tpl.cc" - with open(tpl, encoding="utf8") as f, open( - filename, "w", encoding="utf8" - ) as wrapper_file: - do_line_directive = True - for i, line in enumerate(f): - if do_line_directive: - wrapper_file.write(f'#line {i + 1} "{tpl}"\n') - do_line_directive = False - if "" in line: - for source in sources: - wrapper_file.write(f'#include "{source.name}.h"\n') - do_line_directive = True - elif "" in line: - wrapper_file.write("// Opaque Container Declaration\n") - for i, (container_identifier, container) in enumerate(WMember.opaque_containers.items()): - wrapper_file.write(f"using {container_identifier} = {container.gen_text_cpp()};\n") - wrapper_file.write(f'PYBIND11_MAKE_OPAQUE({container_identifier})\n') - wrapper_file.write("\n") - do_line_directive = True - elif "" in line: - do_line_directive = True - elif "" in line: - wrapper_file.write("\t\t// Opaque Container Binding\n") - for container in WMember.opaque_containers.values(): - container.cont.gen_boost_py(stream=wrapper_file) - wrapper_file.write("\n") - - wrapper_file.write("\t\t// Enums\n") - for enum in enums: - enum.gen_boost_py(stream=wrapper_file) - wrapper_file.write("\n") - - wrapper_file.write("\t\t// Classes\n") - for source in sources: - for wclass in source.classes: - wclass.gen_boost_py(stream=wrapper_file) - wrapper_file.write("\n") - - wrapper_file.write("\t\t// Global Functions\n") - for fun in sorted(unowned_functions, key=lambda x: x.alias): - fun.gen_boost_py(stream=wrapper_file) - wrapper_file.write("\n") - - wrapper_file.write("\t\t// Global Variables\n") - wrapper_file.write('\t\tpy::class_(m, "Yosys")\n') - for glbl in sorted(glbls, key=lambda x: (not x.is_const, x.name)): - glbl.gen_boost_py(stream=wrapper_file) - wrapper_file.write("\t\t\t;\n") - do_line_directive = True - else: - wrapper_file.write(line) - def print_includes(): - for source in sources: - print(source.name + ".pyh") + for header in pyosys_headers: + print(header.name) if __name__ == "__main__": - ap = argparse.ArgumentParser() - ap.add_argument("--print-includes", action="store_true") - ap.add_argument("--debug", default=0, type=int) - ap.add_argument("output", nargs="?") - ns = ap.parse_args() - if ns.print_includes: - print_includes() - exit(0) - gen_wrappers(ns.output, ns.debug) + ap = argparse.ArgumentParser() + ap.add_argument("--debug", default=0, type=int) + group = ap.add_mutually_exclusive_group(required=True) + group.add_argument("--print-includes", action="store_true") + group.add_argument("output", nargs="?") + ns = ap.parse_args() + if ns.print_includes: + print_includes() + exit(0) + + out_path = Path(ns.output) + out_inc = out_path.parent / (out_path.stem + ".inc.cc") + with open(out_path, "w", encoding="utf8") as f, open( + out_inc, "w", encoding="utf8" + ) as inc_f: + generator = PyosysWrapperGenerator( + from_headers=pyosys_headers, wrapper_stream=f, header_stream=inc_f + ) + generator.generate() diff --git a/pyosys/hashlib.h b/pyosys/hashlib.h index e8ace90c8..2ecadc3f1 100644 --- a/pyosys/hashlib.h +++ b/pyosys/hashlib.h @@ -48,6 +48,7 @@ #include "kernel/hashlib.h" namespace pybind11 { +namespace hashlib { template struct is_pointer { static const bool value = false; }; @@ -59,6 +60,11 @@ bool is_mapping(object obj) { return isinstance(obj, mapping); } +// shim +template +void bind_vector(module &m, const char *name_cstr) { + pybind11::bind_vector(m, name_cstr); +} // also used for std::set because the semantics are close enough template @@ -106,6 +112,13 @@ void bind_pool(module &m, const char *name_cstr) { }); } +// shim +template +void bind_set(module &m, const char *name_cstr) { + bind_pool(m, name_cstr); +} + + template void update_dict(C *target, iterable &iterable_or_mapping) { if (is_mapping(iterable_or_mapping)) { @@ -272,4 +285,5 @@ void bind_idict(module &m, const char *name_cstr) { }); } } +}; // namespace hashlib }; // namespace pybind11 diff --git a/pyosys/wrappers_tpl.cc b/pyosys/wrappers_tpl.cc index e921ae17a..d235b2c55 100644 --- a/pyosys/wrappers_tpl.cc +++ b/pyosys/wrappers_tpl.cc @@ -26,7 +26,12 @@ USING_YOSYS_NAMESPACE -// +using std::set; +using std::regex; +using std::ostream; +using namespace RTLIL; + +#include "wrappers.inc.cc" namespace YOSYS_PYTHON { @@ -37,8 +42,6 @@ namespace YOSYS_PYTHON { struct YosysStatics{}; - // - // Trampolines for Classes with Python-Overridable Virtual Methods // https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python class PassTrampoline : public Pass { @@ -182,7 +185,18 @@ namespace YOSYS_PYTHON { })); } - m.def("log_to_stream", &log_to_stream, "pipes yosys logs to a Python stream"); + // Logging Methods + m.def("log_header", [](Design *d, std::string s) { log_formatted_header(d, "%s", s); }); + m.def("log", [](std::string s) { log_formatted_string("%s", s); }); + m.def("log_file_info", [](std::string_view file, int line, std::string s) { log_formatted_file_info(file, line, s); }); + m.def("log_warning", [](std::string s) { log_formatted_warning("Warning: ", s); }); + m.def("log_warning_noprefix", [](std::string s) { log_formatted_warning("", s); }); + m.def("log_file_warning", [](std::string_view file, int line, std::string s) { log_formatted_file_warning(file, line, s); }); + m.def("log_error", [](std::string s) { log_formatted_error(s); }); + m.def("log_file_error", [](std::string_view file, int line, std::string s) { log_formatted_file_error(file, line, s); }); + + // Namespace to host global objects + auto global_variables = py::class_(m, "Yosys"); // Trampoline Classes py::class_>(m, "Pass") @@ -241,6 +255,9 @@ namespace YOSYS_PYTHON { .def("notify_blackout", &RTLIL::Monitor::notify_blackout) ; + // Bind Opaque Containers + bind_autogenerated_opaque_containers(m); + // }; }; diff --git a/pyproject.toml b/pyproject.toml index caa620528..5a218c19d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [build-system] requires = [ - "setuptools>=42", - "pybind11>=3,<4", + "setuptools>=42", + "pybind11>=3,<4", + "cxxheaderparser", ] build-backend = "setuptools.build_meta" diff --git a/tests/pyosys/test_logs.py b/tests/pyosys/test_logs.py new file mode 100644 index 000000000..b6bd02222 --- /dev/null +++ b/tests/pyosys/test_logs.py @@ -0,0 +1,8 @@ +from pyosys import libyosys as ys + +d = ys.Design(); ys.log_header(d, "foo\n") +ys.log("foo\n") +ys.log_warning("foo\n") +ys.log_warning_noprefix("foo\n") +ys.log_file_info("foo.ys", 1, "foo\n") +ys.log_file_warning("foo.ys", 1, "foo\n")