chore: remove wasm32-unknown-unknown support

This commit is contained in:
Carson M.
2024-08-13 09:11:30 -05:00
parent 08aaa0ffab
commit e2c454941b
14 changed files with 24 additions and 433 deletions

View File

@@ -53,19 +53,6 @@ jobs:
cargo test -p ort --verbose --features fetch-models -- --test-threads 1
# Test examples that use in-tree graphs (do NOT run any of the examples that download ~700 MB graphs from pyke parcel...)
cargo run --example custom-ops
test-wasm:
name: Test WebAssembly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install nightly Rust toolchain
uses: dtolnay/rust-toolchain@nightly
- name: Install wasm-pack
run: cargo install wasm-pack
- name: Run tests
working-directory: examples/webassembly
run: |
wasm-pack test --node
# Disable cross-compile until cross updates aarch64-unknown-linux-gnu to Ubuntu 22.04
# ref https://github.com/cross-rs/cross/pull/973
#cross-compile:

1
.gitignore vendored
View File

@@ -187,7 +187,6 @@ WixTools/
**/*.onnx
**/*.ort
**/*.pbseq
!examples/webassembly/**/*.ort
!tests/data/*.onnx
!tests/data/*.ort

View File

@@ -8,8 +8,7 @@ members = [
'examples/yolov8',
'examples/modnet',
'examples/sentence-transformers',
'examples/training',
'examples/webassembly'
'examples/training'
]
default-members = [
'.',
@@ -49,7 +48,7 @@ codegen-units = 1
[package.metadata.docs.rs]
features = [ "ndarray", "half", "training", "operator-libraries", "fetch-models", "load-dynamic", "copy-dylibs" ]
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
targets = ["x86_64-unknown-linux-gnu"]
rustdoc-args = [ "--cfg", "docsrs" ]
[features]
@@ -99,10 +98,6 @@ libc = { version = "0.2", optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true, features = [ "std", "libloaderapi" ] }
[target.'cfg(target_family = "wasm")'.dependencies]
js-sys = "0.3"
web-sys = "0.3"
[dev-dependencies]
anyhow = "1.0"
ureq = "2.1"
@@ -112,7 +107,6 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [ "
glassbench = "0.4"
tokio = { version = "1.36", features = [ "test-util" ] }
tokio-test = "0.4.3"
wasm-bindgen-test = "0.3"
[[bench]]
name = "squeezenet"

View File

@@ -12,21 +12,19 @@ Here are the supported platforms and binary availability status, as of v2.0.0-rc
* ⭕ - Supported. Precompiled binaries not available.
* ❌ - Not supported.
| Platform | x86 | x86-64 | ARMv7 | ARM64 | WASM32 |
|:-------- |:------- |:------ |:------ |:------ |:------ |
| **Windows** | ⭕ | 🟢<sup>\*</sup> | ⭕ | 🔷<sup>\*</sup> | ❌ |
| **Linux** | ⭕ | 🟢† | ⭕ | 🔷‡ | ❌ |
| **macOS** | ❌ | 🔷§ | ❌ | 🔷 | ❌ |
| **iOS** | ❌ | ❌ | ❌ | ⭕ | ❌ |
| **Android** | ❌ | ❌ | ⭕ | ⭕ | ❌ |
| **Web** | ❌ | ❌ | ❌ | ❌ | 🔷¶ |
| Platform | x86 | x86-64 | ARMv7 | ARM64 |
|:-------- |:------- |:------ |:------ |:------ |
| **Windows** | ⭕ | 🟢<sup>\*</sup> | ⭕ | 🔷<sup>\*</sup> |
| **Linux** | ⭕ | 🟢† | ⭕ | 🔷‡ |
| **macOS** | ❌ | 🔷§ | ❌ | 🔷 |
| **iOS** | ❌ | ❌ | ❌ | ⭕ |
| **Android** | ❌ | ❌ | ⭕ | ⭕ |
<div style={{ opacity: 0.5, display: 'flex', flexDirection: 'column', fontSize: '0.75rem' }}>
<p>\* A recent version of Windows 10/11 & Visual Studio 2022 are required for pyke binaries.</p>
<p>† glibc ≥ 2.31 (Ubuntu ≥ 20.04) required for pyke binaries.</p>
<p>‡ glibc ≥ 2.35 (Ubuntu ≥ 22.04) required for pyke binaries.</p>
<p>§ macOS ≥ 10.15 required.</p>
<p>¶ WASM supports a limited subset of ONNX Runtime features. For more info, see [the docs on WebAssembly support](/setup/webassembly).</p>
</div>
If your platform is marked as 🟢 or 🔷, you're in luck! Almost no setup will be required to get `ort` up and running.

View File

@@ -1,26 +0,0 @@
---
title: WebAssembly
description: Deploy ONNX models to the web
---
WebAssembly support in `ort` is currently experimental. If you experience any issues using `ort` in WebAssembly, please [open an issue](https://github.com/pykeio/ort/issues/new).
By nature, some features of ONNX Runtime are not available in the web. These include:
- **Support for `.onnx` models.** You instead need to [convert `.onnx` models to the `.ort` format](https://onnxruntime.ai/docs/performance/model-optimizations/ort-format-models.html).
- **Runtime graph optimizations**, aka `SessionBuilder::with_optimization_level`. You can statically optimize the graph using the `.ort` conversion tool, though.
- **Loading models with `commit_from_file`/`commit_from_url`.** You can create models from a slice of bytes in memory with `SessionBuilder::commit_from_memory` or `SessionBuilder::commit_from_memory_directly`.
Additionally, you'll need to call `ort::wasm::initialize()` at the earliest possible point in your code, before you use any `ort` APIs:
```rust filename="main.rs" copy
use ort::Session;
static MODEL_BYTES: &[u8] = include_bytes!("../model.ort");
fn main() -> anyhow::Result<()> {
#[cfg(target_arch = "wasm32")]
ort::wasm::initialize();
let session = Session::builder()?.commit_from_memory_directly(MODEL_BYTES)?;
...
}
```

View File

@@ -1,26 +0,0 @@
[package]
publish = false
name = "example-webassembly"
version = "0.0.0"
edition = "2021"
[lib]
name = "ortwasm"
crate-type = ["cdylib"]
[dependencies]
ort = { path = "../../" }
ndarray = "0.16"
wasm-bindgen = "0.2.92"
web-sys = "0.3"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-subscriber-wasm = "0.1"
image = { version = "0.25", default-features = false, features = [ "jpeg" ]}
[dev-dependencies]
wasm-bindgen-test = "0.3"
console_error_panic_hook = "0.1"
[features]
load-dynamic = [ "ort/load-dynamic" ]

View File

@@ -1,75 +0,0 @@
use image::{ImageBuffer, Luma, Pixel};
use ort::{ArrayExtensions, Session};
use wasm_bindgen::prelude::*;
static IMAGE_BYTES: &[u8] = include_bytes!("../../../tests/data/mnist_5.jpg");
static MODEL_BYTES: &[u8] = include_bytes!("mnist.ort");
pub fn upsample_inner() -> ort::Result<()> {
let session = Session::builder()?
.commit_from_memory_directly(MODEL_BYTES)
.expect("Could not read model from memory");
// NOTE: An earlier nightly version of Rust 1.78 includes a patch required for ONNX Runtime to link properly, but a
// later version enables debug assertions in `dlmalloc`, which surfaces an allocation bug in the `image` crate:
// https://github.com/rustwasm/wasm-pack/issues/1389 Because of this, using `image::load_from_memory` crashes.
// For demonstration purposes, we're replacing the image loading code shown below with zeros(). In a real application,
// you can get the image from another source, like an HTML canvas.
//
// let image_buffer: ImageBuffer<Luma<u8>, Vec<u8>> = image::load_from_memory(IMAGE_BYTES).unwrap().to_luma8();
// let array = ndarray::Array::from_shape_fn((1, 1, 28, 28), |(_, c, j, i)| {
// let pixel = image_buffer.get_pixel(i as u32, j as u32);
// let channels = pixel.channels();
// (channels[c] as f32) / 255.0
// });
let array = ndarray::Array4::<f32>::zeros((1, 1, 28, 28));
let outputs = session.run(ort::inputs![array]?)?;
let mut probabilities: Vec<(usize, f32)> = outputs[0]
.try_extract_tensor()?
.softmax(ndarray::Axis(1))
.iter()
.copied()
.enumerate()
.collect::<Vec<_>>();
probabilities.sort_unstable_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
assert_eq!(probabilities[0].0, 5, "Expecting class '5' (got {})", probabilities[0].0);
Ok(())
}
macro_rules! console_log {
($($t:tt)*) => (web_sys::console::log_1(&format_args!($($t)*).to_string().into()))
}
#[wasm_bindgen]
pub fn upsample() {
if let Err(e) = upsample_inner() {
console_log!("Error occurred while performing inference: {e:?}");
}
}
#[cfg(test)]
#[wasm_bindgen_test::wasm_bindgen_test]
fn run_test() {
use tracing::Level;
use tracing_subscriber::fmt;
use tracing_subscriber_wasm::MakeConsoleWriter;
#[cfg(target_arch = "wasm32")]
ort::wasm::initialize();
fmt()
.with_ansi(false)
.with_max_level(Level::DEBUG)
.with_writer(MakeConsoleWriter::default().map_trace_level_to(Level::DEBUG))
.without_time()
.init();
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
upsample();
}

Binary file not shown.

View File

@@ -215,15 +215,6 @@ fn prepare_libort_dir() -> (PathBuf, bool) {
println!("cargo:rustc-link-lib=static=noexcep_operators");
}
if target_arch == "wasm32" {
for lib in &["webassembly", "providers_js"] {
let lib_path = lib_dir.join(platform_format_lib(&format!("onnxruntime_{lib}")));
if lib_path.exists() {
println!("cargo:rustc-link-lib=static=onnxruntime_{lib}");
}
}
}
let protobuf_build = transform_dep(external_lib_dir.join("protobuf-build"), &profile);
add_search_dir(&protobuf_build);
for lib in ["protobuf-lited", "protobuf-lite", "protobuf"] {
@@ -245,17 +236,15 @@ fn prepare_libort_dir() -> (PathBuf, bool) {
println!("cargo:rustc-link-lib=static=nsync_cpp");
}
if target_arch != "wasm32" {
add_search_dir(transform_dep(external_lib_dir.join("pytorch_cpuinfo-build"), &profile));
let clog_path = transform_dep(external_lib_dir.join("pytorch_cpuinfo-build").join("deps").join("clog"), &profile);
if clog_path.exists() {
add_search_dir(clog_path);
} else {
add_search_dir(transform_dep(external_lib_dir.join("pytorch_clog-build"), &profile));
}
println!("cargo:rustc-link-lib=static=cpuinfo");
println!("cargo:rustc-link-lib=static=clog");
add_search_dir(transform_dep(external_lib_dir.join("pytorch_cpuinfo-build"), &profile));
let clog_path = transform_dep(external_lib_dir.join("pytorch_cpuinfo-build").join("deps").join("clog"), &profile);
if clog_path.exists() {
add_search_dir(clog_path);
} else {
add_search_dir(transform_dep(external_lib_dir.join("pytorch_clog-build"), &profile));
}
println!("cargo:rustc-link-lib=static=cpuinfo");
println!("cargo:rustc-link-lib=static=clog");
add_search_dir(transform_dep(external_lib_dir.join("re2-build"), &profile));
println!("cargo:rustc-link-lib=static=re2");

View File

@@ -19,7 +19,7 @@ pub type ortchar = c_ushort;
#[cfg(not(target_os = "windows"))]
pub type ortchar = c_char;
#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "wasm32"))]
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
pub type size_t = usize;
#[cfg(all(target_arch = "aarch64", target_os = "windows"))]
pub type size_t = c_ulonglong;

View File

@@ -39,8 +39,7 @@ impl ExecutionProvider for XNNPACKExecutionProvider {
cfg!(any(
target_arch = "aarch64",
all(target_arch = "arm", any(target_os = "linux", target_os = "android")),
target_arch = "x86_64",
target_arch = "wasm32"
target_arch = "x86_64"
))
}

View File

@@ -27,9 +27,6 @@ pub(crate) mod tensor;
pub(crate) mod training;
pub(crate) mod util;
pub(crate) mod value;
#[cfg_attr(docsrs, doc(cfg(target_arch = "wasm32")))]
#[cfg(target_arch = "wasm32")]
pub mod wasm;
#[cfg(feature = "load-dynamic")]
use std::sync::Arc;

View File

@@ -2,11 +2,10 @@
use std::ffi::CString;
#[cfg(feature = "fetch-models")]
use std::fmt::Write;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use std::{
any::Any,
marker::PhantomData,
path::Path,
ptr::{self, NonNull},
rc::Rc,
sync::{atomic::Ordering, Arc}
@@ -162,8 +161,6 @@ impl SessionBuilder {
/// newly optimized model to the given path (for 'offline' graph optimization).
///
/// Note that the file will only be created after the model is committed.
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))]
pub fn with_optimized_model_path<S: AsRef<str>>(self, path: S) -> Result<Self> {
#[cfg(windows)]
let path = path.as_ref().encode_utf16().chain([0]).collect::<Vec<_>>();
@@ -175,8 +172,6 @@ impl SessionBuilder {
/// Enables profiling. Profile information will be writen to `profiling_file` after profiling completes.
/// See [`Session::end_profiling`].
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))]
pub fn with_profiling<S: AsRef<str>>(self, profiling_file: S) -> Result<Self> {
#[cfg(windows)]
let profiling_file = profiling_file.as_ref().encode_utf16().chain([0]).collect::<Vec<_>>();
@@ -205,8 +200,8 @@ impl SessionBuilder {
}
/// Registers a custom operator library at the given library path.
#[cfg(all(feature = "operator-libraries", not(target_arch = "wasm32")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "operator-libraries", not(target_arch = "wasm32")))))]
#[cfg(feature = "operator-libraries")]
#[cfg_attr(docsrs, doc(cfg(feature = "operator-libraries")))]
pub fn with_operator_library(mut self, lib_path: impl AsRef<str>) -> Result<Self> {
let path_cstr = CString::new(lib_path.as_ref())?;
@@ -232,8 +227,6 @@ impl SessionBuilder {
}
/// Enables [`onnxruntime-extensions`](https://github.com/microsoft/onnxruntime-extensions) custom operators.
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))]
pub fn with_extensions(self) -> Result<Self> {
let status = ortsys![unsafe EnableOrtCustomOps(self.session_options_ptr.as_ptr())];
status_to_result(status).map_err(Error::CreateSessionOptions)?;
@@ -248,8 +241,8 @@ impl SessionBuilder {
}
/// Downloads a pre-trained ONNX model from the given URL and builds the session.
#[cfg(all(feature = "fetch-models", not(target_arch = "wasm32")))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "fetch-models", not(target_arch = "wasm32")))))]
#[cfg(feature = "fetch-models")]
#[cfg_attr(docsrs, doc(cfg(feature = "fetch-models")))]
pub fn commit_from_url(self, model_url: impl AsRef<str>) -> Result<Session> {
let mut download_dir = ort_sys::internal::dirs::cache_dir()
.expect("could not determine cache directory")
@@ -299,8 +292,6 @@ impl SessionBuilder {
}
/// Loads an ONNX model from a file and builds the session.
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(docsrs, doc(cfg(not(target_arch = "wasm32"))))]
pub fn commit_from_file<P>(mut self, model_filepath_ref: P) -> Result<Session>
where
P: AsRef<Path>

View File

@@ -1,236 +0,0 @@
//! Utilities for using `ort` in WebAssembly.
//!
//! You **must** call `ort::wasm::initialize()` before using any `ort` APIs in WASM:
//! ```
//! # use ort::Session;
//! # static MODEL_BYTES: &[u8] = include_bytes!("../tests/data/upsample.ort");
//! # fn main() -> ort::Result<()> {
//! #[cfg(target_arch = "wasm32")]
//! ort::wasm::initialize();
//!
//! let session = Session::builder()?.commit_from_memory_directly(MODEL_BYTES)?;
//! # Ok(())
//! # }
//! ```
use std::{
alloc::{self, Layout},
arch::wasm32,
ptr, slice, str
};
mod fmt_shims {
// localized time string formatting functions
// TODO: remove any remaining codepaths to these
#[no_mangle]
pub unsafe extern "C" fn strftime_l(_s: *mut u8, _l: usize, _m: *const u8, _t: *const (), _lt: *const ()) -> usize {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn _tzset_js(_timezone: *mut u32, _daylight: *const i32, _name: *const u8, _dst_name: *mut u8) {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn _mktime_js(_tm: *mut ()) -> ! {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn _localtime_js(_time_t: i64, _tm: *mut ()) -> ! {
unimplemented!()
}
#[no_mangle]
pub unsafe extern "C" fn _gmtime_js(_time_t: i64, _tm: *mut ()) -> ! {
unimplemented!()
}
}
pub(crate) mod libc_shims {
use super::*;
// Rust, unlike C, requires us to know the exact layout of an allocation in order to deallocate it, so we need to
// store this data at the beginning of the allocation for us to be able to pick up on deallocation:
//
// ┌---- actual allocated pointer
// ▼
// +-------------+-------+------+----------------+
// | ...padding | align | size | data... |
// | -align..-8 | -8 | -4 | 0..size |
// +-------------+- -----+------+----------------+
// ▲
// pointer returned to C ---┘
//
// This does unfortunately mean we waste a little extra memory (note that most allocators *also* store the layout
// information in a similar manner, but we can't access it).
const _: () = assert!(std::mem::size_of::<usize>() == 4, "32-bit pointer width (wasm32) required");
unsafe fn alloc_inner<const ZERO: bool>(size: usize, align: usize) -> *mut u8 {
// need enough space to store the size & alignment bytes
let align = align.max(8);
let layout = Layout::from_size_align_unchecked(size + align, align);
let ptr = if ZERO { alloc::alloc_zeroed(layout) } else { alloc::alloc(layout) };
ptr::copy_nonoverlapping(size.to_le_bytes().as_ptr(), ptr.add(align - 4), 4);
ptr::copy_nonoverlapping(align.to_le_bytes().as_ptr(), ptr.add(align - 8), 4);
ptr.add(align)
}
unsafe fn free_inner(ptr: *mut u8) {
// something likes to free(NULL) a lot, which is valid in C (because of course it is...)
if ptr.is_null() {
return;
}
let size = usize::from_le_bytes(slice::from_raw_parts_mut(ptr.sub(4), 4).try_into().unwrap_unchecked());
let align = usize::from_le_bytes(slice::from_raw_parts_mut(ptr.sub(8), 4).try_into().unwrap_unchecked());
let layout = Layout::from_size_align_unchecked(size + align, align);
alloc::dealloc(ptr.sub(align), layout);
}
const DEFAULT_ALIGNMENT: usize = 32;
#[no_mangle]
pub unsafe extern "C" fn malloc(size: usize) -> *mut u8 {
alloc_inner::<false>(size, DEFAULT_ALIGNMENT)
}
#[no_mangle]
pub unsafe extern "C" fn __libc_malloc(size: usize) -> *mut u8 {
alloc_inner::<false>(size, DEFAULT_ALIGNMENT)
}
#[no_mangle]
pub unsafe extern "C" fn __libc_calloc(n: usize, size: usize) -> *mut u8 {
alloc_inner::<true>(size * n, DEFAULT_ALIGNMENT)
}
#[no_mangle]
pub unsafe extern "C" fn free(ptr: *mut u8) {
free_inner(ptr)
}
#[no_mangle]
pub unsafe extern "C" fn __libc_free(ptr: *mut u8) {
free_inner(ptr)
}
#[no_mangle]
pub unsafe extern "C" fn posix_memalign(ptr: *mut *mut u8, align: usize, size: usize) -> i32 {
*ptr = alloc_inner::<false>(size, align);
0
}
#[no_mangle]
pub unsafe extern "C" fn realloc(ptr: *mut u8, newsize: usize) -> *mut u8 {
let size = usize::from_le_bytes(slice::from_raw_parts_mut(ptr.sub(4), 4).try_into().unwrap_unchecked());
let align = usize::from_le_bytes(slice::from_raw_parts_mut(ptr.sub(8), 4).try_into().unwrap_unchecked());
let layout = Layout::from_size_align_unchecked(size + align, align);
let ptr = alloc::realloc(ptr.sub(align), layout, newsize);
ptr::copy_nonoverlapping(size.to_le_bytes().as_ptr(), ptr.add(align - 4), 4);
ptr.add(align)
}
#[no_mangle]
pub unsafe extern "C" fn abort() -> ! {
std::process::abort()
}
}
#[cfg(not(target_os = "wasi"))]
mod wasi_shims {
#[allow(non_camel_case_types)]
type __wasi_errno_t = u16;
const __WASI_ENOTSUP: __wasi_errno_t = 58;
// mock filesystem for non-WASI platforms - most of the codepaths to any FS operations should've been removed, but we
// return ENOTSUP just to be safe
#[no_mangle]
pub unsafe extern "C" fn __wasi_environ_sizes_get(argc: *mut usize, argv_buf_size: *mut usize) -> __wasi_errno_t {
*argc = 0;
*argv_buf_size = 0;
__WASI_ENOTSUP
}
#[no_mangle]
pub unsafe extern "C" fn __wasi_environ_get(_environ: *mut *mut u8, _buf: *mut u8) -> __wasi_errno_t {
__WASI_ENOTSUP
}
#[no_mangle]
pub unsafe extern "C" fn __wasi_fd_seek(_fd: u32, _offset: i64, _whence: u8, _new_offset: *mut u64) -> __wasi_errno_t {
__WASI_ENOTSUP
}
#[no_mangle]
pub unsafe extern "C" fn __wasi_fd_write(_fd: u32, _iovs: *const (), _iovs_len: usize, _nwritten: *mut usize) -> __wasi_errno_t {
__WASI_ENOTSUP
}
#[no_mangle]
pub unsafe extern "C" fn __wasi_fd_read(_fd: u32, _iovs: *const (), _iovs_len: usize, _nread: *mut usize) -> __wasi_errno_t {
__WASI_ENOTSUP
}
#[no_mangle]
pub unsafe extern "C" fn __wasi_fd_close(_fd: u32) -> __wasi_errno_t {
__WASI_ENOTSUP
}
}
mod emscripten_shims {
use super::*;
#[no_mangle]
pub unsafe extern "C" fn emscripten_memcpy_js(dst: *mut (), src: *const (), n: usize) {
std::ptr::copy_nonoverlapping(src, dst, n)
}
#[no_mangle]
pub unsafe extern "C" fn emscripten_get_now() -> f64 {
js_sys::Date::now()
}
#[no_mangle]
pub unsafe extern "C" fn emscripten_get_heap_max() -> usize {
wasm32::memory_size(0) << 16
}
#[no_mangle]
pub unsafe extern "C" fn emscripten_date_now() -> f64 {
js_sys::Date::now()
}
#[no_mangle]
pub unsafe extern "C" fn _emscripten_get_now_is_monotonic() -> i32 {
0
}
#[no_mangle]
pub unsafe extern "C" fn emscripten_builtin_malloc(size: usize) -> *mut u8 {
alloc::alloc_zeroed(Layout::from_size_align_unchecked(size, 32))
}
#[no_mangle]
#[tracing::instrument]
pub unsafe extern "C" fn emscripten_errn(str: *const u8, len: usize) {
let c = str::from_utf8_unchecked(slice::from_raw_parts(str, len));
tracing::error!("Emscripten error: {c}");
}
// despite disabling exceptions literally everywhere when compiling, we still have to stub this...
#[no_mangle]
pub unsafe extern "C" fn __cxa_throw(_ptr: *const (), _type: *const (), _destructor: *const ()) -> ! {
std::process::abort();
}
}
#[no_mangle]
#[export_name = "_initialize"]
pub fn initialize() {
// The presence of an `_initialize` function prevents the linker from calling `__wasm_call_ctors` at the top of every
// function - including the functions `wasm-bindgen` interprets to generate JS glue code. `__wasm_call_ctors` calls
// complex functions that wbg's interpreter isn't equipped to handle, which was preventing wbg from outputting
// anything.
// I'm not entirely sure what `__wasm_call_ctors` is initializing, but it seems to have something to do with C++
// vtables, and it's crucial for proper operation.
extern "C" {
fn __wasm_call_ctors();
}
unsafe { __wasm_call_ctors() };
}