Skip to content

Embind port #7239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 38 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
1 change: 1 addition & 0 deletions check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
])
Expand Down
6 changes: 6 additions & 0 deletions scripts/emcc-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions scripts/test/binaryenjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

1 change: 1 addition & 0 deletions scripts/test/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
167 changes: 167 additions & 0 deletions src/binaryen-embind.cpp
Original file line number Diff line number Diff line change
@@ -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 <emscripten/bind.h>

#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_<Type::BasicType>("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>("Type")
.constructor<Type::BasicType>()
.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<Type>("TypeVec");

value_object<Signature>("Signature")
.field("params", &Signature::params)
.field("results", &Signature::results);

class_<HeapType>("HeapType").constructor<Signature>();

class_<Name>("Name").constructor<const std::string&>();

class_<Expression>("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, base<Expression>>(#id)

#define DELEGATE_FIELD_CASE_END(id) ;

#define DELEGATE_FIELD_MAIN_END

#include "wasm-delegations-fields.def"

// Module-level constructs.

class_<Named>("Named").property("name", &Named::name);

class_<Importable>("Importable")
.property("module", &Importable::module)
.property("base", &Importable::base);

class_<Function>("Function");

value_object<NameType>("NameType")
.field("name", &NameType::name)
.field("type", &NameType::type);
register_vector<NameType>("NameTypeVec");

class_<Builder>("Builder")
.constructor<Module&>()
.class_function("makeFunction",
select_overload<std::unique_ptr<Function>(
Name, HeapType, std::vector<Type> && 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>("Module")
.smart_ptr_constructor("Module", &std::make_shared<Module>)
.property("start", &Module::start)
.function("getFunction", &Module::getFunction, allow_raw_pointers())
.function(
"getFunctionOrNull", &Module::getFunctionOrNull, allow_raw_pointers())
.function("addFunction",
select_overload<Function*(Function*)>(&Module::addFunction),
allow_raw_pointers());
}
2 changes: 1 addition & 1 deletion src/wasm-delegations-fields.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading