xref: /wasmtime-44.0.1/winch/codegen/src/masm.rs (revision 25bf8e0e)
1 use crate::abi::align_to;
2 use crate::abi::{addressing_mode::Address, local::LocalSlot};
3 use crate::isa::reg::Reg;
4 use crate::regalloc::RegAlloc;
5 use std::ops::Range;
6 
7 /// Operand size, in bits.
8 #[derive(Copy, Clone)]
9 pub(crate) enum OperandSize {
10     /// 32 bits.
11     S32,
12     /// 64 bits.
13     S64,
14 }
15 
16 /// An abstraction over a register or immediate.
17 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
18 pub(crate) enum RegImm {
19     /// A register.
20     Reg(Reg),
21     /// An immediate.
22     Imm(i32),
23 }
24 
25 impl RegImm {
26     /// Register constructor.
27     pub fn reg(r: Reg) -> Self {
28         RegImm::Reg(r)
29     }
30 
31     /// Immediate constructor.
32     pub fn imm(imm: i32) -> Self {
33         RegImm::Imm(imm)
34     }
35 }
36 
37 impl From<Reg> for RegImm {
38     fn from(r: Reg) -> Self {
39         Self::Reg(r)
40     }
41 }
42 
43 /// Generic MacroAssembler interface used by the code generation.
44 ///
45 /// The MacroAssembler trait aims to expose an interface, high-level enough,
46 /// so that each ISA can provide its own lowering to machine code. For example,
47 /// for WebAssembly operators that don't have a direct mapping to a machine
48 /// a instruction, the interface defines a signature matching the WebAssembly
49 /// operator, allowing each implementation to lower such operator entirely.
50 /// This approach attributes more responsibility to the MacroAssembler, but frees
51 /// the caller from concerning about assembling the right sequence of
52 /// instructions at the operator callsite.
53 ///
54 /// The interface defaults to a three-argument form for binary operations;
55 /// this allows a natural mapping to instructions for RISC architectures,
56 /// that use three-argument form.
57 /// This approach allows for a more general interface that can be restricted
58 /// where needed, in the case of architectures that use a two-argument form.
59 
60 pub(crate) trait MacroAssembler {
61     /// Emit the function prologue.
62     fn prologue(&mut self);
63 
64     /// Emit the function epilogue.
65     fn epilogue(&mut self, locals_size: u32);
66 
67     /// Reserve stack space.
68     fn reserve_stack(&mut self, bytes: u32);
69 
70     /// Get the address of a local slot.
71     fn local_address(&mut self, local: &LocalSlot) -> Address;
72 
73     /// Get stack pointer offset.
74     fn sp_offset(&mut self) -> u32;
75 
76     /// Perform a stack store.
77     fn store(&mut self, src: RegImm, dst: Address, size: OperandSize);
78 
79     /// Perform a stack load.
80     fn load(&mut self, src: Address, dst: Reg, size: OperandSize);
81 
82     /// Perform a move.
83     fn mov(&mut self, src: RegImm, dst: RegImm, size: OperandSize);
84 
85     /// Perform add operation.
86     fn add(&mut self, dst: RegImm, lhs: RegImm, rhs: RegImm, size: OperandSize);
87 
88     /// Push the register to the stack, returning the offset.
89     fn push(&mut self, src: Reg) -> u32;
90 
91     /// Finalize the assembly and return the result.
92     // NOTE Interim, debug approach
93     fn finalize(&mut self) -> &[String];
94 
95     /// Zero a particular register.
96     fn zero(&mut self, reg: Reg);
97 
98     /// Zero a given memory range.
99     ///
100     /// The default implementation divides the given memory range
101     /// into word-sized slots. Then it unrolls a series of store
102     /// instructions, effectively assigning zero to each slot.
103     fn zero_mem_range(&mut self, mem: &Range<u32>, word_size: u32, regalloc: &mut RegAlloc) {
104         if mem.is_empty() {
105             return;
106         }
107 
108         let start = if mem.start % word_size == 0 {
109             mem.start
110         } else {
111             // Ensure that the start of the range is at least 4-byte aligned.
112             assert!(mem.start % 4 == 0);
113             let start = align_to(mem.start, word_size);
114             let addr = self.local_address(&LocalSlot::i32(start));
115             self.store(RegImm::imm(0), addr, OperandSize::S32);
116             // Ensure that the new start of the range, is word-size aligned.
117             assert!(start % word_size == 0);
118             start
119         };
120 
121         let end = align_to(mem.end, word_size);
122         let slots = (end - start) / word_size;
123 
124         if slots == 1 {
125             let slot = LocalSlot::i64(start + word_size);
126             let addr = self.local_address(&slot);
127             self.store(RegImm::imm(0), addr, OperandSize::S64);
128         } else {
129             // TODO
130             // Add an upper bound to this generation;
131             // given a considerably large amount of slots
132             // this will be inefficient.
133             let zero = regalloc.scratch;
134             self.zero(zero);
135             let zero = RegImm::reg(zero);
136 
137             for step in (start..end).into_iter().step_by(word_size as usize) {
138                 let slot = LocalSlot::i64(step + word_size);
139                 let addr = self.local_address(&slot);
140                 self.store(zero, addr, OperandSize::S64);
141             }
142         }
143     }
144 }
145