mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-28 07:51:07 +00:00
Merge pull request #866 from alexcrichton/fix-lto
More conservatively patch closure descriptors
This commit is contained in:
commit
ebe73a537c
@ -47,6 +47,7 @@ pub fn rewrite(input: &mut Context) -> Result<(), Error> {
|
|||||||
|
|
||||||
info.delete_function_table_entries(input);
|
info.delete_function_table_entries(input);
|
||||||
info.inject_imports(input)?;
|
info.inject_imports(input)?;
|
||||||
|
info.rewrite_calls(input);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +64,13 @@ struct ClosureDescriptors {
|
|||||||
///
|
///
|
||||||
/// This map is later used to replace all calls to the keys of this map with
|
/// This map is later used to replace all calls to the keys of this map with
|
||||||
/// calls to the value of the map.
|
/// calls to the value of the map.
|
||||||
code_idx_to_descriptor: BTreeMap<u32, (u32, Descriptor)>,
|
code_idx_to_descriptor: BTreeMap<u32, DescribeInstruction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DescribeInstruction {
|
||||||
|
new_idx: u32,
|
||||||
|
instr_idx: usize,
|
||||||
|
descriptor: Descriptor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClosureDescriptors {
|
impl ClosureDescriptors {
|
||||||
@ -93,19 +100,19 @@ impl ClosureDescriptors {
|
|||||||
None => return Default::default(),
|
None => return Default::default(),
|
||||||
};
|
};
|
||||||
for (i, function) in code.bodies().iter().enumerate() {
|
for (i, function) in code.bodies().iter().enumerate() {
|
||||||
let mut call_found = false;
|
let call_pos = function.code()
|
||||||
for instruction in function.code().elements() {
|
.elements()
|
||||||
match instruction {
|
.iter()
|
||||||
Instruction::Call(idx) if *idx == wbindgen_describe_closure => {
|
.position(|i| {
|
||||||
call_found = true;
|
match i {
|
||||||
break
|
Instruction::Call(i) => *i == wbindgen_describe_closure,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
_ => {}
|
});
|
||||||
}
|
let call_pos = match call_pos {
|
||||||
}
|
Some(i) => i,
|
||||||
if !call_found {
|
None => continue,
|
||||||
continue
|
};
|
||||||
}
|
|
||||||
let descriptor = input.interpreter.interpret_closure_descriptor(
|
let descriptor = input.interpreter.interpret_closure_descriptor(
|
||||||
i,
|
i,
|
||||||
input.module,
|
input.module,
|
||||||
@ -118,7 +125,11 @@ impl ClosureDescriptors {
|
|||||||
let new_idx = (ret.code_idx_to_descriptor.len() + imports) as u32;
|
let new_idx = (ret.code_idx_to_descriptor.len() + imports) as u32;
|
||||||
ret.code_idx_to_descriptor.insert(
|
ret.code_idx_to_descriptor.insert(
|
||||||
i as u32,
|
i as u32,
|
||||||
(new_idx, Descriptor::decode(descriptor)),
|
DescribeInstruction {
|
||||||
|
new_idx,
|
||||||
|
instr_idx: call_pos,
|
||||||
|
descriptor: Descriptor::decode(descriptor),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
@ -189,23 +200,26 @@ impl ClosureDescriptors {
|
|||||||
/// described by the fields internally. These new imports will be closure
|
/// described by the fields internally. These new imports will be closure
|
||||||
/// factories and are freshly generated shim in JS.
|
/// factories and are freshly generated shim in JS.
|
||||||
fn inject_imports(&self, input: &mut Context) -> Result<(), Error> {
|
fn inject_imports(&self, input: &mut Context) -> Result<(), Error> {
|
||||||
|
let wbindgen_describe_closure = match input.interpreter.describe_closure_idx() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
// We'll be injecting new imports and we'll need to give them all a
|
// We'll be injecting new imports and we'll need to give them all a
|
||||||
// type. The signature is all `(i32, i32) -> i32` currently and we know
|
// type. The signature is all `(i32, i32) -> i32` currently and we know
|
||||||
// that this signature already exists in the module as it's the
|
// that this signature already exists in the module as it's the
|
||||||
// signature of our `#[inline(never)]` functions. Find the type
|
// signature of our `#[inline(never)]` functions. Find the type
|
||||||
// signature index so we can assign it below.
|
// signature index so we can assign it below.
|
||||||
let type_idx = input.module.type_section()
|
let type_idx = {
|
||||||
.unwrap()
|
let kind = input.module.import_section()
|
||||||
.types()
|
.unwrap()
|
||||||
.iter()
|
.entries()[wbindgen_describe_closure as usize]
|
||||||
.position(|ty| {
|
.external();
|
||||||
let fnty = match ty {
|
match kind {
|
||||||
Type::Function(f) => f,
|
External::Function(i) => *i,
|
||||||
};
|
_ => unreachable!(),
|
||||||
fnty.params() == &[ValueType::I32, ValueType::I32] &&
|
}
|
||||||
fnty.return_type() == Some(ValueType::I32)
|
};
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// The last piece of the magic. For all our descriptors we found we
|
// The last piece of the magic. For all our descriptors we found we
|
||||||
// inject a JS shim for the descriptor. This JS shim will manufacture a
|
// inject a JS shim for the descriptor. This JS shim will manufacture a
|
||||||
@ -214,10 +228,10 @@ impl ClosureDescriptors {
|
|||||||
// Once all that's said and done we inject a new import into the wasm module
|
// Once all that's said and done we inject a new import into the wasm module
|
||||||
// of our new wrapper, and the `Remap` step above already wrote calls to
|
// of our new wrapper, and the `Remap` step above already wrote calls to
|
||||||
// this function within the module.
|
// this function within the module.
|
||||||
for (i, (_new_idx, descriptor)) in self.code_idx_to_descriptor.iter() {
|
for (i, instr) in self.code_idx_to_descriptor.iter() {
|
||||||
let import_name = format!("__wbindgen_closure_wrapper{}", i);
|
let import_name = format!("__wbindgen_closure_wrapper{}", i);
|
||||||
|
|
||||||
let closure = descriptor.closure().unwrap();
|
let closure = instr.descriptor.closure().unwrap();
|
||||||
|
|
||||||
let (js, _ts, _js_doc) = {
|
let (js, _ts, _js_doc) = {
|
||||||
let mut builder = Js2Rust::new("", input);
|
let mut builder = Js2Rust::new("", input);
|
||||||
@ -237,7 +251,7 @@ impl ClosureDescriptors {
|
|||||||
input.expose_add_heap_object();
|
input.expose_add_heap_object();
|
||||||
input.function_table_needed = true;
|
input.function_table_needed = true;
|
||||||
let body = format!(
|
let body = format!(
|
||||||
"function(ptr, f) {{
|
"function(ptr, f, _ignored) {{
|
||||||
let cb = {};
|
let cb = {};
|
||||||
cb.f = wasm.__wbg_function_table.get(f);
|
cb.f = wasm.__wbg_function_table.get(f);
|
||||||
cb.a = ptr;
|
cb.a = ptr;
|
||||||
@ -261,10 +275,34 @@ impl ClosureDescriptors {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The final step, rewriting calls to `__wbindgen_describe_closure` to the
|
||||||
|
/// imported functions
|
||||||
|
fn rewrite_calls(&self, input: &mut Context) {
|
||||||
|
// FIXME: Ok so this is a bit sketchy in that it introduces overhead.
|
||||||
|
// What we're doing is taking a our #[inline(never)] shim and *not*
|
||||||
|
// removing it, only switching the one function that it calls internally.
|
||||||
|
//
|
||||||
|
// This isn't great because now we have this non-inlined function which
|
||||||
|
// would certainly benefit from getting inlined. It's a tiny function
|
||||||
|
// though and surrounded by allocation so it's probably not a huge
|
||||||
|
// problem in the long run. Note that `wasm-opt` also implements
|
||||||
|
// inlining, so we can likely rely on that too.
|
||||||
|
//
|
||||||
|
// Still though, it'd be great to not only delete calls to
|
||||||
|
// `__wbindgen_describe_closure`, it'd be great to remove all of the
|
||||||
|
// `breaks_if_inlined` functions entirely.
|
||||||
|
let code = input.module.code_section_mut().unwrap();
|
||||||
|
for (i, instr) in self.code_idx_to_descriptor.iter() {
|
||||||
|
let func = &mut code.bodies_mut()[*i as usize];
|
||||||
|
let new_instr = Instruction::Call(instr.new_idx);
|
||||||
|
func.code_mut().elements_mut()[instr.instr_idx] = new_instr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Remap<'a> {
|
struct Remap<'a> {
|
||||||
code_idx_to_descriptor: &'a BTreeMap<u32, (u32, Descriptor)>,
|
code_idx_to_descriptor: &'a BTreeMap<u32, DescribeInstruction>,
|
||||||
old_num_imports: u32,
|
old_num_imports: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,19 +405,8 @@ impl<'a> Remap<'a> {
|
|||||||
if *idx < self.old_num_imports {
|
if *idx < self.old_num_imports {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
let code_idx = *idx - self.old_num_imports;
|
// ... otherwise we're injecting a number of new imports, so offset
|
||||||
|
// everything.
|
||||||
// If this `idx` points to a function which was effectively a descriptor
|
|
||||||
// function, then we want to re-point it to our imported function which
|
|
||||||
// is actually the shim factory.
|
|
||||||
if let Some((new_idx, _)) = self.code_idx_to_descriptor.get(&code_idx) {
|
|
||||||
*idx = *new_idx;
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// And finally, otherwise this is just a normal function reference we
|
|
||||||
// don't want to touch, but we're injecting imports which shifts all
|
|
||||||
// function indices.
|
|
||||||
*idx += self.code_idx_to_descriptor.len() as u32;
|
*idx += self.code_idx_to_descriptor.len() as u32;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -226,14 +226,22 @@ impl Interpreter {
|
|||||||
) -> Option<&[u32]> {
|
) -> Option<&[u32]> {
|
||||||
// Call the `code_idx` function. This is an internal `#[inline(never)]`
|
// Call the `code_idx` function. This is an internal `#[inline(never)]`
|
||||||
// whose code is completely controlled by the `wasm-bindgen` crate, so
|
// whose code is completely controlled by the `wasm-bindgen` crate, so
|
||||||
// it should take two arguments and return one (all of which we don't
|
// it should take some arguments (the number of arguments depends on the
|
||||||
// care about here). What we're interested in is that while executing
|
// optimization level) and return one (all of which we don't care about
|
||||||
// this function it'll call `__wbindgen_describe_closure` with an
|
// here). What we're interested in is that while executing this function
|
||||||
// argument that we look for.
|
// it'll call `__wbindgen_describe_closure` with an argument that we
|
||||||
|
// look for.
|
||||||
assert!(self.descriptor_table_idx.is_none());
|
assert!(self.descriptor_table_idx.is_none());
|
||||||
let closure_descriptor_idx = (code_idx + self.imports) as u32;
|
let closure_descriptor_idx = (code_idx + self.imports) as u32;
|
||||||
self.stack.push(0);
|
|
||||||
self.stack.push(0);
|
let code_sig = sections.functions.entries()[code_idx].type_ref();
|
||||||
|
let function_ty = match §ions.types.types()[code_sig as usize] {
|
||||||
|
Type::Function(t) => t,
|
||||||
|
};
|
||||||
|
for _ in 0..function_ty.params().len() {
|
||||||
|
self.stack.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
self.call(closure_descriptor_idx, sections);
|
self.call(closure_descriptor_idx, sections);
|
||||||
assert_eq!(self.stack.len(), 1);
|
assert_eq!(self.stack.len(), 1);
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
@ -346,8 +354,8 @@ impl Interpreter {
|
|||||||
} else if Some(*idx) == self.describe_closure_idx {
|
} else if Some(*idx) == self.describe_closure_idx {
|
||||||
self.descriptor_table_idx =
|
self.descriptor_table_idx =
|
||||||
Some(self.stack.pop().unwrap() as u32);
|
Some(self.stack.pop().unwrap() as u32);
|
||||||
assert_eq!(self.stack.pop(), Some(0));
|
self.stack.pop();
|
||||||
assert_eq!(self.stack.pop(), Some(0));
|
self.stack.pop();
|
||||||
self.stack.push(0);
|
self.stack.push(0);
|
||||||
} else {
|
} else {
|
||||||
self.call(*idx, sections);
|
self.call(*idx, sections);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user