|
12 | 12 |
|
13 | 13 | use super::ssa::ssa_convert; |
14 | 14 | use super::{ |
15 | | - BasicBlock, BasicBlockId, Function, Initializer, Instruction, Module, Opcode, Pseudo, PseudoId, |
| 15 | + AsmConstraint, AsmData, BasicBlock, BasicBlockId, Function, Initializer, Instruction, Module, |
| 16 | + Opcode, Pseudo, PseudoId, |
16 | 17 | }; |
17 | 18 | use crate::diag::{error, Position}; |
18 | 19 | use crate::parse::ast::{ |
19 | | - AssignOp, BinaryOp, BlockItem, Declaration, Designator, Expr, ExprKind, ExternalDecl, ForInit, |
20 | | - FunctionDef, InitElement, Stmt, TranslationUnit, UnaryOp, |
| 20 | + AsmOperand, AssignOp, BinaryOp, BlockItem, Declaration, Designator, Expr, ExprKind, |
| 21 | + ExternalDecl, ForInit, FunctionDef, InitElement, Stmt, TranslationUnit, UnaryOp, |
21 | 22 | }; |
22 | 23 | use crate::strings::{StringId, StringTable}; |
23 | 24 | use crate::symbol::SymbolTable; |
@@ -1195,6 +1196,16 @@ impl<'a> Linearizer<'a> { |
1195 | 1196 | // Case/Default labels are handled by linearize_switch |
1196 | 1197 | // If we encounter them outside a switch, ignore them |
1197 | 1198 | } |
| 1199 | + |
| 1200 | + Stmt::Asm { |
| 1201 | + template, |
| 1202 | + outputs, |
| 1203 | + inputs, |
| 1204 | + clobbers, |
| 1205 | + goto_labels, |
| 1206 | + } => { |
| 1207 | + self.linearize_asm(template, outputs, inputs, clobbers, goto_labels); |
| 1208 | + } |
1198 | 1209 | } |
1199 | 1210 | } |
1200 | 1211 |
|
@@ -2124,6 +2135,186 @@ impl<'a> Linearizer<'a> { |
2124 | 2135 | } |
2125 | 2136 | } |
2126 | 2137 |
|
| 2138 | + // ======================================================================== |
| 2139 | + // Inline assembly linearization |
| 2140 | + // ======================================================================== |
| 2141 | + |
| 2142 | + /// Linearize an inline assembly statement |
| 2143 | + fn linearize_asm( |
| 2144 | + &mut self, |
| 2145 | + template: &str, |
| 2146 | + outputs: &[AsmOperand], |
| 2147 | + inputs: &[AsmOperand], |
| 2148 | + clobbers: &[String], |
| 2149 | + goto_labels: &[StringId], |
| 2150 | + ) { |
| 2151 | + let mut ir_outputs = Vec::new(); |
| 2152 | + let mut ir_inputs = Vec::new(); |
| 2153 | + |
| 2154 | + // Process output operands |
| 2155 | + for op in outputs { |
| 2156 | + // Create a pseudo for the output |
| 2157 | + let pseudo = self.alloc_pseudo(); |
| 2158 | + |
| 2159 | + // Parse constraint to get flags |
| 2160 | + let (_is_memory, is_readwrite, _matching) = self.parse_asm_constraint(&op.constraint); |
| 2161 | + |
| 2162 | + // Get symbolic name if present |
| 2163 | + let name = op.name.map(|n| self.str(n).to_string()); |
| 2164 | + |
| 2165 | + // For read-write outputs ("+r"), load the initial value into the SAME pseudo |
| 2166 | + // so that input and output use the same register |
| 2167 | + if is_readwrite { |
| 2168 | + let addr = self.linearize_lvalue(&op.expr); |
| 2169 | + let typ = self.expr_type(&op.expr); |
| 2170 | + let size = self.types.size_bits(typ); |
| 2171 | + // Load into the same pseudo that will be used for output |
| 2172 | + self.emit(Instruction::load(pseudo, addr, 0, typ, size)); |
| 2173 | + |
| 2174 | + // Also add as input, using the SAME pseudo |
| 2175 | + ir_inputs.push(AsmConstraint { |
| 2176 | + pseudo, // Same pseudo as output - ensures same register |
| 2177 | + name: name.clone(), |
| 2178 | + matching_output: None, |
| 2179 | + constraint: op.constraint.clone(), |
| 2180 | + }); |
| 2181 | + } |
| 2182 | + |
| 2183 | + ir_outputs.push(AsmConstraint { |
| 2184 | + pseudo, |
| 2185 | + name, |
| 2186 | + matching_output: None, |
| 2187 | + constraint: op.constraint.clone(), |
| 2188 | + }); |
| 2189 | + } |
| 2190 | + |
| 2191 | + // Process input operands |
| 2192 | + for op in inputs { |
| 2193 | + let (is_memory, _, matching) = self.parse_asm_constraint(&op.constraint); |
| 2194 | + |
| 2195 | + // Get symbolic name if present |
| 2196 | + let name = op.name.map(|n| self.str(n).to_string()); |
| 2197 | + |
| 2198 | + // For matching constraints (like "0"), we need to load the input value |
| 2199 | + // into the matched output's pseudo so they use the same register |
| 2200 | + let pseudo = if let Some(match_idx) = matching { |
| 2201 | + if match_idx < ir_outputs.len() { |
| 2202 | + // Use the matched output's pseudo |
| 2203 | + let out_pseudo = ir_outputs[match_idx].pseudo; |
| 2204 | + // Load the input value into the output's pseudo |
| 2205 | + let val = self.linearize_expr(&op.expr); |
| 2206 | + let typ = self.expr_type(&op.expr); |
| 2207 | + let size = self.types.size_bits(typ); |
| 2208 | + // Copy val to out_pseudo so they share the same register |
| 2209 | + self.emit( |
| 2210 | + Instruction::new(Opcode::Copy) |
| 2211 | + .with_target(out_pseudo) |
| 2212 | + .with_src(val) |
| 2213 | + .with_size(size), |
| 2214 | + ); |
| 2215 | + out_pseudo |
| 2216 | + } else { |
| 2217 | + self.linearize_expr(&op.expr) |
| 2218 | + } |
| 2219 | + } else if is_memory { |
| 2220 | + // For memory operands, get the address |
| 2221 | + self.linearize_lvalue(&op.expr) |
| 2222 | + } else { |
| 2223 | + // For register operands, evaluate the expression |
| 2224 | + self.linearize_expr(&op.expr) |
| 2225 | + }; |
| 2226 | + |
| 2227 | + ir_inputs.push(AsmConstraint { |
| 2228 | + pseudo, |
| 2229 | + name, |
| 2230 | + matching_output: matching, |
| 2231 | + constraint: op.constraint.clone(), |
| 2232 | + }); |
| 2233 | + } |
| 2234 | + |
| 2235 | + // Process goto labels - map label names to BasicBlockIds |
| 2236 | + let ir_goto_labels: Vec<(BasicBlockId, String)> = goto_labels |
| 2237 | + .iter() |
| 2238 | + .map(|label_id| { |
| 2239 | + let label_name = self.str(*label_id).to_string(); |
| 2240 | + let bb = self.get_or_create_label(&label_name); |
| 2241 | + (bb, label_name) |
| 2242 | + }) |
| 2243 | + .collect(); |
| 2244 | + |
| 2245 | + // Create the asm data |
| 2246 | + let asm_data = AsmData { |
| 2247 | + template: template.to_string(), |
| 2248 | + outputs: ir_outputs.clone(), |
| 2249 | + inputs: ir_inputs, |
| 2250 | + clobbers: clobbers.to_vec(), |
| 2251 | + goto_labels: ir_goto_labels.clone(), |
| 2252 | + }; |
| 2253 | + |
| 2254 | + // Emit the asm instruction |
| 2255 | + self.emit(Instruction::asm(asm_data)); |
| 2256 | + |
| 2257 | + // For asm goto: add edges to all possible label targets |
| 2258 | + // The asm may jump to any of these labels, so control flow can go there |
| 2259 | + if !ir_goto_labels.is_empty() { |
| 2260 | + if let Some(current) = self.current_bb { |
| 2261 | + // After the asm instruction, we need a basic block for fall-through |
| 2262 | + // and edges to all goto targets |
| 2263 | + let fall_through = self.alloc_bb(); |
| 2264 | + |
| 2265 | + // Add edges to all goto label targets |
| 2266 | + for (target_bb, _) in &ir_goto_labels { |
| 2267 | + self.link_bb(current, *target_bb); |
| 2268 | + } |
| 2269 | + |
| 2270 | + // Add edge to fall-through (normal case when asm doesn't jump) |
| 2271 | + self.link_bb(current, fall_through); |
| 2272 | + |
| 2273 | + // Emit an explicit branch to the fallthrough block |
| 2274 | + // This is necessary because the asm goto acts as a conditional terminator |
| 2275 | + // Without this, code would fall through to whatever block comes next in layout |
| 2276 | + self.emit(Instruction::br(fall_through)); |
| 2277 | + |
| 2278 | + // Switch to fall-through block for subsequent instructions |
| 2279 | + self.current_bb = Some(fall_through); |
| 2280 | + } |
| 2281 | + } |
| 2282 | + |
| 2283 | + // Store outputs back to their destinations |
| 2284 | + // store(value, addr, ...) - value first, then address |
| 2285 | + for (i, op) in outputs.iter().enumerate() { |
| 2286 | + let out_pseudo = ir_outputs[i].pseudo; |
| 2287 | + let addr = self.linearize_lvalue(&op.expr); |
| 2288 | + let typ = self.expr_type(&op.expr); |
| 2289 | + let size = self.types.size_bits(typ); |
| 2290 | + self.emit(Instruction::store(out_pseudo, addr, 0, typ, size)); |
| 2291 | + } |
| 2292 | + } |
| 2293 | + |
| 2294 | + /// Parse an asm constraint string to extract flags |
| 2295 | + /// Returns (is_memory, is_readwrite, matching_output) |
| 2296 | + /// Note: Early clobber (&) is parsed but not used since our simple register |
| 2297 | + /// allocator doesn't share registers between inputs and outputs anyway |
| 2298 | + fn parse_asm_constraint(&self, constraint: &str) -> (bool, bool, Option<usize>) { |
| 2299 | + let mut is_memory = false; |
| 2300 | + let mut is_readwrite = false; |
| 2301 | + let mut matching = None; |
| 2302 | + |
| 2303 | + for c in constraint.chars() { |
| 2304 | + match c { |
| 2305 | + '+' => is_readwrite = true, |
| 2306 | + '&' | '=' | '%' => {} // Early clobber, output-only, commutative |
| 2307 | + 'r' | 'a' | 'b' | 'c' | 'd' | 'S' | 'D' => {} // Register constraints |
| 2308 | + 'm' | 'o' | 'V' | 'Q' => is_memory = true, |
| 2309 | + 'i' | 'n' | 'g' | 'X' => {} // Immediate, general |
| 2310 | + '0'..='9' => matching = Some((c as u8 - b'0') as usize), |
| 2311 | + _ => {} // Ignore unknown constraints |
| 2312 | + } |
| 2313 | + } |
| 2314 | + |
| 2315 | + (is_memory, is_readwrite, matching) |
| 2316 | + } |
| 2317 | + |
2127 | 2318 | fn get_or_create_label(&mut self, name: &str) -> BasicBlockId { |
2128 | 2319 | if let Some(&bb) = self.label_map.get(name) { |
2129 | 2320 | bb |
|
0 commit comments