Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/translate/func_translator.rs
3092 views
1
//! Stand-alone WebAssembly to Cranelift IR translator.
2
//!
3
//! This module defines the `FuncTranslator` type which can translate a single WebAssembly
4
//! function to Cranelift IR guided by a `FuncEnvironment` which provides information about the
5
//! WebAssembly module and the runtime environment.
6
7
use crate::func_environ::FuncEnvironment;
8
use crate::translate::TargetEnvironment;
9
use crate::translate::code_translator::{bitcast_wasm_returns, translate_operator};
10
use crate::translate::translation_utils::get_vmctx_value_label;
11
use cranelift_codegen::entity::EntityRef;
12
use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel};
13
use cranelift_codegen::timing;
14
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
15
use wasmparser::{BinaryReader, FuncValidator, FunctionBody, OperatorsReader, WasmModuleResources};
16
use wasmtime_environ::{TypeConvert, WasmResult};
17
18
/// WebAssembly to Cranelift IR function translator.
19
///
20
/// A `FuncTranslator` is used to translate a binary WebAssembly function into Cranelift IR guided
21
/// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple
22
/// functions which will reduce heap allocation traffic.
23
pub struct FuncTranslator {
24
func_ctx: FunctionBuilderContext,
25
}
26
27
impl FuncTranslator {
28
/// Create a new translator.
29
pub fn new() -> Self {
30
Self {
31
func_ctx: FunctionBuilderContext::new(),
32
}
33
}
34
35
/// Returns the underlying `FunctionBuilderContext` that this translator
36
/// uses.
37
pub fn context(&mut self) -> &mut FunctionBuilderContext {
38
&mut self.func_ctx
39
}
40
41
/// Translate a binary WebAssembly function from a `FunctionBody`.
42
///
43
/// See [the WebAssembly specification][wasm].
44
///
45
/// [wasm]: https://webassembly.github.io/spec/core/binary/modules.html#code-section
46
///
47
/// The Cranelift IR function `func` should be completely empty except for the `func.signature`
48
/// and `func.name` fields. The signature may contain special-purpose arguments which are not
49
/// regarded as WebAssembly local variables. Any signature arguments marked as
50
/// `ArgumentPurpose::Normal` are made accessible as WebAssembly local variables.
51
pub fn translate_body(
52
&mut self,
53
validator: &mut FuncValidator<impl WasmModuleResources>,
54
body: FunctionBody<'_>,
55
func: &mut ir::Function,
56
environ: &mut FuncEnvironment<'_>,
57
) -> WasmResult<()> {
58
let _tt = timing::wasm_translate_function();
59
let mut reader = body.get_binary_reader();
60
log::trace!(
61
"translate({} bytes, {}{})",
62
reader.bytes_remaining(),
63
func.name,
64
func.signature
65
);
66
debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty");
67
debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty");
68
69
let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
70
builder.set_srcloc(cur_srcloc(&reader));
71
let entry_block = builder.create_block();
72
builder.append_block_params_for_function_params(entry_block);
73
builder.switch_to_block(entry_block);
74
builder.seal_block(entry_block); // Declare all predecessors known.
75
76
environ.create_state_slot(&mut builder);
77
78
// Make sure the entry block is inserted in the layout before we make any callbacks to
79
// `environ`. The callback functions may need to insert things in the entry block.
80
builder.ensure_inserted_block();
81
82
let num_params = declare_wasm_parameters(&mut builder, entry_block, environ);
83
84
// Set up the translation state with a single pushed control block representing the whole
85
// function and its return values.
86
let exit_block = builder.create_block();
87
builder.append_block_params_for_function_returns(exit_block);
88
environ
89
.stacks
90
.initialize(&builder.func.signature, exit_block);
91
92
parse_local_decls(&mut reader, &mut builder, num_params, environ, validator)?;
93
parse_function_body(validator, reader, &mut builder, environ)?;
94
95
builder.finalize();
96
log::trace!("translated Wasm to CLIF:\n{}", func.display());
97
Ok(())
98
}
99
}
100
101
/// Declare local variables for the signature parameters that correspond to WebAssembly locals.
102
///
103
/// Return the number of local variables declared.
104
fn declare_wasm_parameters(
105
builder: &mut FunctionBuilder,
106
entry_block: Block,
107
environ: &mut FuncEnvironment<'_>,
108
) -> usize {
109
let sig_len = builder.func.signature.params.len();
110
let mut next_local = 0;
111
for i in 0..sig_len {
112
let param_type = builder.func.signature.params[i];
113
// There may be additional special-purpose parameters in addition to the normal WebAssembly
114
// signature parameters. For example, a `vmctx` pointer.
115
if let Some(wasm_type) = environ.clif_param_as_wasm_param(i) {
116
// This is a normal WebAssembly signature parameter, so create a local for it.
117
let local = builder.declare_var(param_type.value_type);
118
debug_assert_eq!(local.index(), next_local);
119
next_local += 1;
120
121
if environ.param_needs_stack_map(&builder.func.signature, i) {
122
builder.declare_var_needs_stack_map(local);
123
}
124
125
let param_value = builder.block_params(entry_block)[i];
126
builder.def_var(local, param_value);
127
128
environ.add_state_slot_local(builder, wasm_type, Some(param_value));
129
}
130
if param_type.purpose == ir::ArgumentPurpose::VMContext {
131
let param_value = builder.block_params(entry_block)[i];
132
builder.set_val_label(param_value, get_vmctx_value_label());
133
}
134
}
135
136
next_local
137
}
138
139
/// Parse the local variable declarations that precede the function body.
140
///
141
/// Declare local variables, starting from `num_params`.
142
fn parse_local_decls(
143
reader: &mut BinaryReader,
144
builder: &mut FunctionBuilder,
145
num_params: usize,
146
environ: &mut FuncEnvironment<'_>,
147
validator: &mut FuncValidator<impl WasmModuleResources>,
148
) -> WasmResult<()> {
149
let mut next_local = num_params;
150
let local_count = reader.read_var_u32()?;
151
152
for _ in 0..local_count {
153
builder.set_srcloc(cur_srcloc(reader));
154
let pos = reader.original_position();
155
let count = reader.read_var_u32()?;
156
let ty = reader.read()?;
157
validator.define_locals(pos, count, ty)?;
158
declare_locals(builder, count, ty, &mut next_local, environ)?;
159
}
160
161
Ok(())
162
}
163
164
/// Declare `count` local variables of the same type, starting from `next_local`.
165
///
166
/// Fail if too many locals are declared in the function, or if the type is not valid for a local.
167
fn declare_locals(
168
builder: &mut FunctionBuilder,
169
count: u32,
170
wasm_type: wasmparser::ValType,
171
next_local: &mut usize,
172
environ: &mut FuncEnvironment<'_>,
173
) -> WasmResult<()> {
174
// All locals are initialized to 0.
175
use wasmparser::ValType::*;
176
let (ty, init, needs_stack_map) = match wasm_type {
177
I32 => (
178
ir::types::I32,
179
Some(builder.ins().iconst(ir::types::I32, 0)),
180
false,
181
),
182
I64 => (
183
ir::types::I64,
184
Some(builder.ins().iconst(ir::types::I64, 0)),
185
false,
186
),
187
F32 => (
188
ir::types::F32,
189
Some(builder.ins().f32const(ir::immediates::Ieee32::with_bits(0))),
190
false,
191
),
192
F64 => (
193
ir::types::F64,
194
Some(builder.ins().f64const(ir::immediates::Ieee64::with_bits(0))),
195
false,
196
),
197
V128 => {
198
let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into());
199
(
200
ir::types::I8X16,
201
Some(builder.ins().vconst(ir::types::I8X16, constant_handle)),
202
false,
203
)
204
}
205
Ref(rt) => {
206
let hty = environ.convert_heap_type(rt.heap_type())?;
207
let (ty, needs_stack_map) = environ.reference_type(hty);
208
let init = if rt.is_nullable() {
209
Some(environ.translate_ref_null(builder.cursor(), hty)?)
210
} else {
211
None
212
};
213
(ty, init, needs_stack_map)
214
}
215
};
216
217
for _ in 0..count {
218
let local = builder.declare_var(ty);
219
debug_assert_eq!(local.index(), *next_local);
220
if needs_stack_map {
221
builder.declare_var_needs_stack_map(local);
222
}
223
if let Some(init) = init {
224
builder.def_var(local, init);
225
builder.set_val_label(init, ValueLabel::new(*next_local));
226
}
227
environ.add_state_slot_local(builder, environ.convert_valtype(wasm_type)?, init);
228
*next_local += 1;
229
}
230
Ok(())
231
}
232
233
/// Parse the function body in `reader`.
234
///
235
/// This assumes that the local variable declarations have already been parsed and function
236
/// arguments and locals are declared in the builder.
237
fn parse_function_body(
238
validator: &mut FuncValidator<impl WasmModuleResources>,
239
reader: BinaryReader,
240
builder: &mut FunctionBuilder,
241
environ: &mut FuncEnvironment<'_>,
242
) -> WasmResult<()> {
243
// The control stack is initialized with a single block representing the whole function.
244
debug_assert_eq!(
245
environ.stacks.control_stack.len(),
246
1,
247
"State not initialized"
248
);
249
250
environ.before_translate_function(builder)?;
251
252
let mut reader = OperatorsReader::new(reader);
253
let mut operand_types = vec![];
254
255
while !reader.eof() {
256
let pos = reader.original_position();
257
builder.set_srcloc(cur_srcloc(&reader.get_binary_reader()));
258
259
let op = reader.read()?;
260
let operand_types =
261
validate_op_and_get_operand_types(validator, environ, &mut operand_types, &op, pos)?;
262
263
environ.before_translate_operator(&op, operand_types, builder)?;
264
translate_operator(validator, &op, operand_types, builder, environ)?;
265
environ.after_translate_operator(&op, validator, builder)?;
266
}
267
268
environ.after_translate_function(builder)?;
269
reader.finish()?;
270
271
// The final `End` operator left us in the exit block where we need to manually add a return
272
// instruction.
273
//
274
// If the exit block is unreachable, it may not have the correct arguments, so we would
275
// generate a return instruction that doesn't match the signature.
276
if environ.is_reachable() {
277
if !builder.is_unreachable() {
278
let mut returns = core::mem::take(&mut environ.stacks.stack);
279
environ.handle_before_return(&returns, builder);
280
bitcast_wasm_returns(&mut returns, builder);
281
builder.ins().return_(&returns);
282
}
283
}
284
285
// Discard any remaining values on the stack. Either we just returned them,
286
// or the end of the function is unreachable.
287
environ.stacks.stack.clear();
288
environ.stacks.stack_shape.clear();
289
290
Ok(())
291
}
292
293
fn validate_op_and_get_operand_types<'a>(
294
validator: &mut FuncValidator<impl WasmModuleResources>,
295
environ: &mut FuncEnvironment<'_>,
296
operand_types: &'a mut Vec<wasmtime_environ::WasmValType>,
297
op: &wasmparser::Operator<'_>,
298
pos: usize,
299
) -> WasmResult<Option<&'a [wasmtime_environ::WasmValType]>> {
300
// Get the operand types for this operator.
301
//
302
// Note that we don't know if the `op` is valid yet, but only valid ops will
303
// definitely have arity. However, we also must check the arity before
304
// validating the op so that the validator has the right state to correctly
305
// report the arity. Furthermore, even if the op is valid, if it is in
306
// unreachable code, the op might want to pop more values from the stack
307
// than actually exist on the stack (which is allowed in unreachable code)
308
// so even if we can get arity, we are only guaranteed to have operand types
309
// for ops that are not only valid but also reachable.
310
let arity = op.operator_arity(&*validator);
311
operand_types.clear();
312
let operand_types = arity.and_then(|(operand_arity, _result_arity)| {
313
for i in (0..operand_arity).rev() {
314
let i = usize::try_from(i).unwrap();
315
let ty = validator.get_operand_type(i)??;
316
let ty = environ.convert_valtype(ty).ok()?;
317
operand_types.push(ty);
318
}
319
Some(&operand_types[..])
320
});
321
322
validator.op(pos, &op)?;
323
324
Ok(operand_types)
325
}
326
327
/// Get the current source location from a reader.
328
fn cur_srcloc(reader: &BinaryReader) -> ir::SourceLoc {
329
// We record source locations as byte code offsets relative to the beginning of the file.
330
// This will panic if bytecode is larger than 4 GB.
331
ir::SourceLoc::new(reader.original_position().try_into().unwrap())
332
}
333
334