diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e3699f7f1..68142f895a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,6 +357,8 @@ if(EMSCRIPTEN) add_compile_flag("-sDISABLE_EXCEPTION_CATCHING=0") add_link_flag("-sDISABLE_EXCEPTION_CATCHING=0") endif() + # TODO: only for binaryen.js? + add_compile_flag("-DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0") if(EMSCRIPTEN_ENABLE_PTHREADS) add_compile_flag("-pthread") add_link_flag("-pthread") @@ -384,7 +386,7 @@ if(EMSCRIPTEN) add_link_flag("-sNODERAWFS") endif() # in opt builds, LTO helps so much (>20%) it's worth slow compile times - add_nondebug_compile_flag("-flto") + # add_nondebug_compile_flag("-flto") if(EMSCRIPTEN_ENABLE_WASM64) add_compile_flag("-sMEMORY64 -Wno-experimental") add_link_flag("-sMEMORY64") @@ -562,6 +564,32 @@ if(EMSCRIPTEN) # see https://github.com/emscripten-core/emscripten/issues/17228 target_link_libraries(binaryen_js "-sNODEJS_CATCH_EXIT=0") install(TARGETS binaryen_js DESTINATION ${CMAKE_INSTALL_BINDIR}) + + # binaryen.embind.js variant (WebAssembly) + add_executable(binaryen_embind_wasm + src/binaryen-embind.cpp) + target_link_libraries(binaryen_embind_wasm wasm asmjs emscripten-optimizer passes ir cfg support analysis parser wasm) + target_link_libraries(binaryen_embind_wasm "-sFILESYSTEM") + #target_link_libraries(binaryen_embind_wasm "-sEXPORT_NAME=Binaryen") + target_link_libraries(binaryen_embind_wasm "-sNODERAWFS=0") + target_link_libraries(binaryen_embind_wasm "-sMODULARIZE") + # Do not error on the repeated NODERAWFS argument + target_link_libraries(binaryen_embind_wasm "-Wno-unused-command-line-argument") + # Emit a single file for convenience of people using binaryen.js as a library, + # so they only need to distribute a single file. + #target_link_libraries(binaryen_embind_wasm "-sSINGLE_FILE") + #target_link_libraries(binaryen_embind_wasm "-sEXPORT_ES6") + target_link_libraries(binaryen_embind_wasm "-lembind") + target_link_libraries(binaryen_embind_wasm "-msign-ext") + target_link_libraries(binaryen_embind_wasm "-mbulk-memory") + #target_link_libraries(binaryen_embind_wasm optimized "--closure=1") + # TODO: Fix closure warnings! (#5062) + target_link_libraries(binaryen_embind_wasm optimized "-Wno-error=closure") + #target_link_libraries(binaryen_embind_wasm optimized "-flto") + target_link_libraries(binaryen_embind_wasm optimized "--profiling") # XXX + target_link_libraries(binaryen_embind_wasm optimized "-O1") # XXX + target_link_libraries(binaryen_embind_wasm debug "--profiling") + install(TARGETS binaryen_embind_wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() configure_file(scripts/binaryen-lit.in ${CMAKE_BINARY_DIR}/bin/binaryen-lit @ONLY) diff --git a/check.py b/check.py index dfaada0ab93..a3bce0c9748 100755 --- a/check.py +++ b/check.py @@ -364,6 +364,7 @@ def run(): ('unit', run_unittest), ('binaryenjs', binaryenjs.test_binaryen_js), ('binaryenjs_wasm', binaryenjs.test_binaryen_wasm), + ('binaryenjs_embind', binaryenjs.test_binaryen_embind), ('lit', run_lit), ('gtest', run_gtest), ]) diff --git a/scripts/emcc-tests.sh b/scripts/emcc-tests.sh index 409ba256ed4..205a38a03f9 100755 --- a/scripts/emcc-tests.sh +++ b/scripts/emcc-tests.sh @@ -4,6 +4,12 @@ set -o errexit set -o pipefail mkdir -p emcc-build + +# TODO +# ninja -C emcc-build binaryen_embind_wasm +# NODE=nodejs ./check.py --binaryen-bin=emcc-build/bin binaryenjs_embind +# + echo "emcc-tests: build:wasm" emcmake cmake -B emcc-build -DCMAKE_BUILD_TYPE=Release -G Ninja ninja -C emcc-build binaryen_wasm diff --git a/scripts/test/binaryenjs.py b/scripts/test/binaryenjs.py index 93fbe532f88..38b185fc876 100644 --- a/scripts/test/binaryenjs.py +++ b/scripts/test/binaryenjs.py @@ -113,3 +113,19 @@ def test_binaryen_js(): def test_binaryen_wasm(): do_test_binaryen_js_with(shared.BINARYEN_WASM) + + +def test_binaryen_embind(): + print('\n[ checking binaryen.js (embind) testcases (' + shared.BINARYEN_EMBIND + ')... ]\n') + + for s in sorted(os.listdir(os.path.join(shared.options.binaryen_test, 'binaryen-embind.js'))): + if not s.endswith('.js'): + continue + print(s) + test_path = os.path.join(shared.options.binaryen_test, 'binaryen-embind.js', s) + + # Run the test and pass the build as an argument, so it knows where to + # load it. + out = support.run_command([shared.NODEJS, test_path, shared.BINARYEN_EMBIND]) + assert('success.' in out) + diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 489cfefda09..a95cccc642d 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -213,6 +213,7 @@ def is_exe(fpath): 'wasm-emscripten-finalize')] BINARYEN_JS = os.path.join(options.binaryen_bin, 'binaryen_js.js') BINARYEN_WASM = os.path.join(options.binaryen_bin, 'binaryen_wasm.js') +BINARYEN_EMBIND = os.path.join(options.binaryen_bin, 'binaryen_embind_wasm.js') def wrap_with_valgrind(cmd): diff --git a/src/binaryen-embind.cpp b/src/binaryen-embind.cpp new file mode 100644 index 00000000000..d1bff33f617 --- /dev/null +++ b/src/binaryen-embind.cpp @@ -0,0 +1,167 @@ +/* + * Copyright 2024 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//============================== +// Binaryen Embind declarations +//============================== + +#include + +#include "wasm-builder.h" +#include "wasm.h" + +using namespace wasm; +using namespace emscripten; + +// Wrappers for things that don't quite fit with embind. +namespace { + +std::string stringify(Module& wasm) { + std::stringstream str; + str << wasm; + return str.str(); +} + +// Embind generates getters and setters for properties like +// DELEGATE_FIELD_CHILD_VECTOR. The setter in those cases needs a copy +// constructor, which we intentionally do not have for ArenaVectors etc. To +// work around this, we define a getter +//struct VecWrapper + +} // anonymous namespace + +EMSCRIPTEN_BINDINGS(Binaryen) { + + function("stringify", &stringify); + + enum_("BasicType") + .value("i32", Type::BasicType::i32) + .value("i64", Type::BasicType::i64) + .value("f32", Type::BasicType::f32) + .value("f64", Type::BasicType::f64); + + // TODO: make a "type factory" that produces types, so we can use + // value_object for Type? + class_("Type") + .constructor() + .function("isTuple", &Type::isTuple) + .function("isRef", &Type::isRef) + .function("isFunction", &Type::isFunction) + .function("isData", &Type::isData) + .function("isNullable", &Type::isNullable) + .function("isNonNullable", &Type::isNonNullable) + .function("isNull", &Type::isNull) + .function("isSignature", &Type::isSignature) + .function("isStruct", &Type::isStruct) + .function("isArray", &Type::isArray) + .function("isString", &Type::isString) + .function("isDefaultable", &Type::isDefaultable) + .function("getHeapType", &Type::getHeapType); + register_vector("TypeVec"); + + value_object("Signature") + .field("params", &Signature::params) + .field("results", &Signature::results); + + class_("HeapType").constructor(); + + class_("Name").constructor(); + + class_("Expression").property("type", &Expression::type); + + // Expression types, autogenerated. + +#define DELEGATE_ID id + +#define DELEGATE_FIELD_CHILD(id, field) \ + .property( \ + #field, \ + &id::field, \ + allow_raw_pointers()) +#define DELEGATE_FIELD_CHILD_VECTOR(id, field) \ + .function("get_" #field, +[](id& curr) { return &curr.field; }, return_value_policy::reference()) +#define DELEGATE_FIELD_TYPE(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_HEAPTYPE(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_INT(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_LITERAL(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_NAME(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_SCOPE_NAME_DEF(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_ADDRESS(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_INT_ARRAY(id, field) .property(#field, &id::field) +#define DELEGATE_FIELD_INT_VECTOR(id, field) \ + //.property(#field, &id::field, return_value_policy::reference()) +#define DELEGATE_FIELD_NAME_VECTOR(id, field) \ + //.property(#field, &id::field, return_value_policy::reference()) +#define DELEGATE_FIELD_SCOPE_NAME_USE_VECTOR(id, field) \ + //.property(#field, &id::field, return_value_policy::reference()) +#define DELEGATE_FIELD_TYPE_VECTOR(id, field) \ + //.property(#field, &id::field, return_value_policy::reference()) + +#define DELEGATE_FIELD_MAIN_START + +#define DELEGATE_FIELD_CASE_START(id) class_>(#id) + +#define DELEGATE_FIELD_CASE_END(id) ; + +#define DELEGATE_FIELD_MAIN_END + +#include "wasm-delegations-fields.def" + + // Module-level constructs. + + class_("Named").property("name", &Named::name); + + class_("Importable") + .property("module", &Importable::module) + .property("base", &Importable::base); + + class_("Function"); + + value_object("NameType") + .field("name", &NameType::name) + .field("type", &NameType::type); + register_vector("NameTypeVec"); + + class_("Builder") + .constructor() + .class_function("makeFunction", + select_overload( + Name, HeapType, std::vector && vars, Expression*)>( + &Builder::makeFunction), + allow_raw_pointers()) + + // Create constructors for all classes. +//#define DELEGATE(id) \ +// .function("make" #id, &Builder::make##id, allow_raw_pointers()) +//#include "wasm-delegations.def" +// The above can't naively work. Nop will, on the one hand: +.function("make" "Nop", &Builder::makeNop, allow_raw_pointers()) +// but Block has overloads, and we'd need to manually write one out, like this, +// which avoids fully automating things. +.function("make" "Block", +[](Builder& builder) { return builder.makeBlock(..); }, allow_raw_pointers()) + ; + + class_("Module") + .smart_ptr_constructor("Module", &std::make_shared) + .property("start", &Module::start) + .function("getFunction", &Module::getFunction, allow_raw_pointers()) + .function( + "getFunctionOrNull", &Module::getFunctionOrNull, allow_raw_pointers()) + .function("addFunction", + select_overload(&Module::addFunction), + allow_raw_pointers()); +} diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 623ccbd397e..ab8b6aac96e 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -592,7 +592,7 @@ DELEGATE_FIELD_CASE_START(Pop) DELEGATE_FIELD_CASE_END(Pop) DELEGATE_FIELD_CASE_START(TupleMake) -DELEGATE_FIELD_CHILD_VECTOR(Tuple, operands) +DELEGATE_FIELD_CHILD_VECTOR(TupleMake, operands) DELEGATE_FIELD_CASE_END(TupleMake) DELEGATE_FIELD_CASE_START(TupleExtract) diff --git a/src/wasm-delegations.def b/src/wasm-delegations.def index eb24feb7b83..413901eb70b 100644 --- a/src/wasm-delegations.def +++ b/src/wasm-delegations.def @@ -1,7 +1,7 @@ /* * Copyright 2020 WebAssembly Community Group participants * - * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License") * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * @@ -14,98 +14,98 @@ * limitations under the License. */ -DELEGATE(Nop); -DELEGATE(Block); -DELEGATE(If); -DELEGATE(Loop); -DELEGATE(Break); -DELEGATE(Switch); -DELEGATE(Call); -DELEGATE(CallIndirect); -DELEGATE(LocalGet); -DELEGATE(LocalSet); -DELEGATE(GlobalGet); -DELEGATE(GlobalSet); -DELEGATE(Load); -DELEGATE(Store); -DELEGATE(AtomicRMW); -DELEGATE(AtomicCmpxchg); -DELEGATE(AtomicWait); -DELEGATE(AtomicNotify); -DELEGATE(AtomicFence); -DELEGATE(SIMDExtract); -DELEGATE(SIMDReplace); -DELEGATE(SIMDShuffle); -DELEGATE(SIMDTernary); -DELEGATE(SIMDShift); -DELEGATE(SIMDLoad); -DELEGATE(SIMDLoadStoreLane); -DELEGATE(MemoryInit); -DELEGATE(DataDrop); -DELEGATE(MemoryCopy); -DELEGATE(MemoryFill); -DELEGATE(Const); -DELEGATE(Unary); -DELEGATE(Binary); -DELEGATE(Select); -DELEGATE(Drop); -DELEGATE(Return); -DELEGATE(MemorySize); -DELEGATE(MemoryGrow); -DELEGATE(Unreachable); -DELEGATE(Pop); -DELEGATE(RefNull); -DELEGATE(RefIsNull); -DELEGATE(RefFunc); -DELEGATE(RefEq); -DELEGATE(TableGet); -DELEGATE(TableSet); -DELEGATE(TableSize); -DELEGATE(TableGrow); -DELEGATE(TableFill); -DELEGATE(TableCopy); -DELEGATE(TableInit); -DELEGATE(Try); -DELEGATE(TryTable); -DELEGATE(Throw); -DELEGATE(Rethrow); -DELEGATE(ThrowRef); -DELEGATE(TupleMake); -DELEGATE(TupleExtract); -DELEGATE(RefI31); -DELEGATE(I31Get); -DELEGATE(CallRef); -DELEGATE(RefTest); -DELEGATE(RefCast); -DELEGATE(BrOn); -DELEGATE(StructNew); -DELEGATE(StructGet); -DELEGATE(StructSet); -DELEGATE(StructRMW); -DELEGATE(StructCmpxchg); -DELEGATE(ArrayNew); -DELEGATE(ArrayNewData); -DELEGATE(ArrayNewElem); -DELEGATE(ArrayNewFixed); -DELEGATE(ArrayGet); -DELEGATE(ArraySet); -DELEGATE(ArrayLen); -DELEGATE(ArrayCopy); -DELEGATE(ArrayFill); -DELEGATE(ArrayInitData); -DELEGATE(ArrayInitElem); -DELEGATE(RefAs); -DELEGATE(StringNew); -DELEGATE(StringConst); -DELEGATE(StringMeasure); -DELEGATE(StringEncode); -DELEGATE(StringConcat); -DELEGATE(StringEq); -DELEGATE(StringWTF16Get); -DELEGATE(StringSliceWTF); -DELEGATE(ContBind); -DELEGATE(ContNew); -DELEGATE(Resume); -DELEGATE(Suspend); +DELEGATE(Nop) +DELEGATE(Block) +DELEGATE(If) +DELEGATE(Loop) +DELEGATE(Break) +DELEGATE(Switch) +DELEGATE(Call) +DELEGATE(CallIndirect) +DELEGATE(LocalGet) +DELEGATE(LocalSet) +DELEGATE(GlobalGet) +DELEGATE(GlobalSet) +DELEGATE(Load) +DELEGATE(Store) +DELEGATE(AtomicRMW) +DELEGATE(AtomicCmpxchg) +DELEGATE(AtomicWait) +DELEGATE(AtomicNotify) +DELEGATE(AtomicFence) +DELEGATE(SIMDExtract) +DELEGATE(SIMDReplace) +DELEGATE(SIMDShuffle) +DELEGATE(SIMDTernary) +DELEGATE(SIMDShift) +DELEGATE(SIMDLoad) +DELEGATE(SIMDLoadStoreLane) +DELEGATE(MemoryInit) +DELEGATE(DataDrop) +DELEGATE(MemoryCopy) +DELEGATE(MemoryFill) +DELEGATE(Const) +DELEGATE(Unary) +DELEGATE(Binary) +DELEGATE(Select) +DELEGATE(Drop) +DELEGATE(Return) +DELEGATE(MemorySize) +DELEGATE(MemoryGrow) +DELEGATE(Unreachable) +DELEGATE(Pop) +DELEGATE(RefNull) +DELEGATE(RefIsNull) +DELEGATE(RefFunc) +DELEGATE(RefEq) +DELEGATE(TableGet) +DELEGATE(TableSet) +DELEGATE(TableSize) +DELEGATE(TableGrow) +DELEGATE(TableFill) +DELEGATE(TableCopy) +DELEGATE(TableInit) +DELEGATE(Try) +DELEGATE(TryTable) +DELEGATE(Throw) +DELEGATE(Rethrow) +DELEGATE(ThrowRef) +DELEGATE(TupleMake) +DELEGATE(TupleExtract) +DELEGATE(RefI31) +DELEGATE(I31Get) +DELEGATE(CallRef) +DELEGATE(RefTest) +DELEGATE(RefCast) +DELEGATE(BrOn) +DELEGATE(StructNew) +DELEGATE(StructGet) +DELEGATE(StructSet) +DELEGATE(StructRMW) +DELEGATE(StructCmpxchg) +DELEGATE(ArrayNew) +DELEGATE(ArrayNewData) +DELEGATE(ArrayNewElem) +DELEGATE(ArrayNewFixed) +DELEGATE(ArrayGet) +DELEGATE(ArraySet) +DELEGATE(ArrayLen) +DELEGATE(ArrayCopy) +DELEGATE(ArrayFill) +DELEGATE(ArrayInitData) +DELEGATE(ArrayInitElem) +DELEGATE(RefAs) +DELEGATE(StringNew) +DELEGATE(StringConst) +DELEGATE(StringMeasure) +DELEGATE(StringEncode) +DELEGATE(StringConcat) +DELEGATE(StringEq) +DELEGATE(StringWTF16Get) +DELEGATE(StringSliceWTF) +DELEGATE(ContBind) +DELEGATE(ContNew) +DELEGATE(Resume) +DELEGATE(Suspend) #undef DELEGATE diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index db9d1c1c4f9..6f610cd91a8 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -62,7 +62,7 @@ template struct Visitor { #define DELEGATE(CLASS_TO_VISIT) \ case Expression::Id::CLASS_TO_VISIT##Id: \ return static_cast(this)->visit##CLASS_TO_VISIT( \ - static_cast(curr)) + static_cast(curr)); #include "wasm-delegations.def" diff --git a/test/binaryen-embind.js/basics.js b/test/binaryen-embind.js/basics.js new file mode 100644 index 00000000000..49963768259 --- /dev/null +++ b/test/binaryen-embind.js/basics.js @@ -0,0 +1,66 @@ +const assert = require('node:assert'); +const argv = require('node:process'); + +const binaryen_js_path = argv.argv[2]; + +// Compare after normalizing text by removing extra indentation. +function normEqual(x, y) { + function norm(a) { + a = a.replace(/^\n */g, ''); + a = a.replace(/\n +/g, '\n'); + return a; + } + assert.equal(norm(x), norm(y)); +} + +const binaryenFactory = require(binaryen_js_path); + +binaryenFactory().then((binaryen) => { + // Create a Module. + const module = new binaryen.Module(); + + // Check it prints out as empty. + normEqual(binaryen.stringify(module), + ` + (module + ) + `); + + const builder = new binaryen.Builder(module); + + // Generate a function and everything we need for that. + const i32 = new binaryen.Type(binaryen.BasicType.i32); + const f64 = new binaryen.Type(binaryen.BasicType.f64); + const sig = { + params: i32, + results: i32 + }; + const func_ii = new binaryen.HeapType(sig); + const vars = new binaryen.TypeVec(); + vars.push_back(f64); + const func = binaryen.Builder.makeFunction( + new binaryen.Name("foo"), + func_ii, + vars, + builder.makeBinary(10, builder.makeNop(), builder.makeNop()) + ); + module.addFunction(func); + + // Check the function prints out ok. + normEqual(binaryen.stringify(module), + ` + (module + (type $0 (func (param i32) (result i32))) + (func $foo (param $0 i32) (result i32) + (local $1 f64) + (nop) + ) + ) + `); + + // Clean up. TODO: |new HeapType| etc. above are leaked atm. + module.delete(); + + console.log('success.'); +}); +