Skip to content
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

build for WedAssembly #55

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ option(EAR_PACKAGE_AND_INSTALL "Package and install libear" ${IS_ROOT_PROJECT})
option(EAR_USE_INTERNAL_EIGEN "should we use our own version of Eigen, or find one with find_package?" TRUE)
option(EAR_USE_INTERNAL_XSIMD "should we use our own version of xsimd, or find one with find_package?" TRUE)
option(EAR_SIMD "try to use SIMD extensions" TRUE)
option(EAR_NO_EXCEPTIONS "abort instead of throwing exceptions" FALSE)
set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries")
set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables")
set(INSTALL_INCLUDE_DIR include CACHE PATH "Installation directory for header files")
Expand Down Expand Up @@ -102,6 +103,7 @@ add_feature_info(EAR_PACKAGE_AND_INSTALL ${EAR_PACKAGE_AND_INSTALL} "Package and
add_feature_info(EAR_USE_INTERNAL_EIGEN ${EAR_USE_INTERNAL_EIGEN} "use internal version of Eigen")
add_feature_info(EAR_USE_INTERNAL_XSIMD ${EAR_USE_INTERNAL_XSIMD} "use internal version of xsimd")
add_feature_info(EAR_SIMD ${EAR_SIMD} "try to use SIMD extensions")
add_feature_info(EAR_NO_EXCEPTIONS ${EAR_NO_EXCEPTIONS} "abort instead of throwing exceptions")
feature_summary(WHAT ALL)

if(EAR_PACKAGE_AND_INSTALL)
Expand Down
67 changes: 67 additions & 0 deletions cmake/wasm.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# determine export flags to pass when linking a wasm module

# we would like this to be as small as possible, but not all functions are
# needed all the time, so groups of exports are defined which can be set with
# EAR_WASM_EXPORT_GROUPS

# wasm_add_export adds a function name the wasm_exports_[group] lists, which are
# then combined into one list (wasm_exports) and ultimately a list of flags
# (wasm_export_flags)

# it would be possible to put this in the source instead, but would require some
# tricky macros

macro(wasm_add_export group name)
list(APPEND wasm_exports_${group} ${name})
list(APPEND wasm_exports_all ${name})

# append to wasm_export_groups if not present
list(FIND wasm_export_groups ${group} wasm_has_group)
if(wasm_has_group EQUAL -1)
list(APPEND wasm_export_groups ${group})
endif()
endmacro()

# for all functions in c_api.h, add a call here with a group to put it in

wasm_add_export(objects ear_objects_type_metadata_new)
wasm_add_export(objects ear_objects_type_metadata_free)
wasm_add_export(objects ear_objects_type_metadata_reset)
wasm_add_export(objects ear_objects_type_metadata_set_polar_position)
wasm_add_export(objects ear_objects_type_metadata_set_extent)
wasm_add_export(objects ear_objects_type_metadata_set_gain)
wasm_add_export(objects ear_objects_type_metadata_set_diffuse)

wasm_add_export(bs2051 ear_layout_get)
wasm_add_export(layout ear_layout_num_channels)
wasm_add_export(layout ear_layout_free)

wasm_add_export(objects ear_gain_calculator_objects_new)
wasm_add_export(objects ear_gain_calculator_objects_free)
wasm_add_export(objects ear_gain_calculator_objects_calc_gains)

wasm_add_export(decorrelators ear_decorrelator_compensation_delay)
wasm_add_export(decorrelators ear_design_decorrelator)

set(EAR_WASM_EXPORT_GROUPS
"all"
CACHE
STRING
"groups of wasm functions to export; options: ${wasm_export_groups} or all"
)

# always required
set(wasm_exports malloc free ear_init)

foreach(group IN LISTS EAR_WASM_EXPORT_GROUPS)
foreach(export IN LISTS wasm_exports_${group})
# append to wasm_exports if not present
list(FIND wasm_exports ${export} wasm_has_export)
if(wasm_has_export EQUAL -1)
list(APPEND wasm_exports ${export})
endif()
endforeach()
endforeach()

list(TRANSFORM wasm_exports PREPEND "-Wl,--export=" OUTPUT_VARIABLE
wasm_export_flags)
2 changes: 1 addition & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
add_executable(objects_gains objects_gains.cpp)
target_link_libraries(objects_gains PRIVATE ear)
target_link_libraries(objects_gains PRIVATE ear ear_handle_exceptions)
30 changes: 29 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = nixpkgs.legacyPackages.${system};
pkgs = import nixpkgs {
system = system;
config.allowUnsupportedSystem = true; # for eigen wasm
};
pkgs_wasm = pkgs.pkgsCross.wasi32;

devtools = [
pkgs.clang-tools
Expand All @@ -33,6 +37,30 @@
nativeBuildInputs = attrs.nativeBuildInputs ++ devtools;
});
devShells.default = devShells.libear;

# wasm versions of packages

packages.xsimd_wasm = pkgs_wasm.xsimd.overrideAttrs {
# this should be an overlay
version = pkgs.xsimd.version;
src = pkgs.xsimd.src;
};
packages.libear_wasm = (pkgs_wasm.callPackage ./nix/libear.nix {
src = ./.;
boost = pkgs.boost; # doesn't build for wasm, but we only use headers, so use system version
xsimd = packages.xsimd_wasm;
}).overrideAttrs (super: {
cmakeBuildType = "MinSizeRel";
});

devShells.libear_wasm = packages.libear_wasm.overrideAttrs (attrs: {
nativeBuildInputs = attrs.nativeBuildInputs ++ devtools ++ [
pkgs.wabt
pkgs.wasmtime
pkgs.nodejs
pkgs.nodePackages.prettier
];
});
}
);
}
Expand Down
93 changes: 93 additions & 0 deletions include/ear/c_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include <stddef.h>
#include "export.hpp"

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*ear_warning_cb)(const char *warning);

//////////////////////
// ObjectsTypeMetadata
//////////////////////

typedef struct ear_objects_type_metadata ear_objects_type_metadata;

EAR_EXPORT
ear_objects_type_metadata *ear_objects_type_metadata_new();
EAR_EXPORT
void ear_objects_type_metadata_free(ear_objects_type_metadata **);
EAR_EXPORT
void ear_objects_type_metadata_reset(ear_objects_type_metadata *);

EAR_EXPORT
void ear_objects_type_metadata_set_polar_position(ear_objects_type_metadata *,
double azimuth,
double elevation,
double distance);
EAR_EXPORT
void ear_objects_type_metadata_set_extent(ear_objects_type_metadata *,
double width, double height,
double depth);
EAR_EXPORT
void ear_objects_type_metadata_set_gain(ear_objects_type_metadata *,
double gain);
EAR_EXPORT
void ear_objects_type_metadata_set_diffuse(ear_objects_type_metadata *,
double diffuse);

/////////
// Layout
/////////

typedef struct ear_layout ear_layout;

/// get a layout by its BS.2051 name (e.g. 4+5+0); returns null if there is no
/// layout with the given name
EAR_EXPORT
ear_layout *ear_layout_get(const char *name);
EAR_EXPORT
void ear_layout_free(ear_layout **);

EAR_EXPORT
size_t ear_layout_num_channels(ear_layout *);

////////////////////////
// GainCalculatorObjects
////////////////////////

typedef struct ear_gain_calculator_objects ear_gain_calculator_objects;

EAR_EXPORT
ear_gain_calculator_objects *ear_gain_calculator_objects_new(ear_layout *);
EAR_EXPORT
void ear_gain_calculator_objects_free(ear_gain_calculator_objects **);
EAR_EXPORT
void ear_gain_calculator_objects_calc_gains(ear_gain_calculator_objects *,
ear_objects_type_metadata *,
size_t n_gains,
double *direct_gains,
double *diffuse_gains);
#ifndef __wasm__
EAR_EXPORT
void ear_gain_calculator_objects_calc_gains_cb(
ear_gain_calculator_objects *, ear_objects_type_metadata *, size_t n_gains,
double *direct_gains, double *diffuse_gains, ear_warning_cb warning_cb);
#endif

////////////////
// decorrelators
////////////////

EAR_EXPORT int ear_decorrelator_compensation_delay();

/// design a decorrelation filter for channel_idx in layout
///
/// the number of samples will be written to *length; the result should be
/// freed using free()
EAR_EXPORT float *ear_design_decorrelator(ear_layout *layout,
size_t channel_idx, size_t *length);

#ifdef __cplusplus
}
#endif
2 changes: 1 addition & 1 deletion include/ear/dsp/gain_interpolator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ namespace ear {
while (cmp != 0) {
last_block += cmp;
if (cmp != first_cmp)
throw invalid_argument("interpolation points are not sorted");
ear_throw(invalid_argument("interpolation points are not sorted"));
cmp = block_cmp(last_block, sample_idx);
}

Expand Down
32 changes: 32 additions & 0 deletions include/ear/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,40 @@
#include "export.hpp"

namespace ear {

/// support for building without exceptions
///
/// in the rest of the code, the ear_throw macro is used to throw exceptions,
/// and normally this just throws the given exception
///
/// when exceptions are disabled, it instead calls ear::handle_exception with
/// the exception object, and can be provided by users of the library
///
/// this callback must return out of the library (e.g. by abort or longjmp),
/// and you can't assume that the library is in a reasonable state after this
/// has happened, because clean-up that would normally be done during unwinding
/// will not happen
///
/// this is really unfortunate, but the library was designed with use of C++
/// exceptions in mind, so a lot of code would need to change to fix this
///
/// note that boost has a similar system which requires defining
/// boost::throw_exception
///
/// an example of both is provided in src/handle_exceptions.cpp, which is used
/// to build examples and tests
#ifdef EAR_NO_EXCEPTIONS

[[noreturn]] void handle_exception(const std::exception &e);

#define ear_throw(exc) ::ear::handle_exception(exc);

#else
#define ear_throw(exc) throw exc
#endif

/// thrown if features are used which are not yet implemented
class EAR_EXPORT not_implemented : public std::runtime_error {

Check warning on line 40 in include/ear/exceptions.hpp

View workflow job for this annotation

GitHub Actions / windows-latest shared=true Release

non dll-interface class 'std::runtime_error' used as base for dll-interface class 'ear::not_implemented'
public:
explicit not_implemented(const std::string &what)
: std::runtime_error("not implemented: " + what) {}
Expand All @@ -15,28 +47,28 @@
/// any inputs. This can be caused by an error in the library itself (please
/// report it!) or something going wrong while building the library. This is
/// thrown by ear_assert.
class EAR_EXPORT internal_error : public std::runtime_error {

Check warning on line 50 in include/ear/exceptions.hpp

View workflow job for this annotation

GitHub Actions / windows-latest shared=true Release

non dll-interface class 'std::runtime_error' used as base for dll-interface class 'ear::internal_error'
public:
explicit internal_error(const std::string &what)
: std::runtime_error("internal error: " + what) {}
};

/// thrown if invalid ADM metadata is encountered
class EAR_EXPORT adm_error : public std::invalid_argument {

Check warning on line 57 in include/ear/exceptions.hpp

View workflow job for this annotation

GitHub Actions / windows-latest shared=true Release

non dll-interface class 'std::invalid_argument' used as base for dll-interface class 'ear::adm_error'
public:
explicit adm_error(const std::string &what)
: std::invalid_argument("ADM error: " + what) {}
};

/// thrown if an unknown loudspeaker layout is requested
class EAR_EXPORT unknown_layout : public std::invalid_argument {

Check warning on line 64 in include/ear/exceptions.hpp

View workflow job for this annotation

GitHub Actions / windows-latest shared=true Release

non dll-interface class 'std::invalid_argument' used as base for dll-interface class 'ear::unknown_layout'
public:
explicit unknown_layout(const std::string &what)
: std::invalid_argument("unknown layout: " + what) {}
};

/// thrown if other invariants on parameters are not met
class EAR_EXPORT invalid_argument : public std::invalid_argument {

Check warning on line 71 in include/ear/exceptions.hpp

View workflow job for this annotation

GitHub Actions / windows-latest shared=true Release

non dll-interface class 'std::invalid_argument' used as base for dll-interface class 'ear::invalid_argument'
public:
explicit invalid_argument(const std::string &what)
: std::invalid_argument(what) {}
Expand Down
4 changes: 2 additions & 2 deletions include/ear/helpers/assert.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ namespace ear {
// implementation for _assert_impl. This is wrapped in a macro so that we can
// use __LINE__, __FILE__ etc. in the future.
inline void _assert_impl(bool condition, const char *message) {
if (!condition) throw internal_error(message);
if (!condition) ear_throw(internal_error(message));
}

inline void _assert_impl(bool condition, const std::string &message) {
if (!condition) throw internal_error(message);
if (!condition) ear_throw(internal_error(message));
}
} // namespace ear

Expand Down
9 changes: 5 additions & 4 deletions include/ear/helpers/output_gains.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace ear {
OutputGainsT(std::vector<T> &vec) : vec(vec) {}
virtual void check_size(size_t n) override {
if (vec.size() != n) {
throw invalid_argument("incorrect size for output vector");
ear_throw(invalid_argument("incorrect size for output vector"));
}
}
virtual size_t size() override { return vec.size(); }
Expand Down Expand Up @@ -84,11 +84,12 @@ namespace ear {
OutputGainMatVecT(std::vector<std::vector<T>> &mat) : mat(mat) {}
virtual void check_size(size_t rows, size_t cols) override {
if (mat.size() != cols)
throw invalid_argument("incorrect number of cols in output matrix");
ear_throw(
invalid_argument("incorrect number of cols in output matrix"));
for (auto &col : mat)
if (col.size() != rows)
throw invalid_argument(
"incorrect number of rows in output matrix column");
ear_throw(invalid_argument(
"incorrect number of rows in output matrix column"));
}
virtual size_t rows() override { return mat[0].size(); }
virtual size_t cols() override { return mat.size(); }
Expand Down
20 changes: 19 additions & 1 deletion nix/libear.nix
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
{ lib, buildPackages, stdenv, cmake, src, ninja, boost, eigen, xsimd }:
{ lib, buildPackages, stdenv, cmake, src, ninja, boost, eigen, xsimd, binaryen, nodejs }:
let
isCross = stdenv.buildPlatform != stdenv.hostPlatform;
isWasm = stdenv.hostPlatform.isWasm;
in
(stdenv.mkDerivation {
name = "libear";
inherit src;
nativeBuildInputs = [
cmake
ninja
] ++ lib.optionals isWasm [
binaryen # gets used automatically by clang-ld if in path
nodejs
];

buildInputs = [ boost eigen xsimd ];
cmakeFlags = [
"-DEAR_USE_INTERNAL_EIGEN=OFF"
Expand All @@ -17,7 +22,20 @@ in
]
++ lib.optionals isCross [
"-DCMAKE_CROSSCOMPILING_EMULATOR=${stdenv.hostPlatform.emulator buildPackages}"
] ++ lib.optionals isWasm [
"-DEAR_NO_EXCEPTIONS=ON"
];


doCheck = true;

preConfigure = lib.optionalString isWasm ''
# for wasmtime cache
HOME=$(pwd)
# forced off in make-derivation.nix when build platform can't execute host
# platform, but we have an emulator
doCheck=1
'';

env.NIX_CFLAGS_COMPILE = lib.optionalString isWasm "-DEIGEN_HAS_CXX11_ATOMIC=0 -DCATCH_CONFIG_NO_POSIX_SIGNALS";
})
Loading
Loading