1 use crate::TRAP_INTERNAL_ASSERT;
2 use crate::compiler::Compiler;
3 use cranelift_codegen::cursor::FuncCursor;
4 use cranelift_codegen::ir::condcodes::IntCC;
5 use cranelift_codegen::ir::types::I8;
6 use cranelift_codegen::ir::{self, InstBuilder};
7 use cranelift_frontend::FunctionBuilder;
8 use wasmtime_environ::{BuiltinFunctionIndex, TripleExt};
9 
10 /// Helper trait to share translation of traps between core functions and
11 /// component trampolines.
12 ///
13 /// Traps are conditionally performed as libcalls when signals-based-traps are
14 /// disabled, for example, but otherwise use the native CLIF `trap` instruction.
15 pub trait TranslateTrap {
compiler(&self) -> &Compiler16     fn compiler(&self) -> &Compiler;
vmctx_val(&mut self, cursor: &mut FuncCursor<'_>) -> ir::Value17     fn vmctx_val(&mut self, cursor: &mut FuncCursor<'_>) -> ir::Value;
builtin_funcref( &mut self, builder: &mut FunctionBuilder<'_>, index: BuiltinFunctionIndex, ) -> ir::FuncRef18     fn builtin_funcref(
19         &mut self,
20         builder: &mut FunctionBuilder<'_>,
21         index: BuiltinFunctionIndex,
22     ) -> ir::FuncRef;
debug_tags(&self, _srcloc: ir::SourceLoc) -> Vec<ir::DebugTag>23     fn debug_tags(&self, _srcloc: ir::SourceLoc) -> Vec<ir::DebugTag> {
24         vec![]
25     }
26 
trap(&mut self, builder: &mut FunctionBuilder, trap: ir::TrapCode)27     fn trap(&mut self, builder: &mut FunctionBuilder, trap: ir::TrapCode) {
28         match (
29             self.clif_instruction_traps_enabled(),
30             crate::clif_trap_to_env_trap(trap),
31         ) {
32             // If libcall traps are disabled or there's no wasmtime-defined trap
33             // code for this, then emit a native trap instruction.
34             (true, _) | (_, None) => {
35                 builder.ins().trap(trap);
36             }
37             // ... otherwise with libcall traps explicitly enabled and a
38             // wasmtime-based trap code invoke the libcall to raise a trap and
39             // pass in our trap code. Leave a debug `unreachable` in place
40             // afterwards as a defense-in-depth measure.
41             (false, Some(trap)) => {
42                 let debug_tags = self.debug_tags(builder.srcloc());
43                 let trap_libcall = self.builtin_funcref(builder, BuiltinFunctionIndex::trap());
44                 let vmctx = self.vmctx_val(&mut builder.cursor());
45                 let trap_code = builder.ins().iconst(I8, i64::from(trap as u8));
46                 builder.ins().call(trap_libcall, &[vmctx, trap_code]);
47                 let raise_libcall = self.builtin_funcref(builder, BuiltinFunctionIndex::raise());
48                 let inst = builder.ins().call(raise_libcall, &[vmctx]);
49                 builder.func.debug_tags.set(inst, debug_tags);
50                 builder.ins().trap(TRAP_INTERNAL_ASSERT);
51             }
52         }
53     }
54 
trapz(&mut self, builder: &mut FunctionBuilder, value: ir::Value, trap: ir::TrapCode)55     fn trapz(&mut self, builder: &mut FunctionBuilder, value: ir::Value, trap: ir::TrapCode) {
56         if self.clif_instruction_traps_enabled() {
57             builder.ins().trapz(value, trap);
58         } else {
59             let ty = builder.func.dfg.value_type(value);
60             let zero = builder.ins().iconst(ty, 0);
61             let cmp = builder.ins().icmp(IntCC::Equal, value, zero);
62             self.conditionally_trap(builder, cmp, trap);
63         }
64     }
65 
trapnz(&mut self, builder: &mut FunctionBuilder, value: ir::Value, trap: ir::TrapCode)66     fn trapnz(&mut self, builder: &mut FunctionBuilder, value: ir::Value, trap: ir::TrapCode) {
67         if self.clif_instruction_traps_enabled() {
68             builder.ins().trapnz(value, trap);
69         } else {
70             let ty = builder.func.dfg.value_type(value);
71             let zero = builder.ins().iconst(ty, 0);
72             let cmp = builder.ins().icmp(IntCC::NotEqual, value, zero);
73             self.conditionally_trap(builder, cmp, trap);
74         }
75     }
76 
uadd_overflow_trap( &mut self, builder: &mut FunctionBuilder, lhs: ir::Value, rhs: ir::Value, trap: ir::TrapCode, ) -> ir::Value77     fn uadd_overflow_trap(
78         &mut self,
79         builder: &mut FunctionBuilder,
80         lhs: ir::Value,
81         rhs: ir::Value,
82         trap: ir::TrapCode,
83     ) -> ir::Value {
84         if self.clif_instruction_traps_enabled() {
85             builder.ins().uadd_overflow_trap(lhs, rhs, trap)
86         } else {
87             let (ret, overflow) = builder.ins().uadd_overflow(lhs, rhs);
88             self.conditionally_trap(builder, overflow, trap);
89             ret
90         }
91     }
92 
93     /// Helper to emit a conditional trap based on `trap_cond`.
94     ///
95     /// This should only be used if `self.clif_instruction_traps_enabled()` is
96     /// false, otherwise native CLIF instructions should be used instead.
conditionally_trap( &mut self, builder: &mut FunctionBuilder, trap_cond: ir::Value, trap: ir::TrapCode, )97     fn conditionally_trap(
98         &mut self,
99         builder: &mut FunctionBuilder,
100         trap_cond: ir::Value,
101         trap: ir::TrapCode,
102     ) {
103         assert!(!self.clif_instruction_traps_enabled());
104 
105         let trap_block = builder.create_block();
106         builder.set_cold_block(trap_block);
107         let continuation_block = builder.create_block();
108 
109         builder
110             .ins()
111             .brif(trap_cond, trap_block, &[], continuation_block, &[]);
112 
113         builder.seal_block(trap_block);
114         builder.seal_block(continuation_block);
115 
116         builder.switch_to_block(trap_block);
117         self.trap(builder, trap);
118         builder.switch_to_block(continuation_block);
119     }
120 
121     /// Returns whether it's acceptable to have CLIF instructions natively trap,
122     /// such as division-by-zero.
123     ///
124     /// This is enabled if `signals_based_traps` is `true` or on
125     /// Pulley unconditionally since Pulley doesn't use hardware-based
126     /// traps in its runtime. However, if guest debugging is enabled,
127     /// then we cannot rely on Pulley traps and still need a libcall
128     /// to gain proper ownership of the store in the runtime's
129     /// debugger hooks.
clif_instruction_traps_enabled(&self) -> bool130     fn clif_instruction_traps_enabled(&self) -> bool {
131         let tunables = self.compiler().tunables();
132         tunables.signals_based_traps || (self.is_pulley() && !tunables.debug_guest)
133     }
134 
135     /// Returns whether translation is happening for Pulley bytecode.
is_pulley(&self) -> bool136     fn is_pulley(&self) -> bool {
137         self.compiler().isa().triple().is_pulley()
138     }
139 }
140