From: Benedikt Tutzer Date: Wed, 9 Oct 2019 11:59:35 +0000 (+0200) Subject: Expose global variables and allow logging to python streams X-Git-Tag: working-ls180~964^2~2 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=9c59a56aa495a9d73979c76b92440ad256ca0f13;p=yosys.git Expose global variables and allow logging to python streams Global variables are now accessible via the Yosys class. To capture Yosys output, once can now register an output stream in Pyosys. --- diff --git a/misc/py_wrap_generator.py b/misc/py_wrap_generator.py index 4ce8e947e..d8649e9ce 100644 --- a/misc/py_wrap_generator.py +++ b/misc/py_wrap_generator.py @@ -253,6 +253,8 @@ class WContainer: 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 @@ -880,11 +882,8 @@ class WClass: text += fun.gen_def_virtual() return text - def gen_boost_py(self): - text = "\n\t\tclass_<" + self.name - if self.link_type == link_types.derive: - text += "Wrap, boost::noncopyable" - text += ">(\"" + self.name + "\"" + def gen_boost_py_body(self): + text = "" if self.printable_constrs() == 0 or not self.contains_default_constr(): text += ", no_init" text += ")" @@ -907,6 +906,21 @@ class WClass: text += "\n\t\t\t;\n" return text + def gen_boost_py(self): + body = self.gen_boost_py_body() + if self.link_type == link_types.derive: + text = "\n\t\tclass_<" + self.name + ">(\"" + self.name + "\"" + text += body + text += "\n\t\tclass_<" + self.name + text += "Wrap, boost::noncopyable" + text += ">(\"" + self.name + "\"" + text += body + else: + text = "\n\t\tclass_<" + self.name + ">(\"Cpp" + self.name + "\"" + text += body + return text + + def contains_default_constr(self): for c in self.found_constrs: if len(c.args) == 0: @@ -974,6 +988,7 @@ blacklist_methods = ["YOSYS_NAMESPACE::Pass::run_register", "YOSYS_NAMESPACE::Mo enum_names = ["State","SyncType","ConstFlags"] enums = [] #Do not edit +glbls = [] unowned_functions = [] @@ -1723,6 +1738,159 @@ class WMember: text += ")" return text +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 + + def gen_def(self): + text = "\n\t" + if self.is_const: + text += "const " + text += self.wtype.gen_text() + " get_var_py_" + self.name + "()" + text += "\n\t{\n\t\t" + if self.wtype.attr_type == attr_types.star: + text += "if(" + self.namespace + "::" + self.name + " == NULL)\n\t\t\t" + text += "throw std::runtime_error(\"" + self.namespace + "::" + self.name + " is NULL\");\n\t\t" + if self.wtype.name in known_containers: + text += self.wtype.gen_text_cpp() + else: + if self.is_const: + text += "const " + text += self.wtype.gen_text() + + if self.wtype.name in classnames or (self.wtype.name in known_containers and self.wtype.attr_type == attr_types.star): + text += "*" + text += " ret_ = " + if self.wtype.name in classnames: + text += self.wtype.name + "::get_py_obj(" + if self.wtype.attr_type != attr_types.star: + text += "&" + text += self.namespace + "::" + self.name + if self.wtype.name in classnames: + text += ")" + text += ";" + + if self.wtype.name in classnames: + text += "\n\t\treturn *ret_;" + elif self.wtype.name in known_containers: + text += known_containers[self.wtype.name].translate_cpp("ret_", self.wtype.cont.args, "\n\t\t", self.wtype.attr_type == attr_types.star) + text += "\n\t\treturn ret____tmp;" + else: + text += "\n\t\treturn ret_;" + text += "\n\t}\n" + + if self.is_const: + return text + + ret = Attribute(self.wtype, "rhs"); + + if self.wtype.name in classnames: + text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " *rhs)" + else: + text += "\n\tvoid set_var_py_" + self.name + "(" + self.wtype.gen_text() + " rhs)" + text += "\n\t{" + text += ret.gen_translation() + text += "\n\t\t" + self.namespace + "::" + self.name + " = " + ret.gen_call() + ";" + text += "\n\t}\n" + + return text; + + def gen_boost_py(self): + text = "\n\t\t\t.add_static_property(\"" + self.name + "\", &" + "YOSYS_PYTHON::get_var_py_" + self.name + if not self.is_const: + text += ", &YOSYS_PYTHON::set_var_py_" + self.name + text += ")" + return text + def concat_namespace(tuple_list): if len(tuple_list) == 0: return "" @@ -1859,6 +2027,16 @@ def parse_header(source): 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) @@ -1888,6 +2066,17 @@ def parse_header(source): 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 @@ -1990,6 +2179,7 @@ def gen_wrappers(filename, debug_level_ = 0): 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) @@ -2025,10 +2215,15 @@ def gen_wrappers(filename, debug_level_ = 0): #include #include #include - +#include // std::streamsize +#include +#include // boost::iostreams::sink +#include USING_YOSYS_NAMESPACE namespace YOSYS_PYTHON { + + struct YosysStatics{}; """) for source in sources: @@ -2050,6 +2245,9 @@ namespace YOSYS_PYTHON { for fun in unowned_functions: wrapper_file.write(fun.gen_def()) + for glbl in glbls: + wrapper_file.write(glbl.gen_def()) + wrapper_file.write(""" struct Initializer { Initializer() { @@ -2068,12 +2266,89 @@ namespace YOSYS_PYTHON { } }; + + /// source: https://stackoverflow.com/questions/26033781/converting-python-io-object-to-stdostream-when-using-boostpython?noredirect=1&lq=1 + /// @brief Type that implements the Boost.IOStream's Sink and Flushable + /// concept for writing data to Python object that support: + /// n = object.write(str) # n = None or bytes written + /// object.flush() # if flush exists, then it is callable + class PythonOutputDevice + { + public: + + // This class models both the Sink and Flushable concepts. + struct category + : boost::iostreams::sink_tag, + boost::iostreams::flushable_tag + {}; + + explicit + PythonOutputDevice(boost::python::object object) + : object_(object) + {} + + // Sink concept. + public: + + typedef char char_type; + + std::streamsize write(const char* buffer, std::streamsize buffer_size) + { + namespace python = boost::python; + // Copy the buffer to a python string. + python::str data(buffer, buffer_size); + + // Invoke write on the python object, passing in the data. The following + // is equivalent to: + // n = object_.write(data) + python::extract bytes_written( + object_.attr("write")(data)); + + // Per the Sink concept, return the number of bytes written. If the + // Python return value provides a numeric result, then use it. Otherwise, + // such as the case of a File object, use the buffer_size. + return bytes_written.check() + ? bytes_written + : buffer_size; + } + + // Flushable concept. + public: + + bool flush() + { + // If flush exists, then call it. + boost::python::object flush = object_.attr("flush"); + if (!flush.is_none()) + { + flush(); + } + + // Always return true. If an error occurs, an exception should be thrown. + return true; + } + + private: + boost::python::object object_; + }; + + /// @brief Use an auxiliary function to adapt the legacy function. + void log_to_stream(boost::python::object object) + { + // Create an ostream that delegates to the python object. + boost::iostreams::stream* output = new boost::iostreams::stream(object); + Yosys::log_streams.insert(Yosys::log_streams.begin(), output); + }; + + BOOST_PYTHON_MODULE(libyosys) { using namespace boost::python; class_("Initializer"); scope().attr("_hidden") = new Initializer(); + + def("log_to_stream", &log_to_stream); """) for enum in enums: @@ -2086,6 +2361,11 @@ namespace YOSYS_PYTHON { for fun in unowned_functions: wrapper_file.write(fun.gen_boost_py()) + wrapper_file.write("\n\n\t\tclass_(\"Yosys\")\n") + for glbl in glbls: + wrapper_file.write(glbl.gen_boost_py()) + wrapper_file.write("\t\t;\n") + wrapper_file.write("\n\t}\n}\n#endif") def print_includes():