Skip to content

Commit

Permalink
Add support for TextEncoder#encodeInto
Browse files Browse the repository at this point in the history
This commit adds support for the recently implemented standard of
[`TextEncoder#encodeInto`][standard]. This new function is a "bring your
own buffer" style function where we can avoid an intermediate allocation
and copy by encoding strings directly into wasm's memory.

Currently we feature-detect whether `encodeInto` exists as it is only
implemented in recent browsers and not in all browsers. Additionally
this commit emits the binding using `encodeInto` by default, but this
requires `realloc` functionality to be exposed by the wasm module.
Measured locally an empty binary which takes `&str` previously took
7.6k, but after this commit takes 8.7k due to the extra code needed for
`realloc`.

[standard]: https://encoding.spec.whatwg.org/#dom-textencoder-encodeinto

Closes rustwasm#1172
  • Loading branch information
alexcrichton committed Feb 20, 2019
1 parent de85d99 commit 4e8371e
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
28 changes: 28 additions & 0 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ impl<'a> Context<'a> {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.require_internal_export("__wbindgen_realloc")?;
self.expose_text_encoder();
self.expose_uint8_memory();
self.expose_wasm_vector_len();
Expand All @@ -1168,10 +1169,37 @@ impl<'a> Context<'a> {
} else {
""
};

// Add a binding for using `TextEncoder#encodeInto` which avoids
// intermediate buffer allocations by encoding directly into wasm's
// memory. This is a very new API, however, so we feature detect it and
// fall back to the widely available `TextEncoder#encode` method.
self.global("
function passStringToWasmFast(string) {
let size = string.length;
let ptr = wasm.__wbindgen_malloc(size);
let writeOffset = 0;
while (true) {
const view = getUint8Memory().subarray(ptr + writeOffset, ptr + size);
const { read, written } = cachedTextEncoder.encodeInto(string, view);
string = string.substring(read);
writeOffset += written;
if (string.length === 0)
break;
ptr = wasm.__wbindgen_realloc(ptr, size, size * 2);
size *= 2;
}
WASM_VECTOR_LEN = writeOffset;
return ptr;
}
");

self.global(&format!(
"
function passStringToWasm(arg) {{
{}
if (typeof cachedTextEncoder.encodeInto === 'function')
return passStringToWasmFast(arg);
const buf = cachedTextEncoder.encode(arg);
const ptr = wasm.__wbindgen_malloc(buf.length);
getUint8Memory().set(buf, ptr);
Expand Down
23 changes: 22 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ pub mod __rt {
}

if_std! {
use std::alloc::{alloc, dealloc, Layout};
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::mem;

#[no_mangle]
Expand All @@ -911,6 +911,27 @@ pub mod __rt {
}
}

malloc_failure();
}

#[no_mangle]
pub extern "C" fn __wbindgen_realloc(ptr: *mut u8, old_size: usize, new_size: usize) -> *mut u8 {
let align = mem::align_of::<usize>();
debug_assert!(old_size > 0);
debug_assert!(new_size > 0);
if let Ok(layout) = Layout::from_size_align(old_size, align) {
unsafe {
let ptr = realloc(ptr, layout, new_size);
if !ptr.is_null() {
return ptr
}
}
}
malloc_failure();
}

#[cold]
fn malloc_failure() -> ! {
if cfg!(debug_assertions) {
super::throw_str("invalid malloc request")
} else {
Expand Down

0 comments on commit 4e8371e

Please sign in to comment.