Skip to content

Commit fdbd46f

Browse files
authored
Merge pull request #465 from rustcoreutils/updates
[cc] inline asm, phase 1
2 parents 13dda5e + 9c48353 commit fdbd46f

File tree

10 files changed

+2219
-33
lines changed

10 files changed

+2219
-33
lines changed

cc/arch/aarch64/codegen.rs

Lines changed: 423 additions & 0 deletions
Large diffs are not rendered by default.

cc/arch/lir.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ pub enum Directive {
363363

364364
/// Blank line for readability
365365
Blank,
366+
367+
/// Raw assembly string (emitted verbatim) - used for inline asm
368+
Raw(String),
366369
}
367370

368371
impl Directive {
@@ -574,6 +577,9 @@ impl EmitAsm for Directive {
574577
Directive::Blank => {
575578
let _ = writeln!(out);
576579
}
580+
Directive::Raw(text) => {
581+
let _ = writeln!(out, " {}", text);
582+
}
577583
}
578584
}
579585
}

cc/arch/x86_64/codegen.rs

Lines changed: 635 additions & 2 deletions
Large diffs are not rendered by default.

cc/arch/x86_64/regalloc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ impl XmmReg {
381381
// ============================================================================
382382

383383
/// Location of a value
384-
#[derive(Debug, Clone)]
384+
#[derive(Debug, Clone, PartialEq)]
385385
pub enum Loc {
386386
/// In a general-purpose register
387387
Reg(Reg),

cc/ir/linearize.rs

Lines changed: 194 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@
1212

1313
use super::ssa::ssa_convert;
1414
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,
1617
};
1718
use crate::diag::{error, Position};
1819
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,
2122
};
2223
use crate::strings::{StringId, StringTable};
2324
use crate::symbol::SymbolTable;
@@ -1195,6 +1196,16 @@ impl<'a> Linearizer<'a> {
11951196
// Case/Default labels are handled by linearize_switch
11961197
// If we encounter them outside a switch, ignore them
11971198
}
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+
}
11981209
}
11991210
}
12001211

@@ -2124,6 +2135,186 @@ impl<'a> Linearizer<'a> {
21242135
}
21252136
}
21262137

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+
21272318
fn get_or_create_label(&mut self, name: &str) -> BasicBlockId {
21282319
if let Some(&bb) = self.label_map.get(name) {
21292320
bb

cc/ir/mod.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ pub enum Opcode {
165165
// Non-local jumps (setjmp/longjmp)
166166
Setjmp, // Save execution context, returns 0 or value from longjmp
167167
Longjmp, // Restore execution context (never returns)
168+
169+
// Inline assembly
170+
Asm, // Inline assembly statement
168171
}
169172

170173
impl Opcode {
@@ -259,6 +262,7 @@ impl Opcode {
259262
Opcode::Unreachable => "unreachable",
260263
Opcode::Setjmp => "setjmp",
261264
Opcode::Longjmp => "longjmp",
265+
Opcode::Asm => "asm",
262266
}
263267
}
264268
}
@@ -447,6 +451,42 @@ impl fmt::Display for BasicBlockId {
447451
}
448452
}
449453

454+
// ============================================================================
455+
// Inline Assembly Support
456+
// ============================================================================
457+
458+
/// Constraint information for an inline asm operand
459+
#[derive(Debug, Clone)]
460+
pub struct AsmConstraint {
461+
/// The pseudo (register or value) for this operand
462+
pub pseudo: PseudoId,
463+
/// Optional symbolic name for the operand (e.g., [result])
464+
pub name: Option<String>,
465+
/// Matching output operand index (for constraints like "0")
466+
pub matching_output: Option<usize>,
467+
/// The constraint string (e.g., "r", "a", "=r", "+m")
468+
/// Used by codegen to determine specific register requirements
469+
/// Note: Early clobber (&) is parsed but not explicitly handled since our
470+
/// simple register allocator doesn't share registers between inputs/outputs
471+
pub constraint: String,
472+
}
473+
474+
/// Data for an inline assembly instruction
475+
#[derive(Debug, Clone)]
476+
pub struct AsmData {
477+
/// The assembly template string
478+
pub template: String,
479+
/// Output operands
480+
pub outputs: Vec<AsmConstraint>,
481+
/// Input operands
482+
pub inputs: Vec<AsmConstraint>,
483+
/// Clobber list: registers and special values ("memory", "cc")
484+
pub clobbers: Vec<String>,
485+
/// Goto labels for asm goto: BasicBlockIds the asm can jump to
486+
/// Referenced in template as %l0, %l1, ... or %l[name]
487+
pub goto_labels: Vec<(BasicBlockId, String)>,
488+
}
489+
450490
// ============================================================================
451491
// Instruction
452492
// ============================================================================
@@ -499,6 +539,8 @@ pub struct Instruction {
499539
pub indirect_target: Option<PseudoId>,
500540
/// Source position for debug info
501541
pub pos: Option<Position>,
542+
/// For inline assembly: the asm data (template, operands, clobbers)
543+
pub asm_data: Option<Box<AsmData>>,
502544
}
503545

504546
impl Default for Instruction {
@@ -524,6 +566,7 @@ impl Default for Instruction {
524566
is_two_reg_return: false,
525567
indirect_target: None,
526568
pos: None,
569+
asm_data: None,
527570
}
528571
}
529572
}
@@ -764,6 +807,15 @@ impl Instruction {
764807
.with_src3(cond, if_true, if_false)
765808
.with_type_and_size(typ, size)
766809
}
810+
811+
/// Create an inline assembly instruction
812+
pub fn asm(data: AsmData) -> Self {
813+
Self {
814+
op: Opcode::Asm,
815+
asm_data: Some(Box::new(data)),
816+
..Default::default()
817+
}
818+
}
767819
}
768820

769821
impl fmt::Display for Instruction {

cc/parse/ast.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,22 @@ impl Expr {
559559
}
560560
}
561561

562+
// ============================================================================
563+
// Inline Assembly Support (GCC Extended Asm)
564+
// ============================================================================
565+
566+
/// An operand in an inline assembly statement
567+
/// Format: [name] "constraint" (expr)
568+
#[derive(Debug, Clone)]
569+
pub struct AsmOperand {
570+
/// Optional symbolic name for the operand (e.g., [result])
571+
pub name: Option<StringId>,
572+
/// Constraint string (e.g., "=r", "+m", "r")
573+
pub constraint: String,
574+
/// The C expression (lvalue for outputs, rvalue for inputs)
575+
pub expr: Expr,
576+
}
577+
562578
// ============================================================================
563579
// Statements
564580
// ============================================================================
@@ -619,6 +635,21 @@ pub enum Stmt {
619635

620636
/// Default label (within switch body)
621637
Default,
638+
639+
/// Inline assembly statement (GCC extended asm)
640+
/// Format: asm [volatile] [goto] ( "template" : outputs : inputs : clobbers [: goto_labels] );
641+
Asm {
642+
/// The assembly template string with %0, %1, etc. placeholders
643+
template: String,
644+
/// Output operands: [name] "=constraint" (lvalue)
645+
outputs: Vec<AsmOperand>,
646+
/// Input operands: [name] "constraint" (rvalue)
647+
inputs: Vec<AsmOperand>,
648+
/// Clobber list: registers and special values ("memory", "cc")
649+
clobbers: Vec<String>,
650+
/// Goto labels for asm goto (4th colon): labels the asm can jump to
651+
goto_labels: Vec<StringId>,
652+
},
622653
}
623654

624655
/// Initializer for a for loop (can be declaration or expression)

0 commit comments

Comments
 (0)