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