mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Start optimizing code size:
* Use a bundled custom `WasmRefCell` instead of the one in the standard library. This one primarily doesn't panic via libstd which means that its code footprint is much smaller. * Add a `throw` function to `wasm_bindgen`-the-crate which can be used to throw an exception in JS from Rust. This is useful as a cheap way to throw an exception code-wise (little code bloat) and it's also a great way of reporting error messages to JS! * Cut down on the code size of `__wbindgen_malloc` by aborting on huge requests earlier. * Use a custom `assert_not_null` function which delegates to `throw` to test for incoming null pointers
This commit is contained in:
parent
5b079b8f60
commit
d3387d591f
@ -389,6 +389,13 @@ impl Js {
|
||||
imports.push_str("__wasm_bindgen_object_drop_ref: dropRef,\n");
|
||||
}
|
||||
|
||||
if self.wasm_import_needed("__wbindgen_throw", m) {
|
||||
self.expose_get_string_from_wasm();
|
||||
imports.push_str("__wbindgen_throw: function(ptr: number, len: number) {
|
||||
throw new Error(getStringFromWasm(ptr, len));
|
||||
},\n");
|
||||
}
|
||||
|
||||
let mut writes = String::new();
|
||||
if self.exposed_globals.contains(&"memory") {
|
||||
writes.push_str("memory = exports.memory;\n");
|
||||
|
@ -127,8 +127,8 @@ fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
|
||||
let free_fn = s.free_function();
|
||||
(my_quote! {
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) {
|
||||
assert!(!ptr.is_null());
|
||||
pub unsafe extern fn #free_fn(ptr: *mut ::wasm_bindgen::__rt::WasmRefCell<#name>) {
|
||||
::wasm_bindgen::__rt::assert_not_null(ptr);
|
||||
drop(Box::from_raw(ptr));
|
||||
}
|
||||
}).to_tokens(into);
|
||||
@ -174,9 +174,9 @@ fn bindgen(export_name: &syn::Lit,
|
||||
|
||||
let mut offset = 0;
|
||||
if let Receiver::StructMethod(class, _, _) = receiver {
|
||||
args.push(my_quote! { me: *mut ::std::cell::RefCell<#class> });
|
||||
args.push(my_quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> });
|
||||
arg_conversions.push(my_quote! {
|
||||
assert!(!me.is_null());
|
||||
::wasm_bindgen::__rt::assert_not_null(me);
|
||||
let me = unsafe { &*me };
|
||||
});
|
||||
offset = 1;
|
||||
@ -216,9 +216,9 @@ fn bindgen(export_name: &syn::Lit,
|
||||
});
|
||||
}
|
||||
ast::Type::ByValue(name) => {
|
||||
args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> });
|
||||
arg_conversions.push(my_quote! {
|
||||
assert!(!#ident.is_null());
|
||||
::wasm_bindgen::__rt::assert_not_null(#ident);
|
||||
let #ident = unsafe {
|
||||
(*#ident).borrow_mut();
|
||||
Box::from_raw(#ident).into_inner()
|
||||
@ -226,17 +226,17 @@ fn bindgen(export_name: &syn::Lit,
|
||||
});
|
||||
}
|
||||
ast::Type::ByRef(name) => {
|
||||
args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> });
|
||||
arg_conversions.push(my_quote! {
|
||||
assert!(!#ident.is_null());
|
||||
::wasm_bindgen::__rt::assert_not_null(#ident);
|
||||
let #ident = unsafe { (*#ident).borrow() };
|
||||
let #ident = &*#ident;
|
||||
});
|
||||
}
|
||||
ast::Type::ByMutRef(name) => {
|
||||
args.push(my_quote! { #ident: *mut ::std::cell::RefCell<#name> });
|
||||
args.push(my_quote! { #ident: *mut ::wasm_bindgen::__rt::WasmRefCell<#name> });
|
||||
arg_conversions.push(my_quote! {
|
||||
assert!(!#ident.is_null());
|
||||
::wasm_bindgen::__rt::assert_not_null(#ident);
|
||||
let mut #ident = unsafe { (*#ident).borrow_mut() };
|
||||
let #ident = &mut *#ident;
|
||||
});
|
||||
@ -275,9 +275,9 @@ fn bindgen(export_name: &syn::Lit,
|
||||
convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) };
|
||||
}
|
||||
Some(&ast::Type::ByValue(name)) => {
|
||||
ret_ty = my_quote! { -> *mut ::std::cell::RefCell<#name> };
|
||||
ret_ty = my_quote! { -> *mut ::wasm_bindgen::__rt::WasmRefCell<#name> };
|
||||
convert_ret = my_quote! {
|
||||
Box::into_raw(Box::new(::std::cell::RefCell::new(#ret)))
|
||||
Box::into_raw(Box::new(::wasm_bindgen::__rt::WasmRefCell::new(#ret)))
|
||||
};
|
||||
}
|
||||
Some(&ast::Type::JsObject) => {
|
||||
@ -299,6 +299,17 @@ fn bindgen(export_name: &syn::Lit,
|
||||
my_quote! {
|
||||
#[no_mangle]
|
||||
pub extern fn __wbindgen_malloc(size: usize) -> *mut u8 {
|
||||
// Any malloc request this big is bogus anyway. If this actually
|
||||
// goes down to `Vec` we trigger a whole bunch of panicking
|
||||
// machinery to get pulled in from libstd anyway as it'll verify
|
||||
// the size passed in below.
|
||||
//
|
||||
// Head this all off by just aborting on too-big sizes. This
|
||||
// avoids panicking (code bloat) and gives a better error
|
||||
// message too hopefully.
|
||||
if size >= usize::max_value() / 2 {
|
||||
::wasm_bindgen::throw("invalid malloc request");
|
||||
}
|
||||
let mut ret = Vec::with_capacity(size);
|
||||
let ptr = ret.as_mut_ptr();
|
||||
::std::mem::forget(ret);
|
||||
|
138
src/lib.rs
138
src/lib.rs
@ -53,3 +53,141 @@ impl Drop for JsObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cold]
|
||||
pub fn throw(s: &str) -> ! {
|
||||
extern {
|
||||
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
||||
}
|
||||
unsafe {
|
||||
__wbindgen_throw(s.as_ptr(), s.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod __rt {
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
#[inline]
|
||||
pub fn assert_not_null<T>(s: *mut T) {
|
||||
if s.is_null() {
|
||||
super::throw("null pointer passed to rust");
|
||||
}
|
||||
}
|
||||
|
||||
/// A vendored version of `RefCell` from the standard library.
|
||||
///
|
||||
/// Now why, you may ask, would we do that? Surely `RefCell` in libstd is
|
||||
/// quite good. And you're right, it is indeed quite good! Functionally
|
||||
/// nothing more is needed from `RefCell` in the standard library but for
|
||||
/// now this crate is also sort of optimizing for compiled code size.
|
||||
///
|
||||
/// One major factor to larger binaries in Rust is when a panic happens.
|
||||
/// Panicking in the standard library involves a fair bit of machinery
|
||||
/// (formatting, panic hooks, synchronization, etc). It's all worthwhile if
|
||||
/// you need it but for something like `WasmRefCell` here we don't actually
|
||||
/// need all that!
|
||||
///
|
||||
/// This is just a wrapper around all Rust objects passed to JS intended to
|
||||
/// guard accidental reentrancy, so this vendored version is intended solely
|
||||
/// to not panic in libstd. Instead when it "panics" it calls our `throw`
|
||||
/// function in this crate which raises an error in JS.
|
||||
pub struct WasmRefCell<T> {
|
||||
borrow: Cell<usize>,
|
||||
value: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> WasmRefCell<T> {
|
||||
pub fn new(value: T) -> WasmRefCell<T> {
|
||||
WasmRefCell {
|
||||
value: UnsafeCell::new(value),
|
||||
borrow: Cell::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow(&self) -> Ref<T> {
|
||||
unsafe {
|
||||
if self.borrow.get() == usize::max_value() {
|
||||
borrow_fail();
|
||||
}
|
||||
self.borrow.set(self.borrow.get() + 1);
|
||||
Ref {
|
||||
value: &*self.value.get(),
|
||||
borrow: &self.borrow,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> RefMut<T> {
|
||||
unsafe {
|
||||
if self.borrow.get() != 0 {
|
||||
borrow_fail();
|
||||
}
|
||||
self.borrow.set(usize::max_value());
|
||||
RefMut {
|
||||
value: &mut *self.value.get(),
|
||||
borrow: &self.borrow,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
unsafe {
|
||||
self.value.into_inner()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ref<'b, T: 'b> {
|
||||
value: &'b T,
|
||||
borrow: &'b Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'b, T> Deref for Ref<'b, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T> Drop for Ref<'b, T> {
|
||||
fn drop(&mut self) {
|
||||
self.borrow.set(self.borrow.get() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RefMut<'b, T: 'b> {
|
||||
value: &'b mut T,
|
||||
borrow: &'b Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'b, T> Deref for RefMut<'b, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T> DerefMut for RefMut<'b, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, T> Drop for RefMut<'b, T> {
|
||||
fn drop(&mut self) {
|
||||
self.borrow.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn borrow_fail() -> ! {
|
||||
super::throw("recursive use of an object detected which would lead to \
|
||||
unsafe aliasing in rust");
|
||||
}
|
||||
}
|
||||
|
@ -156,12 +156,11 @@ fn exceptions() {
|
||||
assert.throws(() => new wasm.A(), /cannot invoke `new` directly/);
|
||||
let a = wasm.A.new();
|
||||
a.free();
|
||||
// TODO: figure out a better error message?
|
||||
assert.throws(() => a.free(), /RuntimeError: unreachable/);
|
||||
assert.throws(() => a.free(), /null pointer passed to rust/);
|
||||
|
||||
let b = wasm.A.new();
|
||||
b.foo(b);
|
||||
assert.throws(() => b.bar(b), /RuntimeError: unreachable/);
|
||||
assert.throws(() => b.bar(b), /recursive use of an object/);
|
||||
|
||||
let c = wasm.A.new();
|
||||
let d = wasm.B.new();
|
||||
|
Loading…
x
Reference in New Issue
Block a user