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