From ad44f2e7510955e497137e9fed02264de88674d5 Mon Sep 17 00:00:00 2001 From: Pierre Delaunay Date: Thu, 29 Aug 2024 18:17:12 -0400 Subject: [PATCH] Wasm stuff --- .gitignore | 2 + .vscode/settings.json | 3 +- ignore/parser/idl_generator.py | 308 +++++++++ index.html | 45 +- src/CMakeLists.txt | 3 + src/cli/cli.cpp | 11 + src/parser/parser.cpp | 2 +- src/wasm/ext.cpp | 153 +++++ src/wasm/generated.cpp | 0 src/wasm/interface.cpp | 1132 +++++++++++++++++++++++++++++++- 10 files changed, 1620 insertions(+), 39 deletions(-) create mode 100644 ignore/parser/idl_generator.py create mode 100644 src/wasm/ext.cpp create mode 100644 src/wasm/generated.cpp diff --git a/.gitignore b/.gitignore index 40ce6e71..cc6ba622 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,5 @@ tests/cases/vm/ tests/cases/sema tests/cases/parser + +ignore/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ba0b71b..e63d6421 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -118,7 +118,8 @@ "*.ipp": "cpp", "__assert": "cpp", "__availability": "cpp", - "*.def": "cpp" + "*.def": "cpp", + "html5.h": "c" }, "cmake.configureSettings": { "CMAKE_BUILD_TYPE": "${buildType}" diff --git a/ignore/parser/idl_generator.py b/ignore/parser/idl_generator.py new file mode 100644 index 00000000..b1dc7dbd --- /dev/null +++ b/ignore/parser/idl_generator.py @@ -0,0 +1,308 @@ +from collections import defaultdict + +from idl_parser import IDLParser, ParseFile, IDLLexer, IDLNode + + + +called = False + +def once(fun): + + def _(*args, **kwargs): + global called + if not called: + # called = True + return fun(*args, **kwargs) + return _ + + +@once +def dump(node: IDLNode): + traverse_dump(node) + print() + +def traverse_dump(node: IDLNode, depth: int = 0): + idt = " " * depth + nodestr = str(node) + print(f"{idt} {nodestr:<{50 - len(idt)}} {node._properties}") + for child in node._children: + traverse_dump(child, depth + 1) + + +def get(attr: IDLNode, classname: str, default=None): + for c in attr._children: + if c.GetClass() == classname: + return c + return default + + +class Transform: + def __init__(self, ast): + self.nodes = defaultdict(list) + + for f in ast._children: + assert f.GetClass() == "File" + + for child in f._children: + + method = getattr(self, "normalize_" + child.GetClass()) + + self.nodes[child.GetClass()].append(method(child)) + + + for k, _ in self.nodes.items(): + print(f"{k:>15} {len(_):6d} {_[0]}") + + def normalize_Interface(self, obj): + flat = { + "name": obj.GetName() + } + + for pname, val in obj._properties.items(): + if pname not in ("LINENO", "POSITION", "WARNINGS", "ERRORS"): + flat[pname] = val + + + for child in obj._children: + classname = child.GetClass() + method = getattr(self, "normalize_" + classname) + + if classname not in flat: + flat[classname] = [] + + flat[classname].append(method(child)) + + return flat + + def normalize_Constructor(self, obj): + return obj + + def normalize_Attribute(self, obj): + flat = { + "name": obj.GetName(), + } + + for child in obj._children: + classname = child.GetClass() + method = getattr(self, "normalize_" + classname) + flat[classname] = method(child) + + return flat + + def normalize_Type(self, obj): + flat = {} + + for child in obj._children: + if "name" not in flat: + flat["name"] = [] + + if "class" not in flat: + flat["class"] = [] + + flat["name"].append(child.GetName()) + flat["class"].append(child.GetClass()) + + for pname, pval in child._properties.items(): + if pname not in ("LINENO", "POSITION", "WARNINGS", "ERRORS", "FILENAME", "NAME"): + flat[pname] = pval + + return flat + + def normalize_Operation(self, obj): + flat = { + "name": obj.GetName(), + "args": [], + "return": "void" + } + + arguments = get(obj, "Arguments", None) + + return_type = get(obj, "Type", None) + + if return_type is not None: + flat["return"] = self.normalize_Type(return_type) + + if arguments is not None: + flat["args"] = self.normalize_Arguments(arguments) + + return flat + + def normalize_Elipsis(self, obj): + return obj is not None + + def normalize_Arguments(self, obj): + args = [] + + for child in obj._children: + arg = { + "name": child.GetName(), + "type": self.normalize_Type(get(child, "Type")), + "default": self.normalize_Default(get(child, "Default")), + "variadic": self.normalize_Elipsis(get(child, "Argument")) + } + + for pname, pval in child._properties.items(): + if pname not in ("LINENO", "POSITION", "WARNINGS", "ERRORS", "FILENAME", "NAME"): + arg[pname] = pval + + # print(arg) + args.append(arg) + + return args + + def normalize_Default(self, obj): + if obj is None: + return None + + val = obj.GetProperty('VALUE') + type = obj.GetProperty("TYPE") + + if type == "integer": + pass + elif type == "DOMString": + val = f"\"{val}\"" + elif type == "dictionary": + pass + elif type == "NULL": + # here we need to use `type` to figure out + # what NULL translates to + # if it is Any if needs to be some emscripten::NULL + # if it is optional it needs to be {} + val = "{}" + elif type == "boolean": + val = "true" if bool(val) else "false" + elif type is None: + pass + elif type == "sequence": + pass + elif type == "float": + pass + else: + print("UNHANDLED TYPE:", type) + + return { + "value": val, + "type": type + } + + def normalize_Inherit(self, obj): + return obj.GetName() + + def normalize_Maplike(self, obj): + key = self.normalize_Type(obj._children[0]) + val = self.normalize_Type(obj._children[1]) + + return { + "name": "dict", + "key": key, + "val": val + } + + def normalize_Iterable(self, obj): + return { + "name": "iterable", + "types": [self.normalize_Type(c) for c in obj._children] + } + + def normalize_Const(self, obj): + return { + "name": obj.GetName(), + "type": self.normalize_Type(obj._children[0]), + "value": self.normalize_Default(obj._children[1]), + } + + def normalize_Stringifier(self, obj): + # dump(obj) + return obj + + def normalize_Setlike(self, obj): + # dump(obj) + return obj + + def normalize_AsyncIterable(self, obj): + # dump(obj) + return obj + + def normalize_ExtAttributes(self, obj): + # dump(obj) + return obj + + def normalize_Includes(self, obj): + # dump(obj) + return obj + + def normalize_Dictionary(self, obj): + # dump(obj) + return obj + + def normalize_Enum(self, obj): + # dump(obj) + return obj + + def normalize_Typedef(self, obj): + # dump(obj) + return obj + + def normalize_Callback(self, obj): + # dump(obj) + return obj + + def normalize_Namespace(self, obj): + dump(obj) + return obj + + +class Generate: + def __init__(self, nodes) -> None: + for k, vv in nodes.items(): + for v in vv: + method = getattr(self, "generate_" + k) + method(v) + + def generate_Interface(self, obj): + pass + + + def generate_Includes(self, obj): + pass + + + def generate_Namespace(self, obj): + pass + + def generate_Dictionary(self, obj): + pass + + def generate_Enum(self, obj): + pass + + def generate_Typedef(self, obj): + pass + + def generate_Callback(self, obj): + pass + + +def main(argv): + nodes = [] + parser = IDLParser(IDLLexer()) + errors = 0 + for filename in argv: + filenode = ParseFile(parser, filename) + if (filenode): + errors += filenode.GetProperty('ERRORS') + nodes.append(filenode) + + ast = IDLNode('AST', '__AST__', 0, 0, nodes) + + if errors: + print('\nFound %d errors.\n' % errors) + + nodes = Transform(ast).nodes + + Generate(nodes) + + +if __name__ == '__main__': + import sys + sys.exit(main(sys.argv[1:])) diff --git a/index.html b/index.html index dc85416e..2eb7b7a0 100644 --- a/index.html +++ b/index.html @@ -789,32 +789,43 @@

WebAssembly Exported Functions and Objects

- - - var t = l.next(); - console.log(l.identifier()); + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8ca7bba8..18beecf4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -150,6 +150,9 @@ ENDIF() IF(BUILD_WEBASSEMBLY) LIST(APPEND LIBRARIES embind) + ADD_COMPILE_OPTIONS(-sEXCEPTION_CATCHING_ALLOWED=YES) + ADD_LINK_OPTIONS(-sEXCEPTION_CATCHING_ALLOWED=YES) + # add_definitions() # add_definitions("-s LINKABLE=1") # add_definitions("-s EXPORT_ALL=1") ENDIF() diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 3b44c60d..f513f59c 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -27,6 +27,16 @@ const char* VERSION_STRING = "\n" "[0] Version: " _HASH "\n" "[0] Date: " _DATE "\n\n"; + + + +#ifndef BUILD_WEBASSEMBLY +#define BUILD_WEBASSEMBLY 1 +#endif + +#if BUILD_WEBASSEMBLY + +#else int main(int argc, const char* argv[]) { auto linter = std::make_unique(); @@ -108,3 +118,4 @@ int main(int argc, const char* argv[]) { std::cerr << lython_args; return -1; } +#endif \ No newline at end of file diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 9e577b83..ac5943d8 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -158,7 +158,7 @@ StmtNode* Parser::parse_one(Node* parent, int depth, bool interactive) { tok = next_token(); } - if (in(token().type(), tok_desindent)){ + if (in(token().type(), tok_desindent, tok_eof)){ return nullptr; } diff --git a/src/wasm/ext.cpp b/src/wasm/ext.cpp new file mode 100644 index 00000000..36442438 --- /dev/null +++ b/src/wasm/ext.cpp @@ -0,0 +1,153 @@ + + +namespace emscripten { + template + struct TypeID::type>::value>> + { + static constexpr TYPEID get() { return TypeID::get(); } + }; + + template + struct BindingType> + { + using ValBinding = BindingType; + using WireType = ValBinding::WireType; + + static WireType toWireType(const std::function &f) { + // converter code + } + static std::function fromWireType(WireType value) { + return [=](Args...args) -> R + { + val &&v = ValBinding::fromWireType(value); + + val r = v(args...); + if constexpr (std::is_same_v) + { + // do nothing + } + else + { + return r.template as(); + } + }; + } + } + +// define C function wrapper +typedef std::function cfunction_wrapper; +// the map to store the C function id and wrapper +std::map cfunction_map; + +// helper function, used to find and call the target C function, and delete it? +emscripten::val cfunction_helper(std::string key, emscripten::val args) +{ + auto it = cfunction_map.find(key); + if (it != cfunction_map.end()) + { + auto func_ptr = it->second; + emscripten::val r = (*func_ptr)(args); + // delete it? should I? + // cfunction_map.erase(it); + // delete func_ptr; + return r; + } + return emscripten::val::undefined(); +} + +EMSCRIPTEN_BINDINGS(😅) +{ + // register the helper function first + function("cfunction_helper", &cfunction_helper); + // register the function that would return a lambda value + function("function_callback_function", &function_callback_function); +} + +template +struct StringTypeReplace { + using type = std::string; +}; + +template +static val NewFunctionVal(std::tuple tuple) +{ + return std::apply(&val::new_, std::tuple_cat(std::tuple(val::global("Function")), tuple)); +} + +template +Tuple GetJsFunctionArgsDeclare(std::index_sequence) +{ + return Tuple(std::tuple_element_t(std::string("a") + std::to_string(I))...); +} + +template +auto JsArgumentToCppObject(val args) +{ + return args[I].as(); +} + +template +Tuple ArgumentsToTuple_impl(val args, std::index_sequence) +{ + return Tuple{JsArgumentToCppObject, I>(args)...}; +} + +template +std::tuple...> ArgumentsToTuple(val args) +{ + return ArgumentsToTuple_impl...>>(args, std::make_index_sequence()); +} + +template +struct BindingType> +{ + using ValBinding = BindingType; + using WireType = ValBinding::WireType; + + static WireType toWireType(const std::function &f) { + // wrap the std::function or lambda + cfunction_wrapper wrap_func = [=](val args) + { + if (args.isArray()) { + // convert argument array to std::tuple + auto &&tuple = ArgumentsToTuple(args); + if constexpr (std::is_same_v) + { + std::apply(f, tuple); + return val::undefined(); + } + else + { + R &&r = std::apply(f, tuple); + return val(r); + } + } + return val::undefined(); + }; + // use function pointer as function key, make it be unique + auto func_ptr = new cfunction_wrapper(wrap_func); + std::string key = std::to_string(reinterpret_cast(func_ptr)); + cfunction_map[key] = func_ptr; + + // convert Args... to string 'a0,a1,a2...' as js function arguments declaration + using tuple_type = std::tuple::type...>; + auto tuple = GetJsFunctionArgsDeclare(std::make_index_sequence()); + std::ostringstream oss; + std::apply([&oss](const auto&... args) { + ((oss << args << ","), ...); + }, tuple); + std::string arg_array_str = oss.str(); + if (!arg_array_str.empty()) { + arg_array_str.erase(arg_array_str.size() - 1); + } + + // create a function val that would call the helper function + val &&func = NewFunctionVal(std::tuple_cat(tuple, std::tuple(std::string("return Module.call_cfunction(\"") + key + "\", [" + arg_array_str + "]);"))); + return ValBinding::toWireType(func); + } + + static std::function fromWireType(WireType value) { + // converter code + } +}; +} diff --git a/src/wasm/generated.cpp b/src/wasm/generated.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/wasm/interface.cpp b/src/wasm/interface.cpp index d7e7f38d..3ff31cad 100644 --- a/src/wasm/interface.cpp +++ b/src/wasm/interface.cpp @@ -6,12 +6,876 @@ #include #include - -#include "sema/sema.h" #include "parser/parser.h" +#include "sema/sema.h" #include "vm/tree.h" + +namespace emscripten { + /* +struct LambdaWrapper { + #define _NEW_NAME(prefix, counter) prefix##counter + #define EXPAND_AND_STRINGIFY(x) #x + #define LAMBDA_ID() EXPAND_AND_STRINGIFY(_NEW_NAME(__embind_, __COUNTER__)) + + template + LambdaWrapper(std::function fn, Policies...) { + __lambda_id = LAMBDA_ID(); + + using namespace internal; + + typename WithPolicies::template ArgTypeList args; + + using OriginalInvoker = Invoker; + auto invoke = &maybe_wrap_async::invoke; + + // register function only works on C function pointer + _embind_register_function( + __lambda_id, + args.getCount(), + args.getTypes(), + getSignature(invoke), + reinterpret_cast(invoke), + reinterpret_cast(fn), + isAsync::value); + } + + operator emscripten::val() { + return emscripten::val::global(__lambda_id); + } + + emscripten::val as_function() { + return emscripten::val::global(__lambda_id); + } + + const char* __lambda_id; +}; +*/ +} + +namespace js { + +using AnimId = emscripten::val; +using TimerHandle = emscripten::val; +using str = std::string; + +//! The window object represents an open window in a browser. +struct Window { + + /* + // Properties + closed Returns a boolean true if a window is closed. + console Returns the Console Object for the window. + See also The Console Object. + defaultStatus Deprecated. + document Returns the Document object for the window. + See also The Document Object. + frameElement Returns the frame in which the window runs. + frames Returns all window objects running in the window. + history Returns the History object for the window. + See also The History Object. + innerHeight Returns the height of the window's content area (viewport) including + scrollbars innerWidth Returns the width of a window's content area (viewport) including + scrollbars length Returns the number of