Skip to content

Commit 7ef8f2e

Browse files
authored
winch: Improve frame handling (#9708)
This commit addresses issues identified while working on issue #8091. It improves the frame handling in Winch to prevent subtle bugs and enhance the robustness of the code generation process. Previously, there was no clear mechanism to verify when the frame was fully set up and safe to access the local slots allocated for register arguments, including the special slots used for the `VMContext`. As a result, it was possible to inadvertently read from uninitialized memory if calls were made before the frame was properly set up and sealed. This commit introduces two main changes with the objective to help reduce the risk of introducing bugs related to the above: * A `CodeGenPhase` trait, used via the type state pattern to clearly gate the operations allowed during each phase of the code generation process. * Improve the semantics of locals, by clearly separating the notion of Wasm locals and special locals used by the compiler. This specialization allows a more accurate representation of the semantics of Wasm locals and their index space.
1 parent f40e53e commit 7ef8f2e

File tree

14 files changed

+436
-318
lines changed

14 files changed

+436
-318
lines changed

winch/codegen/src/abi/mod.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -212,21 +212,6 @@ impl ABIOperand {
212212
_ => unreachable!(),
213213
}
214214
}
215-
216-
/// Get the register associated to this [`ABIOperand`].
217-
pub fn get_reg(&self) -> Option<Reg> {
218-
match *self {
219-
ABIOperand::Reg { reg, .. } => Some(reg),
220-
_ => None,
221-
}
222-
}
223-
224-
/// Get the type associated to this [`ABIOperand`].
225-
pub fn ty(&self) -> WasmValType {
226-
match *self {
227-
ABIOperand::Reg { ty, .. } | ABIOperand::Stack { ty, .. } => ty,
228-
}
229-
}
230215
}
231216

232217
/// Information about the [`ABIOperand`] information used in [`ABISig`].

winch/codegen/src/codegen/bounds.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use super::env::HeapData;
55
use crate::{
66
abi::{scratch, vmctx},
7-
codegen::CodeGenContext,
7+
codegen::{CodeGenContext, Emission},
88
isa::reg::{writable, Reg},
99
masm::{IntCmpKind, MacroAssembler, OperandSize, RegImm, TrapCode},
1010
stack::TypedReg,
@@ -82,7 +82,7 @@ impl Index {
8282

8383
/// Loads the bounds of the dynamic heap.
8484
pub(crate) fn load_dynamic_heap_bounds<M>(
85-
context: &mut CodeGenContext,
85+
context: &mut CodeGenContext<Emission>,
8686
masm: &mut M,
8787
heap: &HeapData,
8888
ptr_size: OperandSize,
@@ -149,7 +149,7 @@ pub(crate) fn ensure_index_and_offset<M: MacroAssembler>(
149149
/// criteria is in bounds.
150150
pub(crate) fn load_heap_addr_checked<M, F>(
151151
masm: &mut M,
152-
context: &mut CodeGenContext,
152+
context: &mut CodeGenContext<Emission>,
153153
ptr_size: OperandSize,
154154
heap: &HeapData,
155155
enable_spectre_mitigation: bool,

winch/codegen/src/codegen/call.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
5959
use crate::{
6060
abi::{scratch, vmctx, ABIOperand, ABISig, RetArea},
61-
codegen::{BuiltinFunction, BuiltinType, Callee, CodeGenContext},
61+
codegen::{BuiltinFunction, BuiltinType, Callee, CodeGenContext, Emission},
6262
masm::{
6363
CalleeKind, ContextArgs, MacroAssembler, MemMoveDirection, OperandSize, SPOffset,
6464
VMContextLoc,
@@ -85,7 +85,7 @@ impl FnCall {
8585
pub fn emit<M: MacroAssembler>(
8686
env: &mut FuncEnv<M::Ptr>,
8787
masm: &mut M,
88-
context: &mut CodeGenContext,
88+
context: &mut CodeGenContext<Emission>,
8989
callee: Callee,
9090
) {
9191
let (kind, callee_context) = Self::lower(env, context.vmoffsets, &callee, context, masm);
@@ -129,7 +129,7 @@ impl FnCall {
129129
env: &mut FuncEnv<M::Ptr>,
130130
vmoffsets: &VMOffsets<u8>,
131131
callee: &Callee,
132-
context: &mut CodeGenContext,
132+
context: &mut CodeGenContext<Emission>,
133133
masm: &mut M,
134134
) -> (CalleeKind, ContextArgs) {
135135
let ptr = vmoffsets.ptr.size();
@@ -177,7 +177,7 @@ impl FnCall {
177177
fn lower_import<M: MacroAssembler, P: PtrSize>(
178178
index: FuncIndex,
179179
sig: &ABISig,
180-
context: &mut CodeGenContext,
180+
context: &mut CodeGenContext<Emission>,
181181
masm: &mut M,
182182
vmoffsets: &VMOffsets<P>,
183183
) -> (CalleeKind, ContextArgs) {
@@ -204,7 +204,7 @@ impl FnCall {
204204
fn lower_funcref<M: MacroAssembler>(
205205
sig: &ABISig,
206206
ptr: impl PtrSize,
207-
context: &mut CodeGenContext,
207+
context: &mut CodeGenContext<Emission>,
208208
masm: &mut M,
209209
) -> (CalleeKind, ContextArgs) {
210210
// Pop the funcref pointer to a register and allocate a register to hold the
@@ -275,7 +275,7 @@ impl FnCall {
275275
sig: &ABISig,
276276
callee_context: &ContextArgs,
277277
ret_area: Option<&RetArea>,
278-
context: &mut CodeGenContext,
278+
context: &mut CodeGenContext<Emission>,
279279
masm: &mut M,
280280
) {
281281
let arg_count = sig.params.len_without_retptr();
@@ -337,7 +337,7 @@ impl FnCall {
337337
reserved_space: u32,
338338
ret_area: Option<RetArea>,
339339
masm: &mut M,
340-
context: &mut CodeGenContext,
340+
context: &mut CodeGenContext<Emission>,
341341
) {
342342
// Free any registers holding any function references.
343343
match callee_kind {

winch/codegen/src/codegen/context.rs

Lines changed: 74 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use wasmtime_environ::{VMOffsets, WasmHeapType, WasmValType};
33
use super::ControlStackFrame;
44
use crate::{
55
abi::{scratch, vmctx, ABIOperand, ABIResults, RetArea},
6+
codegen::{CodeGenPhase, Emission, Prologue},
67
frame::Frame,
78
isa::reg::RegClass,
89
masm::{MacroAssembler, OperandSize, RegImm, SPOffset, ShiftKind, StackSlot},
@@ -26,25 +27,78 @@ use crate::{
2627
/// generation process. The code generation context should
2728
/// be generally used as the single entry point to access
2829
/// the compound functionality provided by its elements.
29-
pub(crate) struct CodeGenContext<'a> {
30+
pub(crate) struct CodeGenContext<'a, P: CodeGenPhase> {
3031
/// The register allocator.
3132
pub regalloc: RegAlloc,
3233
/// The value stack.
3334
pub stack: Stack,
3435
/// The current function's frame.
35-
pub frame: Frame,
36+
pub frame: Frame<P>,
3637
/// Reachability state.
3738
pub reachable: bool,
3839
/// A reference to the VMOffsets.
3940
pub vmoffsets: &'a VMOffsets<u8>,
4041
}
4142

42-
impl<'a> CodeGenContext<'a> {
43+
impl<'a> CodeGenContext<'a, Emission> {
44+
/// Prepares arguments for emitting an i32 shift operation.
45+
pub fn i32_shift<M>(&mut self, masm: &mut M, kind: ShiftKind)
46+
where
47+
M: MacroAssembler,
48+
{
49+
let top = self.stack.peek().expect("value at stack top");
50+
51+
if top.is_i32_const() {
52+
let val = self
53+
.stack
54+
.pop_i32_const()
55+
.expect("i32 const value at stack top");
56+
let typed_reg = self.pop_to_reg(masm, None);
57+
masm.shift_ir(
58+
writable!(typed_reg.reg),
59+
val as u64,
60+
typed_reg.reg,
61+
kind,
62+
OperandSize::S32,
63+
);
64+
self.stack.push(typed_reg.into());
65+
} else {
66+
masm.shift(self, kind, OperandSize::S32);
67+
}
68+
}
69+
70+
/// Prepares arguments for emitting an i64 binary operation.
71+
pub fn i64_shift<M>(&mut self, masm: &mut M, kind: ShiftKind)
72+
where
73+
M: MacroAssembler,
74+
{
75+
let top = self.stack.peek().expect("value at stack top");
76+
if top.is_i64_const() {
77+
let val = self
78+
.stack
79+
.pop_i64_const()
80+
.expect("i64 const value at stack top");
81+
let typed_reg = self.pop_to_reg(masm, None);
82+
masm.shift_ir(
83+
writable!(typed_reg.reg),
84+
val as u64,
85+
typed_reg.reg,
86+
kind,
87+
OperandSize::S64,
88+
);
89+
self.stack.push(typed_reg.into());
90+
} else {
91+
masm.shift(self, kind, OperandSize::S64);
92+
};
93+
}
94+
}
95+
96+
impl<'a> CodeGenContext<'a, Prologue> {
4397
/// Create a new code generation context.
4498
pub fn new(
4599
regalloc: RegAlloc,
46100
stack: Stack,
47-
frame: Frame,
101+
frame: Frame<Prologue>,
48102
vmoffsets: &'a VMOffsets<u8>,
49103
) -> Self {
50104
Self {
@@ -56,6 +110,19 @@ impl<'a> CodeGenContext<'a> {
56110
}
57111
}
58112

113+
/// Prepares the frame for the [`Emission`] code generation phase.
114+
pub fn for_emission(self) -> CodeGenContext<'a, Emission> {
115+
CodeGenContext {
116+
regalloc: self.regalloc,
117+
stack: self.stack,
118+
reachable: self.reachable,
119+
vmoffsets: self.vmoffsets,
120+
frame: self.frame.for_emission(),
121+
}
122+
}
123+
}
124+
125+
impl<'a> CodeGenContext<'a, Emission> {
59126
/// Request a specific register to the register allocator,
60127
/// spilling if not available.
61128
pub fn reg<M: MacroAssembler>(&mut self, named: Reg, masm: &mut M) -> Reg {
@@ -325,57 +392,6 @@ impl<'a> CodeGenContext<'a> {
325392
};
326393
}
327394

328-
/// Prepares arguments for emitting an i32 shift operation.
329-
pub fn i32_shift<M>(&mut self, masm: &mut M, kind: ShiftKind)
330-
where
331-
M: MacroAssembler,
332-
{
333-
let top = self.stack.peek().expect("value at stack top");
334-
335-
if top.is_i32_const() {
336-
let val = self
337-
.stack
338-
.pop_i32_const()
339-
.expect("i32 const value at stack top");
340-
let typed_reg = self.pop_to_reg(masm, None);
341-
masm.shift_ir(
342-
writable!(typed_reg.reg),
343-
val as u64,
344-
typed_reg.reg,
345-
kind,
346-
OperandSize::S32,
347-
);
348-
self.stack.push(typed_reg.into());
349-
} else {
350-
masm.shift(self, kind, OperandSize::S32);
351-
}
352-
}
353-
354-
/// Prepares arguments for emitting an i64 binary operation.
355-
pub fn i64_shift<M>(&mut self, masm: &mut M, kind: ShiftKind)
356-
where
357-
M: MacroAssembler,
358-
{
359-
let top = self.stack.peek().expect("value at stack top");
360-
if top.is_i64_const() {
361-
let val = self
362-
.stack
363-
.pop_i64_const()
364-
.expect("i64 const value at stack top");
365-
let typed_reg = self.pop_to_reg(masm, None);
366-
masm.shift_ir(
367-
writable!(typed_reg.reg),
368-
val as u64,
369-
typed_reg.reg,
370-
kind,
371-
OperandSize::S64,
372-
);
373-
self.stack.push(typed_reg.into());
374-
} else {
375-
masm.shift(self, kind, OperandSize::S64);
376-
};
377-
}
378-
379395
/// Prepares arguments for emitting a convert operation.
380396
pub fn convert_op<F, M>(&mut self, masm: &mut M, dst_ty: WasmValType, mut emit: F)
381397
where
@@ -511,7 +527,7 @@ impl<'a> CodeGenContext<'a> {
511527
mut calculate_ret_area: F,
512528
) where
513529
M: MacroAssembler,
514-
F: FnMut(&ABIResults, &mut CodeGenContext, &mut M) -> Option<RetArea>,
530+
F: FnMut(&ABIResults, &mut CodeGenContext<Emission>, &mut M) -> Option<RetArea>,
515531
{
516532
let area = results
517533
.on_stack()
@@ -558,7 +574,7 @@ impl<'a> CodeGenContext<'a> {
558574
where
559575
M: MacroAssembler,
560576
{
561-
let addr = masm.local_address(&self.frame.vmctx_slot);
577+
let addr = masm.local_address(&self.frame.vmctx_slot());
562578
masm.load_ptr(addr, writable!(vmctx!(M)));
563579
}
564580

@@ -570,7 +586,7 @@ impl<'a> CodeGenContext<'a> {
570586
fn spill_impl<M: MacroAssembler>(
571587
stack: &mut Stack,
572588
regalloc: &mut RegAlloc,
573-
frame: &Frame,
589+
frame: &Frame<Emission>,
574590
masm: &mut M,
575591
) {
576592
stack.inner_mut().iter_mut().for_each(|v| match v {

0 commit comments

Comments
 (0)