Merge pull request #1002 from alexcrichton/reference-types

Add experimental support for the `anyref` type
This commit is contained in:
Alex Crichton 2019-02-20 09:29:19 -06:00 committed by GitHub
commit de85d99acd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1839 additions and 523 deletions

View File

@ -65,6 +65,8 @@ matrix:
- cargo test
# Run the main body of the test suite
- cargo test --target wasm32-unknown-unknown
# Make sure the anyref pass at least compiles even if it doesn't run
- NODE_ARGS=/dev/null WASM_BINDGEN_ANYREF=1 cargo test --target wasm32-unknown-unknown --test wasm
# Rerun the test suite but disable `--debug` in generated JS
- WASM_BINDGEN_NO_DEBUG=1 cargo test --target wasm32-unknown-unknown
# Make sure our serde tests work

View File

@ -0,0 +1,16 @@
[package]
name = "wasm-bindgen-anyref-xform"
version = "0.2.37"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/anyref-xform"
homepage = "https://rustwasm.github.io/wasm-bindgen/"
documentation = "https://docs.rs/wasm-bindgen-anyref-xform"
description = """
Internal anyref transformations for wasm-bindgen
"""
edition = '2018'
[dependencies]
failure = "0.1"
walrus = "0.4"

View File

@ -0,0 +1,730 @@
//! Transformation for wasm-bindgen to enable usage of `anyref` in a wasm
//! module.
//!
//! This crate is in charge of enabling code using `wasm-bindgen` to use the
//! `anyref` type inside of the wasm module. This transformation pass primarily
//! wraps exports and imports in shims which use `anyref`, but quickly turn them
//! into `i32` value types. This is all largely a stopgap until Rust has
//! first-class support for the `anyref` type, but that's thought to be in the
//! far future and will take quite some time to implement. In the meantime, we
//! have this!
//!
//! The pass here works by collecting information during binding generation
//! about imports and exports. Afterwards this pass runs in one go against a
//! wasm module, updating exports, imports, calls to these functions, etc. The
//! goal at least is to have valid wasm modules coming in that don't use
//! `anyref` and valid wasm modules going out which use `anyref` at the fringes.
use failure::{bail, format_err, Error};
use std::cmp;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::mem;
use walrus::ir::*;
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
// must be kept in sync with src/lib.rs and ANYREF_HEAP_START
const DEFAULT_MIN: u32 = 32;
/// State of the anyref pass, used to collect information while bindings are
/// generated and used eventually to actually execute the entire pass.
#[derive(Default)]
pub struct Context {
// Functions within the module that we're gonna be wrapping, organized by
// type. The `Function` contains information about what arguments/return
// values in the function signature should turn into anyref.
imports: HashMap<String, HashMap<String, Function>>,
exports: HashMap<String, Function>,
elements: BTreeMap<u32, (u32, Function)>,
// When wrapping closures with new shims, this is the index of the next
// table entry that we'll be handing out.
next_element: u32,
// The anyref table we'll be using, injected after construction
table: Option<TableId>,
// Whether or not the transformation will actually be run at the end
pub enabled: bool,
}
struct Transform<'a> {
cx: &'a mut Context,
// A map of functions to intrinsics that they represent
intrinsic_map: HashMap<FunctionId, Intrinsic>,
// A map of old import functions to the new internally-defined shims which
// call the correct new import functions
import_map: HashMap<FunctionId, FunctionId>,
// A set of all shims we've created
shims: HashSet<FunctionId>,
// Indices of items that we have injected or found. This state is maintained
// during the pass execution.
table: TableId,
clone_ref: FunctionId,
heap_alloc: FunctionId,
heap_dealloc: FunctionId,
stack_pointer: GlobalId,
}
struct Function {
name: String,
// A map of argument index to whether it's an owned or borrowed anyref
// (owned = true)
args: HashMap<usize, bool>,
ret_anyref: bool,
}
enum Intrinsic {
TableGrow,
TableSetNull,
DropRef,
CloneRef,
}
impl Context {
/// Executed first very early over a wasm module, used to learn about how
/// large the function table is so we know what indexes to hand out when
/// we're appending entries.
pub fn prepare(&mut self, module: &mut Module) -> Result<(), Error> {
if !self.enabled {
return Ok(());
}
// Figure out what the maximum index of functions pointers are. We'll
// be adding new entries to the function table later (maybe) so
// precalculate this ahead of time.
let mut tables = module.tables.iter().filter_map(|t| match &t.kind {
walrus::TableKind::Function(f) => Some(f),
_ => None,
});
if let Some(t) = tables.next() {
if tables.next().is_some() {
bail!("more than one function table present")
}
self.next_element = t.elements.len() as u32;
}
drop(tables);
// Add in an anyref table to the module, which we'll be using for
// our transform below.
let kind = walrus::TableKind::Anyref(Default::default());
self.table = Some(module.tables.add_local(DEFAULT_MIN, None, kind));
Ok(())
}
/// Store information about an imported function that needs to be
/// transformed. The actual transformation happens later during `run`.
pub fn import_xform(
&mut self,
module: &str,
name: &str,
anyref: &[(usize, bool)],
ret_anyref: bool,
) -> &mut Self {
if !self.enabled {
return self;
}
let f = self.function(name, anyref, ret_anyref);
self.imports
.entry(module.to_string())
.or_insert_with(Default::default)
.insert(name.to_string(), f);
self
}
/// Store information about an exported function that needs to be
/// transformed. The actual transformation happens later during `run`.
pub fn export_xform(
&mut self,
name: &str,
anyref: &[(usize, bool)],
ret_anyref: bool,
) -> &mut Self {
if !self.enabled {
return self;
}
let f = self.function(name, anyref, ret_anyref);
self.exports.insert(name.to_string(), f);
self
}
/// Store information about a function pointer that needs to be transformed.
/// The actual transformation happens later during `run`. Returns an index
/// that the new wrapped function pointer will be injected at.
pub fn table_element_xform(
&mut self,
idx: u32,
anyref: &[(usize, bool)],
ret_anyref: bool,
) -> u32 {
if !self.enabled {
return idx;
}
let name = format!("closure{}", idx);
let f = self.function(&name, anyref, ret_anyref);
let ret = self.next_element;
self.next_element += 1;
self.elements.insert(ret, (idx, f));
ret
}
fn function(&self, name: &str, anyref: &[(usize, bool)], ret_anyref: bool) -> Function {
Function {
name: name.to_string(),
args: anyref.iter().cloned().collect(),
ret_anyref,
}
}
pub fn anyref_table_id(&self) -> TableId {
self.table.unwrap()
}
pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
if !self.enabled {
return Ok(());
}
let table = self.table.unwrap();
// Inject a stack pointer global which will be used for managing the
// stack on the anyref table.
let init = InitExpr::Value(Value::I32(DEFAULT_MIN as i32));
let stack_pointer = module.globals.add_local(ValType::I32, true, init);
let mut heap_alloc = None;
let mut heap_dealloc = None;
// Find exports of some intrinsics which we only need for a runtime
// implementation.
for export in module.exports.iter() {
let f = match export.item {
walrus::ExportItem::Function(f) => f,
_ => continue,
};
match export.name.as_str() {
"__wbindgen_anyref_table_alloc" => heap_alloc = Some(f),
"__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f),
_ => {}
}
}
let heap_alloc = heap_alloc.ok_or_else(|| format_err!("failed to find heap alloc"))?;
let heap_dealloc =
heap_dealloc.ok_or_else(|| format_err!("failed to find heap dealloc"))?;
// Create a shim function that looks like:
//
// (func __wbindgen_object_clone_ref (param i32) (result i32)
// (local i32)
// (table.set
// (tee_local 1 (call $heap_alloc))
// (table.get (local.get 0)))
// (local.get 1))
let mut builder = walrus::FunctionBuilder::new();
let arg = module.locals.add(ValType::I32);
let local = module.locals.add(ValType::I32);
let alloc = builder.call(heap_alloc, Box::new([]));
let tee = builder.local_tee(local, alloc);
let get_arg = builder.local_get(arg);
let get_table = builder.table_get(table, get_arg);
let set_table = builder.table_set(table, tee, get_table);
let get_local = builder.local_get(local);
let ty = module.types.add(&[ValType::I32], &[ValType::I32]);
let clone_ref = builder.finish(ty, vec![arg], vec![set_table, get_local], module);
let name = "__wbindgen_object_clone_ref".to_string();
module.funcs.get_mut(clone_ref).name = Some(name);
// And run the transformation!
Transform {
cx: self,
intrinsic_map: HashMap::new(),
import_map: HashMap::new(),
shims: HashSet::new(),
table,
clone_ref,
heap_alloc,
heap_dealloc,
stack_pointer,
}
.run(module)
}
}
impl Transform<'_> {
fn run(&mut self, module: &mut Module) -> Result<(), Error> {
// Detect all the various intrinsics and such. This will also along the
// way inject an intrinsic for cloning an anyref.
self.find_intrinsics(module)?;
// Perform transformations of imports, exports, and function pointers.
self.process_imports(module);
for m in self.cx.imports.values() {
assert!(m.is_empty());
}
self.process_exports(module);
assert!(self.cx.exports.is_empty());
self.process_elements(module)?;
assert!(self.cx.elements.is_empty());
// If we didn't actually transform anything, no need to inject or
// rewrite anything from below.
if self.shims.is_empty() {
return Ok(());
}
// Perform all instruction transformations to rewrite calls between
// functions and make sure everything is still hooked up right.
self.rewrite_calls(module);
// Inject initialization routine to set up default slots in the table
// (things like null/undefined/true/false)
self.inject_initialization(module);
Ok(())
}
fn find_intrinsics(&mut self, module: &mut Module) -> Result<(), Error> {
// Build up a map of various imported intrinsics to wire them up to
// different implementations or different functions.
for import in module.imports.iter_mut() {
let f = match import.kind {
walrus::ImportKind::Function(f) => f,
_ => continue,
};
if import.module == "__wbindgen_anyref_xform__" {
match import.name.as_str() {
"__wbindgen_anyref_table_grow" => {
self.intrinsic_map.insert(f, Intrinsic::TableGrow);
}
"__wbindgen_anyref_table_set_null" => {
self.intrinsic_map.insert(f, Intrinsic::TableSetNull);
}
n => bail!("unknown intrinsic: {}", n),
}
} else if import.module == "__wbindgen_placeholder__" {
match import.name.as_str() {
"__wbindgen_object_drop_ref" => {
self.intrinsic_map.insert(f, Intrinsic::DropRef);
}
"__wbindgen_object_clone_ref" => {
self.intrinsic_map.insert(f, Intrinsic::CloneRef);
}
_ => continue,
}
} else {
continue;
}
// Make sure we don't actually end up using the original import
// because any invocation of them should be remapped to something
// else.
import.name = format!("{}_unused", import.name);
}
Ok(())
}
fn process_imports(&mut self, module: &mut Module) {
for import in module.imports.iter_mut() {
let f = match import.kind {
walrus::ImportKind::Function(f) => f,
_ => continue,
};
let import = {
let entries = match self.cx.imports.get_mut(&import.module) {
Some(s) => s,
None => continue,
};
match entries.remove(&import.name) {
Some(s) => s,
None => continue,
}
};
let shim = self.append_shim(
f,
import,
&mut module.types,
&mut module.funcs,
&mut module.locals,
);
self.import_map.insert(f, shim);
}
}
fn process_exports(&mut self, module: &mut Module) {
let mut new_exports = Vec::new();
for export in module.exports.iter() {
let f = match export.item {
walrus::ExportItem::Function(f) => f,
_ => continue,
};
let function = match self.cx.exports.remove(&export.name) {
Some(s) => s,
None => continue,
};
let shim = self.append_shim(
f,
function,
&mut module.types,
&mut module.funcs,
&mut module.locals,
);
new_exports.push((export.name.to_string(), shim, export.id()));
}
for (name, shim, old_id) in new_exports {
module.exports.add(&name, shim);
module.exports.delete(old_id);
}
}
fn process_elements(&mut self, module: &mut Module) -> Result<(), Error> {
let table = match module.tables.main_function_table()? {
Some(t) => t,
None => return Ok(()),
};
let table = module.tables.get_mut(table);
let kind = match &mut table.kind {
walrus::TableKind::Function(f) => f,
_ => unreachable!(),
};
if kind.relative_elements.len() > 0 {
bail!("not compatible with relative element initializers yet");
}
// Create shims for all our functions and append them all to the segment
// which places elements at the end.
while let Some((idx, function)) = self.cx.elements.remove(&(kind.elements.len() as u32)) {
let target = kind.elements[idx as usize].unwrap();
let shim = self.append_shim(
target,
function,
&mut module.types,
&mut module.funcs,
&mut module.locals,
);
kind.elements.push(Some(shim));
}
// ... and next update the limits of the table in case any are listed.
table.initial = cmp::max(table.initial, kind.elements.len() as u32);
if let Some(max) = table.maximum {
table.maximum = Some(cmp::max(max, kind.elements.len() as u32));
}
Ok(())
}
fn append_shim(
&mut self,
shim_target: FunctionId,
mut func: Function,
types: &mut walrus::ModuleTypes,
funcs: &mut walrus::ModuleFunctions,
locals: &mut walrus::ModuleLocals,
) -> FunctionId {
let target = funcs.get_mut(shim_target);
let (is_export, ty) = match &mut target.kind {
walrus::FunctionKind::Import(f) => (false, &mut f.ty),
walrus::FunctionKind::Local(f) => (true, &mut f.ty),
_ => unreachable!()
};
let target_ty = types.get(*ty);
// Learn about the various operations we're doing up front. Afterwards
// we'll have a better idea bout what sort of code we're gonna be
// generating.
enum Convert {
None,
Store { owned: bool },
Load { owned: bool },
}
let mut param_tys = Vec::new();
let mut param_convert = Vec::new();
let mut anyref_stack = 0;
for (i, old_ty) in target_ty.params().iter().enumerate() {
let is_owned = func.args.remove(&i);
let new_ty = is_owned
.map(|_which| ValType::Anyref)
.unwrap_or(old_ty.clone());
param_tys.push(new_ty.clone());
if new_ty == *old_ty {
param_convert.push(Convert::None);
} else if is_export {
// We're calling an export, so we need to push this anyref into
// a table somehow.
param_convert.push(Convert::Store {
owned: is_owned.unwrap(),
});
if is_owned == Some(false) {
anyref_stack += 1;
}
} else {
// We're calling an import, so we just need to fetch our table
// value.
param_convert.push(Convert::Load {
owned: is_owned.unwrap(),
});
}
}
let new_ret = if func.ret_anyref {
assert_eq!(target_ty.results(), &[ValType::I32]);
vec![ValType::Anyref]
} else {
target_ty.results().to_vec()
};
let anyref_ty = types.add(&param_tys, &new_ret);
// If we're an export then our shim is what's actually going to get
// exported, and it's going to have the anyref signature.
//
// If we're an import, then our shim is what the Rust code calls, which
// means it'll have the original signature. The existing import's
// signature, however, is transformed to be an anyref signature.
let shim_ty = if is_export {
anyref_ty
} else {
mem::replace(ty, anyref_ty)
};
let mut builder = walrus::FunctionBuilder::new();
let mut before = Vec::new();
let params = types.get(shim_ty)
.params()
.iter()
.cloned()
.map(|ty| locals.add(ty))
.collect::<Vec<_>>();
// Unconditionally allocate some locals which get cleaned up in later
// gc passes if we don't actually end up using them.
let fp = locals.add(ValType::I32);
let scratch_i32 = locals.add(ValType::I32);
let scratch_anyref = locals.add(ValType::Anyref);
// Update our stack pointer if there's any borrowed anyref objects.
if anyref_stack > 0 {
let sp = builder.global_get(self.stack_pointer);
let size = builder.const_(Value::I32(anyref_stack));
let new_sp = builder.binop(BinaryOp::I32Sub, sp, size);
let tee = builder.local_tee(fp, new_sp);
before.push(builder.global_set(self.stack_pointer, tee));
}
let mut next_stack_offset = 0;
let mut args = Vec::new();
for (i, convert) in param_convert.iter().enumerate() {
let local = builder.local_get(params[i]);
args.push(match *convert {
Convert::None => local,
Convert::Load { owned: true } => {
// load the anyref onto the stack, then afterwards
// deallocate our index, leaving the anyref on the stack.
let get = builder.table_get(self.table, local);
let free = builder.call(self.heap_dealloc, Box::new([local]));
builder.with_side_effects(Vec::new(), get, vec![free])
}
Convert::Load { owned: false } => builder.table_get(self.table, local),
Convert::Store { owned: true } => {
// Allocate space for the anyref, store it, and then leave
// the index of the allocated anyref on the stack.
let alloc = builder.call(self.heap_alloc, Box::new([]));
let tee = builder.local_tee(scratch_i32, alloc);
let store = builder.table_set(self.table, tee, local);
let get = builder.local_get(scratch_i32);
builder.with_side_effects(vec![store], get, Vec::new())
}
Convert::Store { owned: false } => {
// Store an anyref at an offset from our function's stack
// pointer frame.
let get_fp = builder.local_get(fp);
next_stack_offset += 1;
let (index, idx_local) = if next_stack_offset == 1 {
(get_fp, fp)
} else {
let rhs = builder.i32_const(next_stack_offset);
let add = builder.binop(BinaryOp::I32Add, get_fp, rhs);
(builder.local_tee(scratch_i32, add), scratch_i32)
};
let store = builder.table_set(self.table, index, local);
let get = builder.local_get(idx_local);
builder.with_side_effects(vec![store], get, Vec::new())
}
});
}
// Now that we've converted all the arguments, call the original
// function. This may be either an import or an export which we're
// wrapping.
let mut result = builder.call(shim_target, args.into_boxed_slice());
let mut after = Vec::new();
// If an anyref value is returned, then we need to be sure to apply
// special treatment to convert it to an i32 as well. Note that only
// owned anyref values can be returned, so that's all that's handled
// here.
if func.ret_anyref {
if is_export {
// We're an export so we have an i32 on the stack and need to
// convert it to an anyref, basically by doing the same as an
// owned load above: get the value then deallocate our slot.
let tee = builder.local_tee(scratch_i32, result);
result = builder.table_get(self.table, tee);
let get_local = builder.local_get(scratch_i32);
after.push(builder.call(self.heap_dealloc, Box::new([get_local])));
} else {
// Imports are the opposite, we have any anyref on the stack
// and convert it to an i32 by allocating space for it and
// storing it there.
before.push(builder.local_set(scratch_anyref, result));
let alloc = builder.call(self.heap_alloc, Box::new([]));
let tee = builder.local_tee(scratch_i32, alloc);
let get = builder.local_get(scratch_anyref);
before.push(builder.table_set(self.table, tee, get));
result = builder.local_get(scratch_i32);
}
}
// On function exit restore our anyref stack pointer if we decremented
// it to start off.
//
// Note that we pave over all our stack slots with `ref.null` to ensure
// that the table doesn't accidentally hold a strong reference to items
// no longer in use by our wasm instance.
//
// TODO: use `table.fill` once that's spec'd
if anyref_stack > 0 {
for i in 0..anyref_stack {
let get_fp = builder.local_get(fp);
let index = if i > 0 {
let offset = builder.i32_const(i);
builder.binop(BinaryOp::I32Add, get_fp, offset)
} else {
get_fp
};
let null = builder.ref_null();
after.push(builder.table_set(self.table, index, null));
}
let get_fp = builder.local_get(fp);
let size = builder.i32_const(anyref_stack);
let new_sp = builder.binop(BinaryOp::I32Add, get_fp, size);
after.push(builder.global_set(self.stack_pointer, new_sp));
}
// Create the final expression node and then finish the function builder
// with a fresh type we've been calculating so far. Give the function a
// nice name for debugging and then we're good to go!
let expr = builder.with_side_effects(before, result, after);
let id = builder.finish_parts(shim_ty, params, vec![expr], types, funcs);
let name = format!("{}_anyref_shim", func.name);
funcs.get_mut(id).name = Some(name);
self.shims.insert(id);
return id;
}
fn rewrite_calls(&mut self, module: &mut Module) {
for (id, func) in module.funcs.iter_local_mut() {
if self.shims.contains(&id) {
continue;
}
let mut entry = func.entry_block();
Rewrite {
func,
xform: self,
replace: None,
}
.visit_block_id_mut(&mut entry);
}
struct Rewrite<'a, 'b> {
func: &'a mut walrus::LocalFunction,
xform: &'a Transform<'b>,
replace: Option<ExprId>,
}
impl VisitorMut for Rewrite<'_, '_> {
fn local_function_mut(&mut self) -> &mut walrus::LocalFunction {
self.func
}
fn visit_expr_id_mut(&mut self, expr: &mut ExprId) {
expr.visit_mut(self);
if let Some(id) = self.replace.take() {
*expr = id;
}
}
fn visit_call_mut(&mut self, e: &mut Call) {
e.visit_mut(self);
let intrinsic = match self.xform.intrinsic_map.get(&e.func) {
Some(f) => f,
None => {
// If this wasn't a call of an intrinsic, but it was a
// call of one of our old import functions then we
// switch the functions we're calling here.
if let Some(f) = self.xform.import_map.get(&e.func) {
e.func = *f;
}
return;
}
};
let builder = self.func.builder_mut();
match intrinsic {
Intrinsic::TableGrow => {
assert_eq!(e.args.len(), 1);
let delta = e.args[0];
let null = builder.ref_null();
let grow = builder.table_grow(self.xform.table, delta, null);
self.replace = Some(grow);
}
Intrinsic::TableSetNull => {
assert_eq!(e.args.len(), 1);
let index = e.args[0];
let null = builder.ref_null();
let set = builder.table_set(self.xform.table, index, null);
self.replace = Some(set);
}
Intrinsic::DropRef => e.func = self.xform.heap_dealloc,
Intrinsic::CloneRef => e.func = self.xform.clone_ref,
}
}
}
}
// Ensure that the `start` function for this module calls the
// `__wbindgen_init_anyref_table` function. This'll ensure that all
// instances of this module have the initial slots of the anyref table
// initialized correctly.
fn inject_initialization(&mut self, module: &mut Module) {
let ty = module.types.add(&[], &[]);
let import = module.add_import_func(
"__wbindgen_placeholder__",
"__wbindgen_init_anyref_table",
ty,
);
let prev_start = match module.start {
Some(f) => f,
None => {
module.start = Some(import);
return;
}
};
let mut builder = walrus::FunctionBuilder::new();
let call_init = builder.call(import, Box::new([]));
let call_prev = builder.call(prev_start, Box::new([]));
let new_start = builder.finish(ty, Vec::new(), vec![call_init, call_prev], module);
module.start = Some(new_start);
}
}

View File

@ -3,9 +3,9 @@ use std::env;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::iter::FromIterator;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::SeqCst;
use std::sync::atomic::{AtomicBool};
use std::sync::atomic::{AtomicUsize};
use ast;
use proc_macro2::{self, Ident};

View File

@ -18,6 +18,7 @@ log = "0.4"
rustc-demangle = "0.1.13"
tempfile = "3.0"
walrus = "0.4.0"
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.37' }
wasm-bindgen-shared = { path = "../shared", version = '=0.2.37' }
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.37' }
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.37' }

View File

@ -10,7 +10,7 @@
//! this works can be found in the code below.
use crate::descriptor::Descriptor;
use crate::js::js2rust::Js2Rust;
use crate::js::js2rust::{ExportedShim, Js2Rust};
use crate::js::Context;
use failure::Error;
use std::collections::{BTreeMap, HashSet};
@ -142,7 +142,7 @@ impl ClosureDescriptors {
let table = input.module.tables.get_mut(table_id);
let table = match &mut table.kind {
walrus::TableKind::Function(f) => f,
walrus::TableKind::Anyref(_) => unreachable!(),
_ => unreachable!(),
};
for idx in self.element_removal_list.iter().cloned() {
log::trace!("delete element {}", idx);
@ -178,6 +178,7 @@ impl ClosureDescriptors {
let closure = instr.descriptor.closure().unwrap();
let mut shim = closure.shim_idx;
let (js, _ts, _js_doc) = {
let mut builder = Js2Rust::new("", input);
builder.prelude("this.cnt++;");
@ -192,9 +193,12 @@ impl ClosureDescriptors {
builder.rust_argument("this.a").rust_argument("b");
}
builder.finally("if (this.cnt-- == 1) d(this.a, b);");
builder.process(&closure.function)?.finish("function", "f")
builder.process(&closure.function)?.finish(
"function",
"f",
ExportedShim::TableElement(&mut shim),
)
};
input.expose_add_heap_object();
input.function_table_needed = true;
let body = format!(
"function(a, b, _ignored) {{
@ -205,15 +209,19 @@ impl ClosureDescriptors {
cb.cnt = 1;
let real = cb.bind(cb);
real.original = cb;
return addHeapObject(real);
return {};
}}",
closure.shim_idx, closure.dtor_idx, js,
shim,
closure.dtor_idx,
js,
input.add_heap_object("real"),
);
input.export(&import_name, &body, None);
let id = input
.module
.add_import_func("__wbindgen_placeholder__", &import_name, ty);
let module = "__wbindgen_placeholder__";
let id = input.module.add_import_func(module, &import_name, ty);
input.anyref.import_xform(module, &import_name, &[], true);
input.module.funcs.get_mut(id).name = Some(import_name);
let local = match &mut input.module.funcs.get_mut(*func).kind {
walrus::FunctionKind::Local(l) => l,

View File

@ -44,6 +44,15 @@ pub struct Js2Rust<'a, 'b: 'a> {
/// The string value here is the class that this should be a constructor
/// for.
constructor: Option<String>,
/// metadata for anyref transformations
anyref_args: Vec<(usize, bool)>,
ret_anyref: bool,
}
pub enum ExportedShim<'a> {
Named(&'a str),
TableElement(&'a mut u32),
}
impl<'a, 'b> Js2Rust<'a, 'b> {
@ -59,6 +68,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
ret_ty: String::new(),
ret_expr: String::new(),
constructor: None,
anyref_args: Vec::new(),
ret_anyref: false,
}
}
@ -193,13 +204,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
if arg.is_anyref() {
self.js_arguments.push((name.clone(), "any".to_string()));
self.cx.expose_add_heap_object();
if optional {
self.cx.expose_is_like_none();
self.rust_arguments
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name,));
if self.cx.config.anyref {
if optional {
self.cx.expose_add_to_anyref_table()?;
self.cx.expose_is_like_none();
self.rust_arguments
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
} else {
self.anyref_args.push((self.rust_arguments.len(), true));
self.rust_arguments.push(name);
}
} else {
self.rust_arguments.push(format!("addHeapObject({})", name));
self.cx.expose_add_heap_object();
if optional {
self.cx.expose_is_like_none();
self.rust_arguments
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name));
} else {
self.rust_arguments.push(format!("addHeapObject({})", name));
}
}
return Ok(self);
}
@ -383,14 +406,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
if arg.is_ref_anyref() {
self.js_arguments.push((name.clone(), "any".to_string()));
self.cx.expose_borrowed_objects();
self.cx.expose_global_stack_pointer();
// the "stack-ful" nature means that we're always popping from the
// stack, and make sure that we actually clear our reference to
// allow stale values to get GC'd
self.finally("heap[stack_pointer++] = undefined;");
self.rust_arguments
.push(format!("addBorrowedObject({})", name));
if self.cx.config.anyref {
self.anyref_args.push((self.rust_arguments.len(), false));
self.rust_arguments.push(name);
} else {
// the "stack-ful" nature means that we're always popping from the
// stack, and make sure that we actually clear our reference to
// allow stale values to get GC'd
self.cx.expose_borrowed_objects();
self.cx.expose_global_stack_pointer();
self.finally("heap[stack_pointer++] = undefined;");
self.rust_arguments
.push(format!("addBorrowedObject({})", name));
}
return Ok(self);
}
@ -462,7 +490,7 @@ 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);
let f = self.cx.expose_get_vector_from_wasm(ty)?;
self.cx.expose_global_argument_ptr()?;
self.cx.expose_uint32_memory();
self.cx.require_internal_export("__wbindgen_free")?;
@ -494,8 +522,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
// that `takeObject` will naturally pluck out `undefined`.
if ty.is_anyref() {
self.ret_ty = "any".to_string();
self.cx.expose_take_object();
self.ret_expr = format!("return takeObject(RET);");
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
self.ret_anyref = true;
return Ok(self);
}
@ -708,7 +736,12 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
/// Returns two strings, the first of which is the JS expression for the
/// generated function shim and the second is a TypeScript signature of the
/// JS expression.
pub fn finish(&self, prefix: &str, invoc: &str) -> (String, String, String) {
pub fn finish(
&mut self,
prefix: &str,
invoc: &str,
exported_shim: ExportedShim,
) -> (String, String, String) {
let js_args = self
.js_arguments
.iter()
@ -754,6 +787,24 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
ts.push_str(&self.ret_ty);
}
ts.push(';');
if self.ret_anyref || self.anyref_args.len() > 0 {
match exported_shim {
ExportedShim::Named(name) => {
self.cx
.anyref
.export_xform(name, &self.anyref_args, self.ret_anyref);
}
ExportedShim::TableElement(idx) => {
*idx = self.cx.anyref.table_element_xform(
*idx,
&self.anyref_args,
self.ret_anyref,
);
}
}
}
(js, ts, self.js_doc_comments())
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,5 @@
use crate::descriptor::{Descriptor, Function};
use crate::js::js2rust::ExportedShim;
use crate::js::{Context, ImportTarget, Js2Rust};
use failure::{bail, Error};
@ -39,6 +40,11 @@ pub struct Rust2Js<'a, 'b: 'a> {
/// Whether or not the last argument is a slice representing variadic arguments.
variadic: bool,
/// list of arguments that are anyref, and whether they're an owned anyref
/// or not.
pub anyref_args: Vec<(usize, bool)>,
pub ret_anyref: bool,
}
impl<'a, 'b> Rust2Js<'a, 'b> {
@ -55,6 +61,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
catch: false,
catch_and_rethrow: false,
variadic: false,
anyref_args: Vec::new(),
ret_anyref: false,
}
}
@ -101,7 +109,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
if let Some(ty) = arg.vector_kind() {
let abi2 = self.shim_argument();
let f = self.cx.expose_get_vector_from_wasm(ty);
let f = self.cx.expose_get_vector_from_wasm(ty)?;
self.prelude(&format!(
"let v{0} = {prefix}{func}({0}, {1});",
abi,
@ -141,12 +149,14 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
// No need to special case `optional` here because `takeObject` will
// naturally work.
if arg.is_anyref() {
self.cx.expose_take_object();
self.js_arguments.push(format!("takeObject({})", abi));
let arg = self.cx.take_object(&abi);
self.js_arguments.push(arg);
self.anyref_args.push((self.arg_idx - 1, true));
return Ok(());
} else if arg.is_ref_anyref() {
self.cx.expose_get_object();
self.js_arguments.push(format!("getObject({})", abi));
let arg = self.cx.get_object(&abi);
self.js_arguments.push(arg);
self.anyref_args.push((self.arg_idx - 1, false));
return Ok(());
}
@ -263,6 +273,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
if let Some((f, mutable)) = arg.stack_closure() {
let arg2 = self.shim_argument();
let mut shim = f.shim_idx;
let (js, _ts, _js_doc) = {
let mut builder = Js2Rust::new("", self.cx);
if mutable {
@ -274,10 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
} else {
builder.rust_argument("this.a");
}
builder
.rust_argument("this.b")
.process(f)?
.finish("function", "this.f")
builder.rust_argument("this.b").process(f)?.finish(
"function",
"this.f",
ExportedShim::TableElement(&mut shim),
)
};
self.cx.function_table_needed = true;
self.global_idx();
@ -291,7 +303,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
abi,
arg2,
js = js,
idx = f.shim_idx,
idx = shim,
));
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
@ -349,16 +361,31 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
return Ok(());
}
if ty.is_anyref() {
self.cx.expose_add_heap_object();
if optional {
self.cx.expose_is_like_none();
self.ret_expr = "
const val = JS;
return isLikeNone(val) ? 0 : addHeapObject(val);
"
.to_string();
if self.cx.config.anyref {
if optional {
self.cx.expose_add_to_anyref_table()?;
self.cx.expose_is_like_none();
self.ret_expr = "
const val = JS;
return isLikeNone(val) ? 0 : addToAnyrefTable(val);
"
.to_string();
} else {
self.ret_anyref = true;
self.ret_expr = "return JS;".to_string()
}
} else {
self.ret_expr = "return addHeapObject(JS);".to_string()
self.cx.expose_add_heap_object();
if optional {
self.cx.expose_is_like_none();
self.ret_expr = "
const val = JS;
return isLikeNone(val) ? 0 : addHeapObject(val);
"
.to_string();
} else {
self.ret_expr = "return addHeapObject(JS);".to_string()
}
}
return Ok(());
}
@ -565,6 +592,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
arg_idx: _,
cx: _,
global_idx: _,
anyref_args: _,
ret_anyref: _,
} = self;
!catch &&
@ -581,7 +610,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
js_arguments == shim_arguments
}
pub fn finish(&mut self, invoc: &ImportTarget) -> Result<String, Error> {
pub fn finish(&mut self, invoc: &ImportTarget, shim: &str) -> Result<String, Error> {
let mut ret = String::new();
ret.push_str("function(");
ret.push_str(&self.shim_arguments.join(", "));
@ -596,7 +625,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
let variadic = self.variadic;
let ret_expr = &self.ret_expr;
let js_arguments = &self.js_arguments;
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
let ret = if variadic {
let (last_arg, args) = match js_arguments.split_last() {
@ -617,6 +645,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
Ok(ret)
};
let js_arguments = &self.js_arguments;
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
if variadic {
bail!("{} cannot be variadic", desc);
@ -670,7 +699,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
};
if self.catch {
self.cx.expose_handle_error();
self.cx.expose_handle_error()?;
invoc = format!(
"\
try {{\n\
@ -712,6 +741,33 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
ret.push_str(&invoc);
ret.push_str("\n}\n");
if self.ret_anyref || self.anyref_args.len() > 0 {
// Some return values go at the the beginning of the argument list
// (they force a return pointer). Handle that here by offsetting all
// our arg indices by one, but throw in some sanity checks for if
// this ever changes.
if let Some(start) = self.shim_arguments.get(0) {
if start == "ret" {
assert!(!self.ret_anyref);
if let Some(next) = self.shim_arguments.get(1) {
assert_eq!(next, "arg0");
}
for (idx, _) in self.anyref_args.iter_mut() {
*idx += 1;
}
} else {
assert_eq!(start, "arg0");
}
}
self.cx.anyref.import_xform(
"__wbindgen_placeholder__",
shim,
&self.anyref_args,
self.ret_anyref,
);
}
Ok(ret)
}

View File

@ -35,6 +35,7 @@ pub struct Bindgen {
// Experimental support for the wasm threads proposal, transforms the wasm
// module to be "ready to be instantiated on any thread"
threads: Option<wasm_bindgen_threads_xform::Config>,
anyref: bool,
}
enum Input {
@ -62,6 +63,7 @@ impl Bindgen {
emit_start: true,
weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
threads: threads_config(),
anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(),
}
}
@ -176,6 +178,22 @@ impl Bindgen {
(module, stem)
}
};
// This isn't the hardest thing in the world too support but we
// basically don't know how to rationalize #[wasm_bindgen(start)] and
// the actual `start` function if present. Figure this out later if it
// comes up, but otherwise we should continue to be compatible with
// LLVM's output today.
//
// Note that start function handling in `js/mod.rs` will need to be
// updated as well, because `#[wasm_bindgen(start)]` is inserted *after*
// a module's start function, if any, because we assume start functions
// only show up when injected on behalf of wasm-bindgen's passes.
if module.start.is_some() {
bail!("wasm-bindgen is currently incompatible with modules that \
already have a start function");
}
let mut program_storage = Vec::new();
let programs = extract_programs(&mut module, &mut program_storage)
.with_context(|_| "failed to extract wasm-bindgen custom sections")?;
@ -233,7 +251,10 @@ impl Bindgen {
imported_statics: Default::default(),
direct_imports: Default::default(),
start: None,
anyref: Default::default(),
};
cx.anyref.enabled = self.anyref;
cx.anyref.prepare(cx.module)?;
for program in programs.iter() {
js::SubContext {
program,

View File

@ -191,7 +191,10 @@ fn set_f64() {
Reflect::set_f64(&a, 0.0, &JsValue::from_str("Bye!")).unwrap();
assert_eq!(Reflect::get_f64(&a, 0.0).unwrap(), JsValue::from_str("Bye!"));
assert_eq!(
Reflect::get_f64(&a, 0.0).unwrap(),
JsValue::from_str("Bye!")
);
}
#[wasm_bindgen_test]

View File

@ -27,6 +27,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
"wasm-bindgen-wasm-interpreter",
"wasm-bindgen-webidl",
"wasm-bindgen-threads-xform",
"wasm-bindgen-anyref-xform",
"wasm-bindgen-cli-support",
"wasm-bindgen-cli",
"wasm-bindgen",

208
src/anyref.rs Normal file
View File

@ -0,0 +1,208 @@
use std::slice;
use std::vec::Vec;
use std::ptr;
use std::alloc::{self, Layout};
use std::mem;
use JsValue;
externs! {
#[link(wasm_import_module = "__wbindgen_anyref_xform__")]
extern "C" {
fn __wbindgen_anyref_table_grow(delta: usize) -> i32;
fn __wbindgen_anyref_table_set_null(idx: usize) -> ();
}
}
pub struct Slab {
data: Vec<usize>,
head: usize,
base: usize,
}
impl Slab {
fn new() -> Slab {
Slab {
data: Vec::new(),
head: 0,
base: 0,
}
}
fn alloc(&mut self) -> usize {
let ret = self.head;
if ret == self.data.len() {
if self.data.len() == self.data.capacity() {
let extra = 128;
let r = unsafe {
__wbindgen_anyref_table_grow(extra)
};
if r == -1 {
internal_error("table grow failure")
}
if self.base == 0 {
self.base = r as usize + (super::JSIDX_RESERVED as usize);
} else if self.base + self.data.len() != r as usize {
internal_error("someone else allocated table entires?")
}
// poor man's `try_reserve_exact` until that's stable
unsafe {
let new_cap = self.data.capacity() + extra;
let size = mem::size_of::<usize>() * new_cap;
let align = mem::align_of::<usize>();
let layout = match Layout::from_size_align(size, align) {
Ok(l) => l,
Err(_) => internal_error("size/align layout failure"),
};
let ptr = alloc::alloc(layout) as *mut usize;
if ptr.is_null() {
internal_error("allocation failure");
}
ptr::copy_nonoverlapping(
self.data.as_ptr(),
ptr,
self.data.len(),
);
let new_vec = Vec::from_raw_parts(
ptr,
self.data.len(),
new_cap,
);
let mut old = mem::replace(&mut self.data, new_vec);
old.set_len(0);
}
}
// custom condition to ensure `push` below doesn't call `reserve` in
// optimized builds which pulls in lots of panic infrastructure
if self.data.len() >= self.data.capacity() {
internal_error("push should be infallible now")
}
self.data.push(ret + 1);
}
// usage of `get_mut` thwarts panicking infrastructure in optimized
// builds
match self.data.get_mut(ret) {
Some(slot) => self.head = *slot,
None => internal_error("ret out of bounds"),
}
ret + self.base
}
fn dealloc(&mut self, slot: usize) {
if slot < self.base {
internal_error("free reserved slot");
}
let slot = slot - self.base;
// usage of `get_mut` thwarts panicking infrastructure in optimized
// builds
match self.data.get_mut(slot) {
Some(ptr) => {
*ptr = self.head;
self.head = slot;
}
None => internal_error("slot out of bounds"),
}
}
}
fn internal_error(msg: &str) -> ! {
let msg = if cfg!(debug_assertions) { msg } else { "" };
super::throw_str(msg)
}
// Whoa, there's two `tl` modules here! That's currently intention, but for sort
// of a weird reason. The table here is fundamentally thread local, so we want
// to use the `thread_local!` macro. The implementation of thread locals (as of
// the time of this writing) generates a lot of code as it pulls in panic paths
// in libstd (even when using `try_with`). There's a patch to fix that
// (rust-lang/rust#55518), but in the meantime the stable/beta channels produce
// a lot of code.
//
// Matters are made worse here because this code is almost never used (it's only
// here for an unstable feature). If we were to have panics here, though, then
// we couldn't effectively gc away the panic infrastructure, meaning this unused
// infrastructure would show up in binaries! That's a no-no for wasm-bindgen.
//
// In the meantime, if the atomics feature is turned on (which it never is by
// default) then we use `thread_local!`, otherwise we use a home-grown
// implementation that will be replaced once #55518 lands on stable.
#[cfg(target_feature = "atomics")]
mod tl {
use std::*; // hack to get `thread_local!` to work
use super::Slab;
use std::cell::Cell;
thread_local!(pub static HEAP_SLAB: Cell<Slab> = Cell::new(Slab::new()));
}
#[cfg(not(target_feature = "atomics"))]
mod tl {
use std::alloc::{self, Layout};
use std::cell::Cell;
use std::ptr;
use super::Slab;
pub struct HeapSlab;
pub static HEAP_SLAB: HeapSlab = HeapSlab;
static mut SLOT: *mut Cell<Slab> = 0 as *mut Cell<Slab>;
impl HeapSlab {
pub fn try_with<R>(&self, f: impl FnOnce(&Cell<Slab>) -> R) -> Result<R, ()> {
unsafe {
if SLOT.is_null() {
let ptr = alloc::alloc(Layout::new::<Cell<Slab>>());
if ptr.is_null() {
super::internal_error("allocation failure");
}
let ptr = ptr as *mut Cell<Slab>;
ptr::write(ptr, Cell::new(Slab::new()));
SLOT = ptr;
}
Ok(f(&*SLOT))
}
}
}
}
#[no_mangle]
pub extern fn __wbindgen_anyref_table_alloc() -> usize {
tl::HEAP_SLAB.try_with(|slot| {
let mut slab = slot.replace(Slab::new());
let ret = slab.alloc();
slot.replace(slab);
ret
}).unwrap_or_else(|_| internal_error("tls access failure"))
}
#[no_mangle]
pub extern fn __wbindgen_anyref_table_dealloc(idx: usize) {
if idx < super::JSIDX_RESERVED as usize {
return
}
// clear this value from the table so while the table slot is un-allocated
// we don't keep around a strong reference to a potentially large object
unsafe {
__wbindgen_anyref_table_set_null(idx);
}
tl::HEAP_SLAB.try_with(|slot| {
let mut slab = slot.replace(Slab::new());
slab.dealloc(idx);
slot.replace(slab);
}).unwrap_or_else(|_| internal_error("tls access failure"))
}
#[no_mangle]
pub unsafe extern fn __wbindgen_drop_anyref_slice(ptr: *mut JsValue, len: usize) {
for slot in slice::from_raw_parts_mut(ptr, len) {
ptr::drop_in_place(slot);
}
}
// see comment in module above this in `link_mem_intrinsics`
#[inline(never)]
pub fn link_intrinsics() {
}

View File

@ -30,6 +30,24 @@ macro_rules! if_std {
)*)
}
macro_rules! externs {
($(#[$attr:meta])* extern "C" { $(fn $name:ident($($args:tt)*) -> $ret:ty;)* }) => (
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
$(#[$attr])*
extern "C" {
$(fn $name($($args)*) -> $ret;)*
}
$(
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
#[allow(unused_variables)]
unsafe extern fn $name($($args)*) -> $ret {
panic!("function not implemented on non-wasm32 targets")
}
)*
)
}
/// A module which is typically glob imported from:
///
/// ```
@ -57,6 +75,7 @@ if_std! {
extern crate std;
use std::prelude::v1::*;
pub mod closure;
mod anyref;
}
/// Representation of an object owned by JS.
@ -462,58 +481,44 @@ macro_rules! numbers {
numbers! { i8 u8 i16 u16 i32 u32 f32 f64 }
macro_rules! externs {
($(fn $name:ident($($args:tt)*) -> $ret:ty;)*) => (
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
#[link(wasm_import_module = "__wbindgen_placeholder__")]
extern "C" {
$(fn $name($($args)*) -> $ret;)*
}
$(
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
#[allow(unused_variables)]
unsafe extern "C" fn $name($($args)*) -> $ret {
panic!("function not implemented on non-wasm32 targets")
}
)*
)
}
externs! {
fn __wbindgen_object_clone_ref(idx: u32) -> u32;
fn __wbindgen_object_drop_ref(idx: u32) -> ();
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_number_new(f: f64) -> u32;
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
fn __wbindgen_is_null(idx: u32) -> u32;
fn __wbindgen_is_undefined(idx: u32) -> u32;
fn __wbindgen_boolean_get(idx: u32) -> u32;
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_is_symbol(idx: u32) -> u32;
fn __wbindgen_is_object(idx: u32) -> u32;
fn __wbindgen_is_function(idx: u32) -> u32;
fn __wbindgen_is_string(idx: u32) -> u32;
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
fn __wbindgen_rethrow(a: u32) -> !;
#[link(wasm_import_module = "__wbindgen_placeholder__")]
extern "C" {
fn __wbindgen_object_clone_ref(idx: u32) -> u32;
fn __wbindgen_object_drop_ref(idx: u32) -> ();
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_number_new(f: f64) -> u32;
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
fn __wbindgen_is_null(idx: u32) -> u32;
fn __wbindgen_is_undefined(idx: u32) -> u32;
fn __wbindgen_boolean_get(idx: u32) -> u32;
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_is_symbol(idx: u32) -> u32;
fn __wbindgen_is_object(idx: u32) -> u32;
fn __wbindgen_is_function(idx: u32) -> u32;
fn __wbindgen_is_string(idx: u32) -> u32;
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8;
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
fn __wbindgen_rethrow(a: u32) -> !;
fn __wbindgen_cb_drop(idx: u32) -> u32;
fn __wbindgen_cb_forget(idx: u32) -> ();
fn __wbindgen_cb_drop(idx: u32) -> u32;
fn __wbindgen_cb_forget(idx: u32) -> ();
fn __wbindgen_describe(v: u32) -> ();
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_describe(v: u32) -> ();
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
fn __wbindgen_memory() -> u32;
fn __wbindgen_module() -> u32;
fn __wbindgen_memory() -> u32;
fn __wbindgen_module() -> u32;
}
}
impl Clone for JsValue {
#[inline]
fn clone(&self) -> JsValue {
unsafe {
let idx = __wbindgen_object_clone_ref(self.idx);
@ -973,7 +978,9 @@ pub mod __rt {
/// in the object file and link the intrinsics.
///
/// Ideas for how to improve this are most welcome!
pub fn link_mem_intrinsics() {}
pub fn link_mem_intrinsics() {
::anyref::link_intrinsics();
}
}
/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`