Skip to content

Commit 7ec9251

Browse files
authored
winch: Add support for the <i32|i64>.div_* instructions (#5807)
* Refactor the structure and responsibilities of `CodeGenContext` This commit refactors how the `CodeGenContext` is used throughout the code generation process, making it easier to pass it around when more flexibility is desired in the MacroAssembler to perform the lowering of certain instructions. As of this change, the responsibility of the `CodeGenContext` is to provide an interface for operations that require an orchestration between the register allocator, the value stack and function's frame. The MacroAssembler is removed from the CodeGenContext as is passed as a dependency where needed, effectly using it as an independent code generation interface only. By giving more responsibilities to the `CodeGenContext` we can clearly separate the concerns of the register allocator, which previously did more than it should (e.g. popping values and spilling). This change ultimately allows passing in the `CodeGenContext` to the `MacroAssembler` when a given instruction cannot be generically described through a common interface. Allowing each implementation to decide the best way to lower a particular instruction. * winch: Add support for the WebAssembly `<i32|i64>.div_*` instructions Given that some architectures have very specific requirements on how to handle division, this change uses `CodeGenContext` as a dependency to the `div` MacroAssembler instruction to ensure that each implementation can decide on how to lower the division. This approach also allows -- in architectures where division can be expressed as an ordinary binary operation -- to rely on the `CodeGenContext::i32_binop` or `CodeGenContext::i64_binop` helpers.
1 parent 853ff78 commit 7ec9251

File tree

30 files changed

+851
-220
lines changed

30 files changed

+851
-220
lines changed
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
use crate::{
2+
frame::Frame,
3+
masm::{MacroAssembler, OperandSize, RegImm},
4+
reg::Reg,
5+
regalloc::RegAlloc,
6+
stack::{Stack, Val},
7+
};
8+
9+
/// The code generation context.
10+
/// The code generation context is made up of three
11+
/// essential data structures:
12+
///
13+
/// * The register allocator, in charge of keeping the inventory of register
14+
/// availability.
15+
/// * The value stack, which keeps track of the state of the values
16+
/// after each operation.
17+
/// * The current function's frame.
18+
///
19+
/// These data structures normally require cooperating with each other
20+
/// to perform most of the operations needed during the code
21+
/// generation process. The code generation context should
22+
/// be generally used as the single entry point to access
23+
/// the compound functionality provided by its elements.
24+
pub(crate) struct CodeGenContext<'a> {
25+
/// The register allocator.
26+
pub regalloc: RegAlloc,
27+
/// The value stack.
28+
pub stack: Stack,
29+
/// The current function's frame.
30+
pub frame: &'a Frame,
31+
}
32+
33+
impl<'a> CodeGenContext<'a> {
34+
/// Create a new code generation context.
35+
pub fn new(regalloc: RegAlloc, stack: Stack, frame: &'a Frame) -> Self {
36+
Self {
37+
regalloc,
38+
stack,
39+
frame,
40+
}
41+
}
42+
43+
/// Request a specific general purpose register to the register allocator,
44+
/// spilling if not available.
45+
pub fn gpr<M: MacroAssembler>(&mut self, named: Reg, masm: &mut M) -> Reg {
46+
self.regalloc.gpr(named, &mut |regalloc| {
47+
Self::spill(&mut self.stack, regalloc, &self.frame, masm)
48+
})
49+
}
50+
51+
/// Request the next avaiable general purpose register to the register allocator,
52+
/// spilling if no registers are available.
53+
pub fn any_gpr<M: MacroAssembler>(&mut self, masm: &mut M) -> Reg {
54+
self.regalloc
55+
.any_gpr(&mut |regalloc| Self::spill(&mut self.stack, regalloc, &self.frame, masm))
56+
}
57+
58+
/// Free the given general purpose register.
59+
pub fn free_gpr(&mut self, reg: Reg) {
60+
self.regalloc.free_gpr(reg);
61+
}
62+
63+
/// Loads the stack top value into a register, if it isn't already one;
64+
/// spilling if there are no registers available.
65+
pub fn pop_to_reg<M: MacroAssembler>(&mut self, masm: &mut M, size: OperandSize) -> Reg {
66+
if let Some(reg) = self.stack.pop_reg() {
67+
return reg;
68+
}
69+
70+
let dst = self.any_gpr(masm);
71+
let val = self.stack.pop().expect("a value at stack top");
72+
Self::move_val_to_reg(val, dst, masm, self.frame, size);
73+
dst
74+
}
75+
76+
/// Checks if the stack top contains the given register. The register
77+
/// gets allocated otherwise, potentially causing a spill.
78+
/// Once the requested register is allocated, the value at the top of the stack
79+
/// gets loaded into the register.
80+
pub fn pop_to_named_reg<M: MacroAssembler>(
81+
&mut self,
82+
masm: &mut M,
83+
named: Reg,
84+
size: OperandSize,
85+
) -> Reg {
86+
if let Some(reg) = self.stack.pop_named_reg(named) {
87+
return reg;
88+
}
89+
90+
let dst = self.gpr(named, masm);
91+
let val = self.stack.pop().expect("a value at stack top");
92+
Self::move_val_to_reg(val, dst, masm, self.frame, size);
93+
dst
94+
}
95+
96+
fn move_val_to_reg<M: MacroAssembler>(
97+
src: Val,
98+
dst: Reg,
99+
masm: &mut M,
100+
frame: &Frame,
101+
size: OperandSize,
102+
) {
103+
match src {
104+
Val::Reg(src) => masm.mov(RegImm::reg(src), RegImm::reg(dst), size),
105+
Val::I32(imm) => masm.mov(RegImm::imm(imm.into()), RegImm::reg(dst), size),
106+
Val::I64(imm) => masm.mov(RegImm::imm(imm), RegImm::reg(dst), size),
107+
Val::Local(index) => {
108+
let slot = frame
109+
.get_local(index)
110+
.expect(&format!("valid locat at index = {}", index));
111+
let addr = masm.local_address(&slot);
112+
masm.load(addr, dst, slot.ty.into());
113+
}
114+
v => panic!("Unsupported value {:?}", v),
115+
};
116+
}
117+
118+
/// Prepares arguments for emitting an i32 binary operation.
119+
pub fn i32_binop<F, M>(&mut self, masm: &mut M, emit: &mut F)
120+
where
121+
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
122+
M: MacroAssembler,
123+
{
124+
let top = self.stack.peek().expect("value at stack top");
125+
126+
if top.is_i32_const() {
127+
let val = self
128+
.stack
129+
.pop_i32_const()
130+
.expect("i32 const value at stack top");
131+
let reg = self.pop_to_reg(masm, OperandSize::S32);
132+
emit(
133+
masm,
134+
RegImm::reg(reg),
135+
RegImm::imm(val as i64),
136+
OperandSize::S32,
137+
);
138+
self.stack.push(Val::reg(reg));
139+
} else {
140+
let src = self.pop_to_reg(masm, OperandSize::S32);
141+
let dst = self.pop_to_reg(masm, OperandSize::S32);
142+
emit(masm, dst.into(), src.into(), OperandSize::S32);
143+
self.regalloc.free_gpr(src);
144+
self.stack.push(Val::reg(dst));
145+
}
146+
}
147+
148+
/// Prepares arguments for emitting an i64 binary operation.
149+
pub fn i64_binop<F, M>(&mut self, masm: &mut M, emit: &mut F)
150+
where
151+
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
152+
M: MacroAssembler,
153+
{
154+
let top = self.stack.peek().expect("value at stack top");
155+
if top.is_i64_const() {
156+
let val = self
157+
.stack
158+
.pop_i64_const()
159+
.expect("i64 const value at stack top");
160+
let reg = self.pop_to_reg(masm, OperandSize::S64);
161+
emit(masm, RegImm::reg(reg), RegImm::imm(val), OperandSize::S64);
162+
self.stack.push(Val::reg(reg));
163+
} else {
164+
let src = self.pop_to_reg(masm, OperandSize::S64);
165+
let dst = self.pop_to_reg(masm, OperandSize::S64);
166+
emit(masm, dst.into(), src.into(), OperandSize::S64);
167+
self.regalloc.free_gpr(src);
168+
self.stack.push(Val::reg(dst));
169+
}
170+
}
171+
172+
/// Spill locals and registers to memory.
173+
// TODO optimize the spill range;
174+
//
175+
// At any point in the program, the stack
176+
// might already contain Memory entries;
177+
// we could effectively ignore that range;
178+
// only focusing on the range that contains
179+
// spillable values.
180+
fn spill<M: MacroAssembler>(
181+
stack: &mut Stack,
182+
regalloc: &mut RegAlloc,
183+
frame: &Frame,
184+
masm: &mut M,
185+
) {
186+
stack.inner_mut().iter_mut().for_each(|v| match v {
187+
Val::Reg(r) => {
188+
let offset = masm.push(*r);
189+
regalloc.free_gpr(*r);
190+
*v = Val::Memory(offset);
191+
}
192+
Val::Local(index) => {
193+
let slot = frame.get_local(*index).expect("valid local at slot");
194+
let addr = masm.local_address(&slot);
195+
masm.load(addr, regalloc.scratch, slot.ty.into());
196+
let offset = masm.push(regalloc.scratch);
197+
*v = Val::Memory(offset);
198+
}
199+
v => {
200+
println!("trying to spill something unknown {:?}", v);
201+
}
202+
});
203+
}
204+
}
Lines changed: 19 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,12 @@
11
use crate::{
22
abi::{ABISig, ABI},
3-
frame::Frame,
4-
masm::{MacroAssembler, OperandSize, RegImm},
5-
regalloc::RegAlloc,
6-
stack::{Stack, Val},
3+
masm::{MacroAssembler, OperandSize},
74
};
85
use anyhow::Result;
96
use wasmparser::{BinaryReader, FuncValidator, ValType, ValidatorResources, VisitOperator};
107

11-
/// The code generation context.
12-
pub(crate) struct CodeGenContext<'a, M>
13-
where
14-
M: MacroAssembler,
15-
{
16-
pub masm: &'a mut M,
17-
pub stack: Stack,
18-
pub frame: &'a Frame,
19-
}
20-
21-
impl<'a, M> CodeGenContext<'a, M>
22-
where
23-
M: MacroAssembler,
24-
{
25-
/// Create a new code generation context.
26-
pub fn new(masm: &'a mut M, stack: Stack, frame: &'a Frame) -> Self {
27-
Self { masm, stack, frame }
28-
}
29-
30-
/// Prepares arguments for emitting an i32 binary operation.
31-
pub fn i32_binop<F>(&mut self, regalloc: &mut RegAlloc, emit: &mut F)
32-
where
33-
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
34-
{
35-
let top = self.stack.peek().expect("value at stack top");
36-
37-
if top.is_i32_const() {
38-
let val = self
39-
.stack
40-
.pop_i32_const()
41-
.expect("i32 const value at stack top");
42-
let reg = regalloc.pop_to_reg(self, OperandSize::S32);
43-
emit(
44-
&mut self.masm,
45-
RegImm::reg(reg),
46-
RegImm::imm(val as i64),
47-
OperandSize::S32,
48-
);
49-
self.stack.push(Val::reg(reg));
50-
} else {
51-
let src = regalloc.pop_to_reg(self, OperandSize::S32);
52-
let dst = regalloc.pop_to_reg(self, OperandSize::S32);
53-
emit(&mut self.masm, dst.into(), src.into(), OperandSize::S32);
54-
regalloc.free_gpr(src);
55-
self.stack.push(Val::reg(dst));
56-
}
57-
}
58-
59-
/// Prepares arguments for emitting an i64 binary operation.
60-
pub fn i64_binop<F>(&mut self, regalloc: &mut RegAlloc, emit: &mut F)
61-
where
62-
F: FnMut(&mut M, RegImm, RegImm, OperandSize),
63-
{
64-
let top = self.stack.peek().expect("value at stack top");
65-
if top.is_i64_const() {
66-
let val = self
67-
.stack
68-
.pop_i64_const()
69-
.expect("i64 const value at stack top");
70-
let reg = regalloc.pop_to_reg(self, OperandSize::S64);
71-
emit(
72-
&mut self.masm,
73-
RegImm::reg(reg),
74-
RegImm::imm(val),
75-
OperandSize::S64,
76-
);
77-
self.stack.push(Val::reg(reg));
78-
} else {
79-
let src = regalloc.pop_to_reg(self, OperandSize::S64);
80-
let dst = regalloc.pop_to_reg(self, OperandSize::S64);
81-
emit(&mut self.masm, dst.into(), src.into(), OperandSize::S64);
82-
regalloc.free_gpr(src);
83-
self.stack.push(Val::reg(dst));
84-
}
85-
}
86-
}
8+
mod context;
9+
pub(crate) use context::*;
8710

8811
/// The code generation abstraction.
8912
pub(crate) struct CodeGen<'a, M>
@@ -97,22 +20,22 @@ where
9720
sig: ABISig,
9821

9922
/// The code generation context.
100-
pub context: CodeGenContext<'a, M>,
23+
pub context: CodeGenContext<'a>,
10124

102-
/// The register allocator.
103-
pub regalloc: RegAlloc,
25+
/// The MacroAssembler.
26+
pub masm: &'a mut M,
10427
}
10528

10629
impl<'a, M> CodeGen<'a, M>
10730
where
10831
M: MacroAssembler,
10932
{
110-
pub fn new<A: ABI>(context: CodeGenContext<'a, M>, sig: ABISig, regalloc: RegAlloc) -> Self {
33+
pub fn new<A: ABI>(masm: &'a mut M, context: CodeGenContext<'a>, sig: ABISig) -> Self {
11134
Self {
11235
word_size: <A as ABI>::word_bytes(),
11336
sig,
11437
context,
115-
regalloc,
38+
masm,
11639
}
11740
}
11841

@@ -131,10 +54,8 @@ where
13154

13255
// TODO stack checks
13356
fn emit_start(&mut self) -> Result<()> {
134-
self.context.masm.prologue();
135-
self.context
136-
.masm
137-
.reserve_stack(self.context.frame.locals_size);
57+
self.masm.prologue();
58+
self.masm.reserve_stack(self.context.frame.locals_size);
13859
Ok(())
13960
}
14061

@@ -145,10 +66,10 @@ where
14566
) -> Result<()> {
14667
self.spill_register_arguments();
14768
let defined_locals_range = &self.context.frame.defined_locals_range;
148-
self.context.masm.zero_mem_range(
69+
self.masm.zero_mem_range(
14970
defined_locals_range.as_range(),
15071
self.word_size,
151-
&mut self.regalloc,
72+
&mut self.context.regalloc,
15273
);
15374

15475
while !body.eof() {
@@ -185,7 +106,7 @@ where
185106
// Emit the usual function end instruction sequence.
186107
pub fn emit_end(&mut self) -> Result<()> {
187108
self.handle_abi_result();
188-
self.context.masm.epilogue(self.context.frame.locals_size);
109+
self.masm.epilogue(self.context.frame.locals_size);
189110
Ok(())
190111
}
191112

@@ -206,14 +127,14 @@ where
206127
.frame
207128
.get_local(index as u32)
208129
.expect("valid local slot at location");
209-
let addr = self.context.masm.local_address(local);
130+
let addr = self.masm.local_address(local);
210131
let src = arg
211132
.get_reg()
212133
.expect("arg should be associated to a register");
213134

214135
match &ty {
215-
ValType::I32 => self.context.masm.store(src.into(), addr, OperandSize::S32),
216-
ValType::I64 => self.context.masm.store(src.into(), addr, OperandSize::S64),
136+
ValType::I32 => self.masm.store(src.into(), addr, OperandSize::S32),
137+
ValType::I64 => self.masm.store(src.into(), addr, OperandSize::S64),
217138
_ => panic!("Unsupported type {:?}", ty),
218139
}
219140
});
@@ -225,8 +146,8 @@ where
225146
}
226147
let named_reg = self.sig.result.result_reg();
227148
let reg = self
228-
.regalloc
229-
.pop_to_named_reg(&mut self.context, named_reg, OperandSize::S64);
230-
self.regalloc.free_gpr(reg);
149+
.context
150+
.pop_to_named_reg(self.masm, named_reg, OperandSize::S64);
151+
self.context.regalloc.free_gpr(reg);
231152
}
232153
}

0 commit comments

Comments
 (0)