1 /* Copyright 2018 Mozilla Foundation
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 //! Cranelift WebAssembly function compiler.
17 //!
18 //! This module defines the `compile()` function which uses Cranelift to compile a single
19 //! WebAssembly function.
20 
21 use log::{debug, info};
22 use std::fmt;
23 use std::mem;
24 
25 use cranelift_codegen::binemit::{
26     Addend, CodeInfo, CodeOffset, NullStackmapSink, Reloc, RelocSink, Stackmap, TrapSink,
27 };
28 use cranelift_codegen::entity::EntityRef;
29 use cranelift_codegen::ir::{
30     self, constant::ConstantOffset, stackslot::StackSize, ExternalName, JumpTable, SourceLoc,
31     TrapCode,
32 };
33 use cranelift_codegen::isa::TargetIsa;
34 use cranelift_codegen::CodegenResult;
35 use cranelift_codegen::Context;
36 use cranelift_wasm::{FuncIndex, FuncTranslator, ModuleTranslationState, WasmResult};
37 
38 use crate::bindings;
39 use crate::isa::make_isa;
40 use crate::utils::DashResult;
41 use crate::wasm2clif::{init_sig, TransEnv, TRAP_THROW_REPORTED};
42 
43 // Namespace for user-defined functions.
44 const USER_FUNCTION_NAMESPACE: u32 = 0;
45 
46 // Namespace for builtins functions that are translated to symbolic accesses in Spidermonkey.
47 const SYMBOLIC_FUNCTION_NAMESPACE: u32 = 1;
48 
49 /// The result of a function's compilation: code + metadata.
50 pub struct CompiledFunc {
51     pub frame_pushed: StackSize,
52     pub contains_calls: bool,
53     pub metadata: Vec<bindings::MetadataEntry>,
54     // rodata_relocs is Vec<CodeOffset>, but u32 is C++-friendlier
55     pub rodata_relocs: Vec<u32>,
56     // TODO(bbouvier) should just be a pointer into the masm buffer
57     pub code_buffer: Vec<u8>,
58     pub code_size: CodeOffset,
59     pub jumptables_size: CodeOffset,
60     pub rodata_size: CodeOffset,
61 }
62 
63 impl CompiledFunc {
new() -> Self64     fn new() -> Self {
65         Self {
66             frame_pushed: 0,
67             contains_calls: false,
68             metadata: vec![],
69             rodata_relocs: vec![],
70             code_buffer: vec![],
71             code_size: 0,
72             jumptables_size: 0,
73             rodata_size: 0,
74         }
75     }
76 
clear(&mut self)77     fn clear(&mut self) {
78         self.frame_pushed = 0;
79         self.contains_calls = false;
80         self.metadata.clear();
81         self.rodata_relocs.clear();
82         self.code_buffer.clear();
83         self.code_size = 0;
84         self.jumptables_size = 0;
85         self.rodata_size = 0;
86     }
87 }
88 
89 /// A batch compiler holds on to data structures that can be recycled for multiple function
90 /// compilations.
91 pub struct BatchCompiler<'static_env, 'module_env> {
92     // Attributes that are constant accross multiple compilations.
93     static_environ: &'static_env bindings::StaticEnvironment,
94     environ: bindings::ModuleEnvironment<'module_env>,
95     isa: Box<dyn TargetIsa>,
96 
97     // Stateless attributes.
98     func_translator: FuncTranslator,
99     dummy_module_state: ModuleTranslationState,
100 
101     // Mutable attributes.
102     /// Cranelift overall context.
103     context: Context,
104 
105     /// Temporary storage for trap relocations before they're moved back to the CompiledFunc.
106     trap_relocs: Traps,
107 
108     /// The translation from wasm to clif environment.
109     trans_env: TransEnv<'static_env, 'module_env>,
110 
111     /// Results of the current compilation.
112     pub current_func: CompiledFunc,
113 }
114 
115 impl<'static_env, 'module_env> BatchCompiler<'static_env, 'module_env> {
new( static_environ: &'static_env bindings::StaticEnvironment, environ: bindings::ModuleEnvironment<'module_env>, ) -> DashResult<Self>116     pub fn new(
117         static_environ: &'static_env bindings::StaticEnvironment,
118         environ: bindings::ModuleEnvironment<'module_env>,
119     ) -> DashResult<Self> {
120         let isa = make_isa(static_environ)?;
121         let trans_env = TransEnv::new(&*isa, environ, static_environ);
122         Ok(BatchCompiler {
123             static_environ,
124             environ,
125             isa,
126             func_translator: FuncTranslator::new(),
127             // TODO for Cranelift to support multi-value, feed it the real type section here.
128             dummy_module_state: ModuleTranslationState::new(),
129             context: Context::new(),
130             trap_relocs: Traps::new(),
131             trans_env,
132             current_func: CompiledFunc::new(),
133         })
134     }
135 
136     /// Clears internal data structures.
clear(&mut self)137     pub fn clear(&mut self) {
138         self.context.clear();
139         self.trap_relocs.clear();
140         self.trans_env.clear();
141         self.current_func.clear();
142     }
143 
compile(&mut self, stackmaps: bindings::Stackmaps) -> CodegenResult<()>144     pub fn compile(&mut self, stackmaps: bindings::Stackmaps) -> CodegenResult<()> {
145         let info = self.context.compile(&*self.isa)?;
146         debug!("Optimized wasm function IR: {}", self);
147         self.binemit(info, stackmaps)
148     }
149 
150     /// Translate the WebAssembly code to Cranelift IR.
translate_wasm(&mut self, func: &bindings::FuncCompileInput) -> WasmResult<()>151     pub fn translate_wasm(&mut self, func: &bindings::FuncCompileInput) -> WasmResult<()> {
152         // Set up the signature before translating the WebAssembly byte code.
153         // The translator refers to it.
154         let index = FuncIndex::new(func.index as usize);
155 
156         self.context.func.signature =
157             init_sig(&self.environ, self.static_environ.call_conv(), index)?;
158         self.context.func.name = wasm_function_name(index);
159 
160         self.func_translator.translate(
161             &self.dummy_module_state,
162             func.bytecode(),
163             func.offset_in_module as usize,
164             &mut self.context.func,
165             &mut self.trans_env,
166         )?;
167 
168         info!("Translated wasm function {}.", func.index);
169         debug!("Translated wasm function IR: {}", self);
170         Ok(())
171     }
172 
173     /// Emit binary machine code to `emitter`.
binemit(&mut self, info: CodeInfo, stackmaps: bindings::Stackmaps) -> CodegenResult<()>174     fn binemit(&mut self, info: CodeInfo, stackmaps: bindings::Stackmaps) -> CodegenResult<()> {
175         let total_size = info.total_size as usize;
176         let frame_pushed = self.frame_pushed();
177         let contains_calls = self.contains_calls();
178 
179         info!(
180             "Emitting {} bytes, frame_pushed={}\n.",
181             total_size, frame_pushed
182         );
183 
184         self.current_func.frame_pushed = frame_pushed;
185         self.current_func.contains_calls = contains_calls;
186 
187         // TODO: If we can get a pointer into `size` pre-allocated bytes of memory, we wouldn't
188         // have to allocate and copy here.
189         // TODO(bbouvier) try to get this pointer from the C++ caller, with an unlikely callback to
190         // C++ if the remaining size is smaller than  needed.
191         if self.current_func.code_buffer.len() < total_size {
192             let current_size = self.current_func.code_buffer.len();
193             // There's no way to do a proper uninitialized reserve, so first reserve and then
194             // unsafely set the final size.
195             self.current_func
196                 .code_buffer
197                 .reserve(total_size - current_size);
198             unsafe { self.current_func.code_buffer.set_len(total_size) };
199         }
200 
201         {
202             let mut relocs = Relocations::new(
203                 &mut self.current_func.metadata,
204                 &mut self.current_func.rodata_relocs,
205             );
206 
207             let code_buffer = &mut self.current_func.code_buffer;
208             unsafe {
209                 self.context.emit_to_memory(
210                     &*self.isa,
211                     code_buffer.as_mut_ptr(),
212                     &mut relocs,
213                     &mut self.trap_relocs,
214                     &mut NullStackmapSink {},
215                 )
216             };
217 
218             self.current_func
219                 .metadata
220                 .append(&mut self.trap_relocs.metadata);
221         }
222 
223         if self.static_environ.ref_types_enabled {
224             if self.context.mach_compile_result.is_some() {
225                 // TODO Bug 1633721: new backend: support stackmaps.
226                 log::warn!("new isel backend doesn't support stackmaps yet");
227             } else {
228                 self.emit_stackmaps(stackmaps);
229             }
230         }
231 
232         self.current_func.code_size = info.code_size;
233         self.current_func.jumptables_size = info.jumptables_size;
234         self.current_func.rodata_size = info.rodata_size;
235 
236         Ok(())
237     }
238 
239     /// Iterate over each instruction to generate a stack map for each instruction that needs it.
240     ///
241     /// Note a stackmap is associated to the address of the next instruction following the actual
242     /// instruction needing the stack map. This is because this is the only information
243     /// Spidermonkey has access to when it looks up a stack map (during stack frame iteration).
emit_stackmaps(&self, mut stackmaps: bindings::Stackmaps)244     fn emit_stackmaps(&self, mut stackmaps: bindings::Stackmaps) {
245         let encinfo = self.isa.encoding_info();
246         let func = &self.context.func;
247         let stack_slots = &func.stack_slots;
248         for block in func.layout.blocks() {
249             let mut pending_safepoint = None;
250             for (offset, inst, inst_size) in func.inst_offsets(block, &encinfo) {
251                 if let Some(stackmap) = pending_safepoint.take() {
252                     stackmaps.add_stackmap(stack_slots, offset + inst_size, stackmap);
253                 }
254                 if func.dfg[inst].opcode() == ir::Opcode::Safepoint {
255                     let args = func.dfg.inst_args(inst);
256                     let stackmap = Stackmap::from_values(&args, func, &*self.isa);
257                     pending_safepoint = Some(stackmap);
258                 }
259             }
260             debug_assert!(pending_safepoint.is_none());
261         }
262     }
263 
264     /// Compute the `framePushed` argument to pass to `GenerateFunctionPrologue`. This is the
265     /// number of frame bytes used by Cranelift, not counting the values pushed by the standard
266     /// prologue generated by `GenerateFunctionPrologue`.
frame_pushed(&self) -> StackSize267     fn frame_pushed(&self) -> StackSize {
268         // Cranelift computes the total stack frame size including the pushed return address,
269         // standard SM prologue pushes, and its own stack slots.
270         let total = if let Some(result) = &self.context.mach_compile_result {
271             result.frame_size
272         } else {
273             self.context
274                 .func
275                 .stack_slots
276                 .layout_info
277                 .expect("No frame")
278                 .frame_size
279         };
280 
281         let sm_pushed = StackSize::from(self.isa.flags().baldrdash_prologue_words())
282             * mem::size_of::<usize>() as StackSize;
283 
284         total
285             .checked_sub(sm_pushed)
286             .expect("SpiderMonkey prologue pushes not counted")
287     }
288 
289     /// Determine whether the current function may contain calls.
contains_calls(&self) -> bool290     fn contains_calls(&self) -> bool {
291         // Conservatively, just check to see if it contains any function
292         // signatures which could be called.
293         !self.context.func.dfg.signatures.is_empty()
294     }
295 }
296 
297 impl<'static_env, 'module_env> fmt::Display for BatchCompiler<'static_env, 'module_env> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result298     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299         write!(f, "{}", self.context.func.display(self.isa.as_ref()))
300     }
301 }
302 
303 /// Create a Cranelift function name representing a WebAssembly function with `index`.
wasm_function_name(func: FuncIndex) -> ExternalName304 pub fn wasm_function_name(func: FuncIndex) -> ExternalName {
305     ExternalName::User {
306         namespace: USER_FUNCTION_NAMESPACE,
307         index: func.index() as u32,
308     }
309 }
310 
311 /// Create a Cranelift function name representing a builtin function.
symbolic_function_name(sym: bindings::SymbolicAddress) -> ExternalName312 pub fn symbolic_function_name(sym: bindings::SymbolicAddress) -> ExternalName {
313     ExternalName::User {
314         namespace: SYMBOLIC_FUNCTION_NAMESPACE,
315         index: sym as u32,
316     }
317 }
318 
319 struct Relocations<'a> {
320     metadata: &'a mut Vec<bindings::MetadataEntry>,
321     rodata_relocs: &'a mut Vec<CodeOffset>,
322 }
323 
324 impl<'a> Relocations<'a> {
new( metadata: &'a mut Vec<bindings::MetadataEntry>, rodata_relocs: &'a mut Vec<CodeOffset>, ) -> Self325     fn new(
326         metadata: &'a mut Vec<bindings::MetadataEntry>,
327         rodata_relocs: &'a mut Vec<CodeOffset>,
328     ) -> Self {
329         Self {
330             metadata,
331             rodata_relocs,
332         }
333     }
334 }
335 
336 impl<'a> RelocSink for Relocations<'a> {
337     /// Add a relocation referencing a block at the current offset.
reloc_block(&mut self, _at: CodeOffset, _reloc: Reloc, _block_offset: CodeOffset)338     fn reloc_block(&mut self, _at: CodeOffset, _reloc: Reloc, _block_offset: CodeOffset) {
339         unimplemented!("block relocations NYI");
340     }
341 
342     /// Add a relocation referencing an external symbol at the current offset.
reloc_external( &mut self, at: CodeOffset, srcloc: SourceLoc, reloc: Reloc, name: &ExternalName, _addend: Addend, )343     fn reloc_external(
344         &mut self,
345         at: CodeOffset,
346         srcloc: SourceLoc,
347         reloc: Reloc,
348         name: &ExternalName,
349         _addend: Addend,
350     ) {
351         debug_assert!(!srcloc.is_default());
352 
353         match *name {
354             ExternalName::User {
355                 namespace: USER_FUNCTION_NAMESPACE,
356                 index,
357             } => {
358                 // A simple function call to another wasm function.
359                 let func_index = FuncIndex::new(index as usize);
360 
361                 // On x86, the Spidermonkey relocation must point to the next instruction.
362                 // Cranelift gives us the exact offset to the immediate, so fix it up by the
363                 // relocation's size.
364                 #[cfg(feature = "cranelift_x86")]
365                 let offset = at
366                     + match reloc {
367                         Reloc::X86CallPCRel4 => 4,
368                         _ => unreachable!(),
369                     };
370 
371                 // Spidermonkey Aarch64 requires the relocation to point just after the start of
372                 // the actual relocation, for historical reasons.
373                 #[cfg(feature = "cranelift_arm64")]
374                 let offset = match reloc {
375                     Reloc::Arm64Call => at + 4,
376                     _ => unreachable!(),
377                 };
378 
379                 #[cfg(not(any(feature = "cranelift_x86", feature = "cranelift_arm64")))]
380                 let offset = {
381                     // Avoid warning about unused relocation.
382                     let _reloc = reloc;
383                     at
384                 };
385 
386                 self.metadata.push(bindings::MetadataEntry::direct_call(
387                     offset, srcloc, func_index,
388                 ));
389             }
390 
391             ExternalName::User {
392                 namespace: SYMBOLIC_FUNCTION_NAMESPACE,
393                 index,
394             } => {
395                 // This is a symbolic function reference encoded by `symbolic_function_name()`.
396                 let sym = index.into();
397 
398                 // See comments about offsets in the User match arm above.
399 
400                 #[cfg(feature = "cranelift_x86")]
401                 let offset = at
402                     + match reloc {
403                         Reloc::Abs8 => 8,
404                         _ => unreachable!(),
405                     };
406 
407                 #[cfg(feature = "cranelift_arm64")]
408                 let offset = match reloc {
409                     Reloc::Abs8 => at + 4,
410                     _ => unreachable!(),
411                 };
412 
413                 #[cfg(not(any(feature = "cranelift_x86", feature = "cranelift_arm64")))]
414                 let offset = at;
415 
416                 self.metadata.push(bindings::MetadataEntry::symbolic_access(
417                     offset, srcloc, sym,
418                 ));
419             }
420 
421             ExternalName::LibCall(call) => {
422                 let sym = match call {
423                     ir::LibCall::CeilF32 => bindings::SymbolicAddress::CeilF32,
424                     ir::LibCall::CeilF64 => bindings::SymbolicAddress::CeilF64,
425                     ir::LibCall::FloorF32 => bindings::SymbolicAddress::FloorF32,
426                     ir::LibCall::FloorF64 => bindings::SymbolicAddress::FloorF64,
427                     ir::LibCall::NearestF32 => bindings::SymbolicAddress::NearestF32,
428                     ir::LibCall::NearestF64 => bindings::SymbolicAddress::NearestF64,
429                     ir::LibCall::TruncF32 => bindings::SymbolicAddress::TruncF32,
430                     ir::LibCall::TruncF64 => bindings::SymbolicAddress::TruncF64,
431                     _ => {
432                         panic!("Don't understand external {}", name);
433                     }
434                 };
435 
436                 // The Spidermonkey relocation must point to the next instruction, on x86.
437                 #[cfg(feature = "cranelift_x86")]
438                 let offset = at
439                     + match reloc {
440                         Reloc::Abs8 => 8,
441                         _ => unreachable!(),
442                     };
443 
444                 // Spidermonkey AArch64 doesn't expect a relocation offset, in this case.
445                 #[cfg(feature = "cranelift_arm64")]
446                 let offset = match reloc {
447                     Reloc::Abs8 => at,
448                     _ => unreachable!(),
449                 };
450 
451                 #[cfg(not(any(feature = "cranelift_x86", feature = "cranelift_arm64")))]
452                 let offset = at;
453 
454                 self.metadata.push(bindings::MetadataEntry::symbolic_access(
455                     offset, srcloc, sym,
456                 ));
457             }
458 
459             _ => {
460                 panic!("Don't understand external {}", name);
461             }
462         }
463     }
464 
465     /// Add a relocation referencing a constant.
reloc_constant(&mut self, _at: CodeOffset, _reloc: Reloc, _const_offset: ConstantOffset)466     fn reloc_constant(&mut self, _at: CodeOffset, _reloc: Reloc, _const_offset: ConstantOffset) {
467         unimplemented!("constant pool relocations NYI");
468     }
469 
470     /// Add a relocation referencing a jump table.
reloc_jt(&mut self, at: CodeOffset, reloc: Reloc, _jt: JumpTable)471     fn reloc_jt(&mut self, at: CodeOffset, reloc: Reloc, _jt: JumpTable) {
472         match reloc {
473             Reloc::X86PCRelRodata4 => {
474                 self.rodata_relocs.push(at);
475             }
476             _ => {
477                 panic!("Unhandled/unexpected reloc type");
478             }
479         }
480     }
481 
482     /// Track call sites information, giving us the return address offset.
add_call_site(&mut self, opcode: ir::Opcode, ret_addr: CodeOffset, srcloc: SourceLoc)483     fn add_call_site(&mut self, opcode: ir::Opcode, ret_addr: CodeOffset, srcloc: SourceLoc) {
484         // Direct calls need a plain relocation, so we don't need to handle them again.
485         if opcode == ir::Opcode::CallIndirect {
486             self.metadata
487                 .push(bindings::MetadataEntry::indirect_call(ret_addr, srcloc));
488         }
489     }
490 }
491 
492 struct Traps {
493     metadata: Vec<bindings::MetadataEntry>,
494 }
495 
496 impl Traps {
new() -> Self497     fn new() -> Self {
498         Self {
499             metadata: Vec::new(),
500         }
501     }
clear(&mut self)502     fn clear(&mut self) {
503         self.metadata.clear();
504     }
505 }
506 
507 impl TrapSink for Traps {
508     /// Add trap information for a specific offset.
trap(&mut self, trap_offset: CodeOffset, loc: SourceLoc, trap: TrapCode)509     fn trap(&mut self, trap_offset: CodeOffset, loc: SourceLoc, trap: TrapCode) {
510         // Translate the trap code into one of BaldrMonkey's trap codes.
511         use ir::TrapCode::*;
512         let bd_trap = match trap {
513             StackOverflow => {
514                 // Cranelift will give us trap information for every spill/push/call. But
515                 // Spidermonkey takes care of tracking stack overflows itself in the function
516                 // entries, so we don't have to.
517                 return;
518             }
519             HeapOutOfBounds | OutOfBounds | TableOutOfBounds => bindings::Trap::OutOfBounds,
520             IndirectCallToNull => bindings::Trap::IndirectCallToNull,
521             BadSignature => bindings::Trap::IndirectCallBadSig,
522             IntegerOverflow => bindings::Trap::IntegerOverflow,
523             IntegerDivisionByZero => bindings::Trap::IntegerDivideByZero,
524             BadConversionToInteger => bindings::Trap::InvalidConversionToInteger,
525             Interrupt => bindings::Trap::CheckInterrupt,
526             UnreachableCodeReached => bindings::Trap::Unreachable,
527             User(x) if x == TRAP_THROW_REPORTED => bindings::Trap::ThrowReported,
528             User(_) => panic!("Uncovered trap code {}", trap),
529         };
530 
531         debug_assert!(!loc.is_default());
532         self.metadata
533             .push(bindings::MetadataEntry::trap(trap_offset, loc, bd_trap));
534     }
535 }
536