Don't use the global stack for string lengths

This commit updates the `Abi` associated type for all slice types to a
`WasmSlice` type, an aggregate of two `u32` integers. This translates to an ABI
where when passed as a function argument it expands to two integer arguments,
and when passed as a return value it passes a return pointer as the first
argument to get filled in.

This is hopefully more forwards-compatible with the host bindings proposal which
uses this strategy for passing string arguments at least. It's a little sketchy
what we're doing as there's not really a stable ABI yet, but hopefully this'll
all be relatively stable for awhile!
This commit is contained in:
Alex Crichton
2018-05-02 21:03:50 -07:00
parent 4304a262c6
commit 139b7a1aae
5 changed files with 137 additions and 105 deletions

View File

@ -23,10 +23,6 @@ pub struct Js2Rust<'a, 'b: 'a> {
/// finishes. This is scheduled in a `finally` block.
finally: String,
/// Next global index to write to when passing arguments via the single
/// global stack.
global_idx: usize,
/// Index of the next argument for unique name generation purposes.
arg_idx: usize,
@ -52,7 +48,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
js_arguments: Vec::new(),
prelude: String::new(),
finally: String::new(),
global_idx: 0,
arg_idx: 0,
ret_ty: String::new(),
ret_expr: String::new(),
@ -102,21 +97,23 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
self
}
fn abi_arg(&mut self) -> String {
let s = format!("arg{}", self.arg_idx);
self.arg_idx += 1;
s
}
pub fn argument(&mut self, arg: &Descriptor) -> Result<&mut Self, Error> {
let i = self.arg_idx;
self.arg_idx += 1;
let name = format!("arg{}", i);
let name = self.abi_arg();
if let Some(kind) = arg.vector_kind() {
self.js_arguments.push((name.clone(), kind.js_ty().to_string()));
let func = self.cx.pass_to_wasm_function(kind)?;
self.cx.expose_set_global_argument()?;
let global_idx = self.global_idx();
self.prelude(&format!("\
const [ptr{i}, len{i}] = {func}({arg});\n\
setGlobalArgument(len{i}, {global_idx});\n\
", i = i, func = func, arg = name, global_idx = global_idx));
", i = i, func = func, arg = name));
if arg.is_by_ref() {
if arg.is_mut_ref() {
let get = self.cx.memview_function(kind);
@ -133,6 +130,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
self.cx.require_internal_export("__wbindgen_free")?;
}
self.rust_arguments.push(format!("ptr{}", i));
self.rust_arguments.push(format!("len{}", i));
return Ok(self)
}
@ -187,7 +185,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
_assertBoolean({name});\n\
", name = name));
}
self.rust_arguments.push(format!("arg{i} ? 1 : 0", i = i));
self.rust_arguments.push(format!("{} ? 1 : 0", name));
}
Descriptor::Anyref => {
self.js_arguments.push((name.clone(), "any".to_string()));
@ -225,13 +223,18 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
if let Some(ty) = ty.vector_kind() {
self.ret_ty = ty.js_ty().to_string();
let f = self.cx.expose_get_vector_from_wasm(ty);
self.cx.expose_get_global_argument()?;
self.cx.expose_global_argument_ptr()?;
self.cx.expose_uint32_memory();
self.cx.require_internal_export("__wbindgen_free")?;
self.prelude("const retptr = globalArgumentPtr();");
self.rust_arguments.insert(0, "retptr".to_string());
self.ret_expr = format!("\
const ret = RET;\n\
const len = getGlobalArgument(0);\n\
const realRet = {}(ret, len);\n\
wasm.__wbindgen_free(ret, len * {});\n\
RET;\n\
const mem = getUint32Memory();\n\
const ptr = mem[retptr / 4];\n\
const len = mem[retptr / 4 + 1];\n\
const realRet = {}(ptr, len);\n\
wasm.__wbindgen_free(ptr, len * {});\n\
return realRet;\n\
", f, ty.size());
return Ok(self)
@ -309,10 +312,4 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
let ts = format!("{} {}({}): {};\n", prefix, self.js_name, ts_args, self.ret_ty);
(js, ts)
}
fn global_idx(&mut self) -> usize {
let ret = self.global_idx;
self.global_idx += 1;
ret
}
}

View File

@ -1318,21 +1318,6 @@ impl<'a> Context<'a> {
}
}
fn expose_set_global_argument(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("set_global_argument") {
return Ok(());
}
self.expose_uint32_memory();
self.expose_global_argument_ptr()?;
self.global("
function setGlobalArgument(arg, i) {
const idx = globalArgumentPtr() / 4 + i;
getUint32Memory()[idx] = arg;
}
");
Ok(())
}
fn expose_get_global_argument(&mut self) -> Result<(), Error> {
if !self.exposed_globals.insert("get_global_argument") {
return Ok(());

View File

@ -72,31 +72,32 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
Ok(self)
}
fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> {
let i = self.arg_idx;
fn shim_argument(&mut self) -> String {
let s = format!("arg{}", self.arg_idx);
self.arg_idx += 1;
self.shim_arguments.push(s.clone());
s
}
self.shim_arguments.push(format!("arg{}", i));
fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> {
let abi = self.shim_argument();
if let Some(ty) = arg.vector_kind() {
let abi2 = self.shim_argument();
let f = self.cx.expose_get_vector_from_wasm(ty);
self.cx.expose_get_global_argument()?;
let next_global = self.global_idx();
self.prelude(&format!("\
let len{0} = getGlobalArgument({next_global});\n\
let v{0} = {func}(arg{0}, len{0});\n\
", i, func = f, next_global = next_global));
self.prelude(&format!("let v{0} = {func}({0}, {1});",
abi, abi2, func = f));
if !arg.is_by_ref() {
self.prelude(&format!("\
wasm.__wbindgen_free(arg{0}, len{0} * {size});\
", i, size = ty.size()));
wasm.__wbindgen_free({0}, {1} * {size});\
", abi, abi2, size = ty.size()));
self.cx.require_internal_export("__wbindgen_free")?;
} else if arg.is_mut_ref() {
let f = self.cx.expose_commit_slice_to_wasm(ty)?;
self.finally(&format!("{}(arg{i}, v{i});", f, i = i));
self.finally(&format!("{}({1}, v{1});", f, abi));
}
self.js_arguments.push(format!("v{}", i));
self.js_arguments.push(format!("v{}", abi));
return Ok(())
}
@ -104,9 +105,9 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
if arg.is_by_ref() {
bail!("cannot invoke JS functions with custom ref types yet")
}
let assign = format!("let c{0} = {1}.__construct(arg{0});", i, class);
let assign = format!("let c{0} = {1}.__construct({0});", abi, class);
self.prelude(&assign);
self.js_arguments.push(format!("c{}", i));
self.js_arguments.push(format!("c{}", abi));
return Ok(())
}
@ -132,12 +133,12 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
self.global_idx();
self.prelude(&format!("\
let cb{0} = {js};\n\
cb{0}.f = wasm.__wbg_function_table.get(arg{0});\n\
cb{0}.f = wasm.__wbg_function_table.get({0});\n\
cb{0}.a = getGlobalArgument({next_global});\n\
cb{0}.b = getGlobalArgument({next_global} + 1);\n\
", i, js = js, next_global = next_global));
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", i));
self.js_arguments.push(format!("cb{0}.bind(cb{0})", i));
", abi, js = js, next_global = next_global));
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
return Ok(())
}
@ -168,35 +169,35 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({c}));\n\
let real = cb{0}.bind(cb{0});\n\
real.original = cb{0};\n\
idx{0} = getUint32Memory()[arg{0} / 4] = addHeapObject(real);\n\
idx{0} = getUint32Memory()[{0} / 4] = addHeapObject(real);\n\
",
i,
abi,
js = js,
a = self.global_idx(),
b = self.global_idx(),
c = self.global_idx(),
);
self.prelude(&format!("\
let idx{0} = getUint32Memory()[arg{0} / 4];\n\
let idx{0} = getUint32Memory()[{0} / 4];\n\
if (idx{0} === 0xffffffff) {{\n\
{1}\
}}\n\
", i, indent(&reset_idx)));
", abi, indent(&reset_idx)));
self.cx.expose_get_object();
self.js_arguments.push(format!("getObject(idx{})", i));
self.js_arguments.push(format!("getObject(idx{})", abi));
return Ok(())
}
let invoc_arg = match *arg {
ref d if d.is_number() => format!("arg{}", i),
Descriptor::Boolean => format!("arg{} !== 0", i),
ref d if d.is_number() => abi,
Descriptor::Boolean => format!("{} !== 0", abi),
Descriptor::Anyref => {
self.cx.expose_take_object();
format!("takeObject(arg{})", i)
format!("takeObject({})", abi)
}
ref d if d.is_ref_anyref() => {
self.cx.expose_get_object();
format!("getObject(arg{})", i)
format!("getObject({})", abi)
}
_ => bail!("unimplemented argument type in imported function: {:?}", arg),
};
@ -218,11 +219,12 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
if let Some(ty) = ty.vector_kind() {
let f = self.cx.pass_to_wasm_function(ty)?;
self.cx.expose_uint32_memory();
self.cx.expose_set_global_argument()?;
self.shim_arguments.insert(0, "ret".to_string());
self.ret_expr = format!("\
const [retptr, retlen] = {}(JS);\n\
setGlobalArgument(retlen, 0);\n\
return retptr;\n\
const mem = getUint32Memory();
mem[ret / 4] = retptr;
mem[ret / 4 + 1] = retlen;
", f);
return Ok(())
}