Add support for TextEncoder#encodeInto

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 #1172
This commit is contained in:
Alex Crichton
2019-02-20 08:39:46 -08:00
parent de85d99acd
commit 745b16e3d2
4 changed files with 110 additions and 7 deletions

View File

@ -1,6 +1,6 @@
use crate::decode;
use crate::descriptor::{Descriptor, VectorKind};
use crate::Bindgen;
use crate::{Bindgen, EncodeInto};
use failure::{bail, Error, ResultExt};
use std::collections::{HashMap, HashSet};
use walrus::{MemoryId, Module};
@ -1168,19 +1168,77 @@ impl<'a> Context<'a> {
} else {
""
};
self.global(&format!(
// The first implementation we have for this is to use
// `TextEncoder#encode` which has been around for quite some time.
let use_encode = format!(
"
function passStringToWasm(arg) {{
{}
const buf = cachedTextEncoder.encode(arg);
const ptr = wasm.__wbindgen_malloc(buf.length);
getUint8Memory().set(buf, ptr);
WASM_VECTOR_LEN = buf.length;
return ptr;
}}
",
debug
));
);
// Another possibility is to use `TextEncoder#encodeInto` which is much
// newer and isn't implemented everywhere yet. It's more efficient,
// however, becaues it allows us to elide an intermediate allocation.
let use_encode_into = format!(
"
{}
let size = arg.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(arg, view);
arg = arg.substring(read);
writeOffset += written;
if (arg.length === 0) {{
break;
}}
ptr = wasm.__wbindgen_realloc(ptr, size, size * 2);
size *= 2;
}}
WASM_VECTOR_LEN = writeOffset;
return ptr;
",
debug
);
match self.config.encode_into {
EncodeInto::Never => {
self.global(&format!(
"function passStringToWasm(arg) {{ {} }}",
use_encode,
));
}
EncodeInto::Always => {
self.require_internal_export("__wbindgen_realloc")?;
self.global(&format!(
"function passStringToWasm(arg) {{ {} }}",
use_encode_into,
));
}
EncodeInto::Test => {
self.require_internal_export("__wbindgen_realloc")?;
self.global(&format!(
"
let passStringToWasm;
if (typeof cachedTextEncoder.encodeInto === 'function') {{
passStringToWasm = function(arg) {{ {} }};
}} else {{
passStringToWasm = function(arg) {{ {} }};
}}
",
use_encode_into,
use_encode,
));
}
}
Ok(())
}

View File

@ -36,6 +36,7 @@ pub struct Bindgen {
// module to be "ready to be instantiated on any thread"
threads: Option<wasm_bindgen_threads_xform::Config>,
anyref: bool,
encode_into: EncodeInto,
}
enum Input {
@ -44,6 +45,12 @@ enum Input {
None,
}
pub enum EncodeInto {
Test,
Always,
Never,
}
impl Bindgen {
pub fn new() -> Bindgen {
Bindgen {
@ -64,6 +71,7 @@ impl Bindgen {
weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
threads: threads_config(),
anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(),
encode_into: EncodeInto::Test,
}
}
@ -144,6 +152,11 @@ impl Bindgen {
self
}
pub fn encode_into(&mut self, mode: EncodeInto) -> &mut Bindgen {
self.encode_into = mode;
self
}
pub fn generate<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
self._generate(path.as_ref())
}