diff --git a/bootstrap/src/test.rs b/bootstrap/src/test.rs index db036dc..476651f 100644 --- a/bootstrap/src/test.rs +++ b/bootstrap/src/test.rs @@ -22,6 +22,12 @@ impl Run for TestCommand { cprintln!("Test failed: {}", info); })); + cprintln!("[TEST] running cargo test"); + let mut command = std::process::Command::new("cargo"); + command.args(["test", "--manifest-path", "crates/Cargo.toml"]); + log::debug!("running {:?}", command); + assert!(command.status().unwrap().success(), "failed to run {:?}", command); + let testcases = self.collect_testcases(manifest); cprintln!("[TEST] found {} testcases", testcases.len()); diff --git a/crates/Cargo.lock b/crates/Cargo.lock index aad4464..87c0f98 100644 --- a/crates/Cargo.lock +++ b/crates/Cargo.lock @@ -5,3 +5,7 @@ version = 3 [[package]] name = "rustc_codegen_c" version = "0.1.0" + +[[package]] +name = "rustc_codegen_c_ast" +version = "0.1.0" diff --git a/crates/Cargo.toml b/crates/Cargo.toml index 8d7c0ce..925d396 100644 --- a/crates/Cargo.toml +++ b/crates/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["rustc_codegen_c"] +members = ["rustc_codegen_c", "rustc_codegen_c_ast"] [workspace.package] version = "0.1.0" diff --git a/crates/rustc_codegen_c_ast/Cargo.toml b/crates/rustc_codegen_c_ast/Cargo.toml new file mode 100644 index 0000000..a9a8f45 --- /dev/null +++ b/crates/rustc_codegen_c_ast/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rustc_codegen_c_ast" +edition = "2021" +version.workspace = true + +[dependencies] + +# This package uses rustc crates. +[package.metadata.rust-analyzer] +rustc_private = true diff --git a/crates/rustc_codegen_c_ast/src/arena.rs b/crates/rustc_codegen_c_ast/src/arena.rs new file mode 100644 index 0000000..7781c73 --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/arena.rs @@ -0,0 +1,15 @@ +//! This module defines the memory arena for C AST nodes. + +use crate::decl::CDeclKind; +use crate::expr::CExprKind; +use crate::func::CFuncKind; +use crate::stmt::CStmtKind; +use crate::ty::CTyKind; + +rustc_arena::declare_arena!([ + [] decl: CDeclKind<'tcx>, + [] expr: CExprKind<'tcx>, + [] func: CFuncKind<'tcx>, + [] stmt: CStmtKind<'tcx>, + [] ty: CTyKind<'tcx>, +]); diff --git a/crates/rustc_codegen_c_ast/src/decl.rs b/crates/rustc_codegen_c_ast/src/decl.rs new file mode 100644 index 0000000..cfb5ede --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/decl.rs @@ -0,0 +1,50 @@ +//! This module defines AST nodes for C declarations. + +use crate::expr::{CExpr, CValue}; +use crate::pretty::{Print, PrinterCtx, INDENT}; +use crate::ty::{print_declarator, CTy}; +use crate::ModuleCtx; + +/// C declarations. +pub type CDecl<'mx> = &'mx CDeclKind<'mx>; + +/// C declaration kinds. +#[derive(Debug, Clone)] +pub enum CDeclKind<'mx> { + /// Variable declaration consisting of a name, type, and optional initializer. + /// + /// Example: + /// - `int foo;` // `ty val` + /// - `int foo = bar` `ty val = expr` + Var { name: CValue<'mx>, ty: CTy<'mx>, init: Option> }, +} + +impl<'mx> ModuleCtx<'mx> { + /// Create a new declaration. + pub fn decl(self, decl: CDeclKind<'mx>) -> CDecl<'mx> { + self.arena().alloc(decl) + } + + /// Create a new variable declaration. + pub fn var(self, name: CValue<'mx>, ty: CTy<'mx>, init: Option>) -> CDecl<'mx> { + self.decl(CDeclKind::Var { name, ty, init }) + } +} + +impl Print for CDecl<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + match self { + CDeclKind::Var { name, ty, init } => { + ctx.ibox(INDENT, |ctx| { + print_declarator(*ty, Some(*name), ctx); + if let Some(init) = init { + ctx.word(" ="); + ctx.softbreak(); + init.print_to(ctx); + } + ctx.word(";"); + }); + } + } + } +} diff --git a/crates/rustc_codegen_c_ast/src/expr.rs b/crates/rustc_codegen_c_ast/src/expr.rs new file mode 100644 index 0000000..9f7a94a --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/expr.rs @@ -0,0 +1,133 @@ +//! This module defines the AST nodes for C expressions. + +use crate::pretty::{Print, PrinterCtx, INDENT}; +use crate::ty::{print_declarator, CTy}; +use crate::ModuleCtx; + +/// Represents the values of C variables, parameters, and scalars. +/// +/// There are two variants to distinguish between constants and variables, +/// as is done in LLVM IR. We follow the `rustc_codegen_ssa` convention for this representation. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum CValue<'mx> { + /// A constant scalar + Scalar(i128), + /// A local variable indexed by a number, in the form `_0`, `_1`, etc. + Local(usize), + /// A function name + Func(&'mx str), +} + +/// C expressions. +pub type CExpr<'mx> = &'mx CExprKind<'mx>; + +/// C expressions. +#[derive(Debug, Clone)] +pub enum CExprKind<'mx> { + /// A "raw" C expression, simply a string of C code, which is printed as-is. + Raw(&'static str), + /// A value, such as a constant, variable, or function name. + Value(CValue<'mx>), + /// A binary operation expression, e.g. `lhs + rhs`. + Binary { lhs: CExpr<'mx>, rhs: CExpr<'mx>, op: &'static str }, + /// A type cast expression, e.g. `(int) x`. + Cast { ty: CTy<'mx>, expr: CExpr<'mx> }, + /// A function call expression, e.g. `foo(x, y)`. + Call { callee: CExpr<'mx>, args: Vec> }, + /// A member access expression, e.g. `foo.bar` or `foo->bar`. + Member { + expr: CExpr<'mx>, + /// Whether to use the `->` operator instead of `.`. + arrow: bool, + field: &'mx str, + }, +} + +impl<'mx> ModuleCtx<'mx> { + /// Create a new expression. + pub fn expr(&self, expr: CExprKind<'mx>) -> CExpr<'mx> { + self.arena().alloc(expr) + } + + /// Create a new raw expression. + pub fn raw(&self, raw: &'static str) -> CExpr<'mx> { + self.expr(CExprKind::Raw(raw)) + } + + /// Create a new value expression. + pub fn value(&self, value: CValue<'mx>) -> CExpr<'mx> { + self.expr(CExprKind::Value(value)) + } + + /// Create a new binary expression. + pub fn binary(&self, lhs: CExpr<'mx>, rhs: CExpr<'mx>, op: &'static str) -> CExpr<'mx> { + self.expr(CExprKind::Binary { lhs, rhs, op }) + } + + /// Create a new cast expression. + pub fn cast(&self, ty: CTy<'mx>, expr: CExpr<'mx>) -> CExpr<'mx> { + self.expr(CExprKind::Cast { ty, expr }) + } + + /// Create a new function call expression. + pub fn call(&self, callee: CExpr<'mx>, args: Vec>) -> CExpr<'mx> { + self.expr(CExprKind::Call { callee, args }) + } + + /// Create a new member access expression. + pub fn member(&self, expr: CExpr<'mx>, field: &'mx str) -> CExpr<'mx> { + self.expr(CExprKind::Member { expr, field, arrow: false }) + } +} + +impl Print for CValue<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + match self { + CValue::Scalar(i) => ctx.word(i.to_string()), + CValue::Local(i) => ctx.word(format!("_{}", i)), + CValue::Func(name) => ctx.word(name.to_string()), + } + } +} + +impl Print for CExpr<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + match self { + CExprKind::Raw(raw) => ctx.word(*raw), + CExprKind::Value(value) => value.print_to(ctx), + CExprKind::Binary { lhs, rhs, op } => ctx.ibox_delim(INDENT, ("(", ")"), 0, |ctx| { + ctx.ibox(-INDENT, |ctx| lhs.print_to(ctx)); + + ctx.softbreak(); + ctx.word(*op); + ctx.nbsp(); + + rhs.print_to(ctx); + }), + CExprKind::Cast { ty, expr } => ctx.ibox(INDENT, |ctx| { + ctx.word("("); + print_declarator(*ty, None, ctx); + ctx.word(")"); + + ctx.nbsp(); + expr.print_to(ctx); + }), + CExprKind::Call { callee, args } => ctx.ibox(INDENT, |ctx| { + callee.print_to(ctx); + ctx.cbox_delim(INDENT, ("(", ")"), 0, |ctx| { + ctx.seperated(",", args, |ctx, arg| arg.print_to(ctx)); + }); + }), + CExprKind::Member { expr, arrow, field } => ctx.cbox(INDENT, |ctx| { + expr.print_to(ctx); + ctx.zerobreak(); + if *arrow { + ctx.word("->"); + } else { + ctx.word("."); + } + ctx.word(field.to_string()); + }), + } + } +} diff --git a/crates/rustc_codegen_c_ast/src/func.rs b/crates/rustc_codegen_c_ast/src/func.rs new file mode 100644 index 0000000..b0d3904 --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/func.rs @@ -0,0 +1,91 @@ +//! This module defines AST nodes for C functions. + +use std::cell::{Cell, RefCell}; + +use rustc_data_structures::intern::Interned; + +use crate::expr::CValue; +use crate::pretty::{Print, PrinterCtx}; +use crate::stmt::{print_compound, CStmt}; +use crate::ty::{print_declarator, CTy}; +use crate::ModuleCtx; + +/// C functions definition. +pub type CFunc<'mx> = Interned<'mx, CFuncKind<'mx>>; + +/// C function definition. +#[derive(Debug, Clone)] +pub struct CFuncKind<'mx> { + /// Function name. + pub name: &'mx str, + /// Return type. + pub ty: CTy<'mx>, + /// Function parameters. + pub params: Vec<(CTy<'mx>, CValue<'mx>)>, + /// Function body. + pub body: RefCell>>, + /// A counter for local variables, for generating unique names. + local_var_counter: Cell, +} + +impl<'mx> CFuncKind<'mx> { + /// Make a new function definition. + pub fn new(name: &'mx str, ty: CTy<'mx>, params: impl IntoIterator>) -> Self { + let params = params + .into_iter() + .enumerate() + .map(|(i, ty)| (ty, CValue::Local(i))) + .collect::>(); + let local_var_counter = Cell::new(params.len()); + + Self { name, ty, params, body: RefCell::new(Vec::new()), local_var_counter } + } + + /// Push a statement to the end of the function body. + pub fn push_stmt(&self, stmt: CStmt<'mx>) { + self.body.borrow_mut().push(stmt); + } + + /// Get a new unique local variable. + pub fn next_local_var(&self) -> CValue { + let val = CValue::Local(self.local_var_counter.get()); + self.local_var_counter.set(self.local_var_counter.get() + 1); + val + } +} + +impl<'mx> ModuleCtx<'mx> { + /// Create a new function definition. + pub fn func(&self, func: CFuncKind<'mx>) -> &'mx CFuncKind<'mx> { + self.arena().alloc(func) + } +} + +impl Print for CFunc<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + ctx.ibox(0, |ctx| { + print_signature(*self, ctx); + ctx.softbreak(); // I don't know how to avoid a newline here + print_compound(&self.0.body.borrow(), ctx); + }) + } +} + +pub(crate) fn print_func_decl(func: CFunc, ctx: &mut PrinterCtx) { + print_signature(func, ctx); + ctx.word(";"); +} + +fn print_signature(func: CFunc, ctx: &mut PrinterCtx) { + ctx.ibox(0, |ctx| { + print_declarator(func.0.ty, Some(CValue::Func(func.0.name)), ctx); + + ctx.valign_delim(("(", ")"), |ctx| { + ctx.seperated(",", &func.0.params, |ctx, (ty, name)| { + ctx.ibox(0, |ctx| { + print_declarator(*ty, Some(*name), ctx); + }) + }) + }); + }); +} diff --git a/crates/rustc_codegen_c_ast/src/lib.rs b/crates/rustc_codegen_c_ast/src/lib.rs new file mode 100644 index 0000000..173c736 --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/lib.rs @@ -0,0 +1,69 @@ +//! This crate defines the subset of the C AST used by the C codegen backend. +//! +//! Its primary purpose is to facilitate the construction and pretty-printing of C code. +//! Note that parsing is not included in this crate. +//! +//! It also provides utilities to assist with building the C AST and integrating +//! with the `rustc_codegen_ssa` backend. +#![feature(rustc_private)] + +use std::fmt::{self, Display}; + +use crate::pretty::Print; + +extern crate rustc_arena; +extern crate rustc_ast_pretty; +extern crate rustc_data_structures; +extern crate rustc_driver; +extern crate rustc_type_ir; + +pub mod arena; +pub mod decl; +pub mod expr; +pub mod func; +pub mod module; +pub mod pretty; +pub mod stmt; +pub mod ty; + +/// A context containing a C file's AST nodes which are under construction. +#[derive(Clone, Copy)] +pub struct ModuleCtx<'mx>(pub &'mx ModuleArena<'mx>); + +impl<'mx> ModuleCtx<'mx> { + /// Get the memory arena for the context. + pub fn arena(&self) -> &'mx arena::Arena<'mx> { + &self.0.arena + } + + /// Get the AST node for the module. + pub fn module(&self) -> &'mx module::Module<'mx> { + &self.0.module + } + + /// Allocate a string in the arena. + pub fn alloc_str(&self, s: &str) -> &'mx str { + self.arena().alloc_str(s) + } +} + +impl<'mx> Display for ModuleCtx<'mx> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut printer = pretty::PrinterCtx::new(); + self.module().print_to(&mut printer); + write!(f, "{}", printer.finish()) + } +} + +pub struct ModuleArena<'mx> { + /// The memory arena for the module. + pub arena: arena::Arena<'mx>, + /// The module's AST node. + pub module: module::Module<'mx>, +} + +impl<'mx> ModuleArena<'mx> { + pub fn new(helper: &'static str) -> Self { + Self { arena: arena::Arena::default(), module: module::Module::new(helper) } + } +} diff --git a/crates/rustc_codegen_c_ast/src/module.rs b/crates/rustc_codegen_c_ast/src/module.rs new file mode 100644 index 0000000..d279cdb --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/module.rs @@ -0,0 +1,83 @@ +//! This module defines AST nodes for C modules. + +use std::cell::RefCell; + +use crate::decl::CDecl; +use crate::func::{print_func_decl, CFunc}; +use crate::pretty::{Print, PrinterCtx}; + +/// C module definition. +#[derive(Debug, Clone)] +pub struct Module<'mx> { + /// Includes. Only the file name is recorded, without the angle brackets. + pub includes: RefCell>, + /// A piece of helper code to be included at the beginning of the file. + pub helper: &'static str, + /// Declarations. + pub decls: RefCell>>, + /// Function definitions. + pub funcs: RefCell>>, +} + +impl<'mx> Module<'mx> { + /// Make a new module definition. + pub fn new(helper: &'static str) -> Self { + Self { + includes: RefCell::new(Vec::new()), + helper, + decls: RefCell::new(Vec::new()), + funcs: RefCell::new(Vec::new()), + } + } + + /// Push an include directive to the end of the includes list. + pub fn push_include(&self, include: &'static str) { + self.includes.borrow_mut().push(include); + } + + /// Push a declaration to the end of the declarations list. + pub fn push_decl(&self, decl: CDecl<'mx>) { + self.decls.borrow_mut().push(decl); + } + + /// Push a function definition to the end of the function definitions list. + pub fn push_func(&self, func: CFunc<'mx>) { + self.funcs.borrow_mut().push(func); + } +} + +impl Print for Module<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + ctx.cbox(0, |ctx| { + for &include in self.includes.borrow().iter() { + ctx.word("#include <"); + ctx.word(include); + ctx.word(">"); + ctx.hardbreak(); + } + + ctx.hardbreak(); + + ctx.word(self.helper); + + for &decl in self.decls.borrow().iter() { + ctx.hardbreak(); + ctx.hardbreak(); + decl.print_to(ctx); + } + + for &func in self.funcs.borrow().iter() { + ctx.hardbreak(); + print_func_decl(func, ctx); + } + + for &func in self.funcs.borrow().iter() { + ctx.hardbreak(); + ctx.hardbreak(); + func.print_to(ctx); + } + + ctx.hardbreak(); + }); + } +} diff --git a/crates/rustc_codegen_c_ast/src/pretty.rs b/crates/rustc_codegen_c_ast/src/pretty.rs new file mode 100644 index 0000000..4d9b30b --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/pretty.rs @@ -0,0 +1,149 @@ +//! Pretty printing support for C AST nodes. + +use std::borrow::Cow; + +use rustc_ast_pretty::pp; + +/// Default indentation size. +pub const INDENT: isize = 2; + +/// Pretty printer, see [`rustc_ast_pretty::pp::Printer`] for details. +pub struct PrinterCtx { + pp: pp::Printer, +} + +impl Default for PrinterCtx { + fn default() -> Self { + Self::new() + } +} + +impl PrinterCtx { + pub fn new() -> Self { + Self { pp: pp::Printer::new() } + } + + pub fn finish(self) -> String { + self.pp.eof() + } + + pub(crate) fn seperated( + &mut self, + sep: &'static str, + elements: &[T], + mut op: impl FnMut(&mut Self, &T), + ) { + if let Some((first, rest)) = elements.split_first() { + op(self, first); + for elt in rest { + self.pp.word_space(sep); + op(self, elt); + } + } + } + + /// Inconsistent breaking box + /// + /// See the module document of [`rustc_ast_pretty::pp`] for details. + pub(crate) fn ibox(&mut self, indent: isize, op: impl FnOnce(&mut Self)) { + self.pp.ibox(indent); + op(self); + self.pp.end(); + } + + /// Inconsistent breaking box, with delimiters surrounding the inner content + /// + /// This is often used for printing content inside parentheses, e.g. function + /// arguments. + pub(crate) fn ibox_delim( + &mut self, + indent: isize, + delim: (&'static str, &'static str), + padding: usize, + op: impl FnOnce(&mut Self), + ) { + self.ibox(indent, |this| { + this.word(delim.0); + this.pp.break_offset(padding, 0); + op(this); + this.word(delim.1); + }); + } + + /// Consistent breaking box + /// + /// See the module document of [`rustc_ast_pretty::pp`] for details. + pub(crate) fn cbox(&mut self, indent: isize, op: impl FnOnce(&mut Self)) { + self.pp.cbox(indent); + op(self); + self.pp.end(); + } + + /// Consistent breaking box, with delimiters surrounding the inner content + /// + /// This is often used for printing content inside braces, e.g. a block of + /// statements. + pub(crate) fn cbox_delim( + &mut self, + indent: isize, + delim: (&'static str, &'static str), + padding: usize, + op: impl FnOnce(&mut Self), + ) { + self.cbox(indent, |this| { + this.word(delim.0); + this.pp.break_offset(padding, 0); + op(this); + this.pp.break_offset(padding, -indent); + this.word(delim.1); + }); + } + + pub(crate) fn valign(&mut self, op: impl FnOnce(&mut Self)) { + self.pp.visual_align(); + op(self); + self.pp.end(); + } + + pub(crate) fn valign_delim( + &mut self, + delim: (&'static str, &'static str), + op: impl FnOnce(&mut Self), + ) { + self.valign(|this| { + this.word(delim.0); + op(this); + this.word(delim.1); + }); + } + + /// Soft break: space if fits, otherwise newline + pub(crate) fn softbreak(&mut self) { + self.pp.space() + } + + /// Hard break: always newline + pub(crate) fn hardbreak(&mut self) { + self.pp.hardbreak(); + } + + /// Zero break: nothing if fits, otherwise newline + pub(crate) fn zerobreak(&mut self) { + self.pp.zerobreak(); + } + + /// Print a string + pub(crate) fn word(&mut self, s: impl Into>) { + self.pp.word(s) + } + + /// Non-breaking space, the same as `word(" ")` + pub(crate) fn nbsp(&mut self) { + self.pp.nbsp() + } +} + +/// Trait for a type that can be pretty printed. +pub trait Print { + fn print_to(&self, ctx: &mut PrinterCtx); +} diff --git a/crates/rustc_codegen_c_ast/src/stmt.rs b/crates/rustc_codegen_c_ast/src/stmt.rs new file mode 100644 index 0000000..7c2df36 --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/stmt.rs @@ -0,0 +1,85 @@ +//! This module defines the AST nodes for C statements. + +use crate::decl::CDecl; +use crate::expr::CExpr; +use crate::pretty::{Print, PrinterCtx, INDENT}; +use crate::ModuleCtx; + +/// C statement. +pub type CStmt<'mx> = &'mx CStmtKind<'mx>; + +/// C statement. +#[derive(Debug, Clone)] +pub enum CStmtKind<'mx> { + /// Compound statement, which is a sequence of statements enclosed in braces. + Compound(Vec>), + /// Return statement. + Return(Option>), + /// Declaration statement, e.g. `int x = 42;`. + Decl(CDecl<'mx>), + /// Expression statement, e.g. `foo(x + 1);`. + Expr(CExpr<'mx>), +} + +impl<'mx> ModuleCtx<'mx> { + /// Create a new statement. + pub fn stmt(self, stmt: CStmtKind<'mx>) -> CStmt<'mx> { + self.arena().alloc(stmt) + } + + /// Create a compound statement. + pub fn compound(self, stmts: Vec>) -> CStmt<'mx> { + self.stmt(CStmtKind::Compound(stmts)) + } + + /// Create a return statement. + pub fn ret(self, expr: Option>) -> CStmt<'mx> { + self.stmt(CStmtKind::Return(expr)) + } + + /// Create a declaration statement. + pub fn decl_stmt(self, decl: CDecl<'mx>) -> CStmt<'mx> { + self.stmt(CStmtKind::Decl(decl)) + } + + /// Create an expression statement. + pub fn expr_stmt(self, expr: CExpr<'mx>) -> CStmt<'mx> { + self.stmt(CStmtKind::Expr(expr)) + } +} + +impl Print for CStmt<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + match self { + CStmtKind::Compound(stmts) => print_compound(stmts, ctx), + CStmtKind::Return(ret) => { + ctx.ibox(INDENT, |ctx| { + ctx.word("return"); + if let Some(ret) = ret { + ctx.softbreak(); + ret.print_to(ctx); + } + ctx.word(";"); + }); + } + CStmtKind::Decl(decl) => decl.print_to(ctx), + CStmtKind::Expr(expr) => { + expr.print_to(ctx); + ctx.word(";"); + } + } + } +} + +/// Print a compound statement. +pub(crate) fn print_compound(stmts: &[CStmt], ctx: &mut PrinterCtx) { + ctx.cbox_delim(INDENT, ("{", "}"), 1, |ctx| { + if let Some((first, rest)) = stmts.split_first() { + first.print_to(ctx); + for stmt in rest { + ctx.hardbreak(); + stmt.print_to(ctx); + } + } + }); +} diff --git a/crates/rustc_codegen_c_ast/src/ty.rs b/crates/rustc_codegen_c_ast/src/ty.rs new file mode 100644 index 0000000..0f12668 --- /dev/null +++ b/crates/rustc_codegen_c_ast/src/ty.rs @@ -0,0 +1,251 @@ +//! This module defines the AST nodes for C types. + +use rustc_data_structures::intern::Interned; +use rustc_type_ir::{IntTy, UintTy}; + +use crate::expr::CValue; +use crate::pretty::{Print, PrinterCtx}; +use crate::ModuleCtx; + +/// C types. +/// +/// A C type is either a primitive type or a complex type. Primitive types are +/// the basic types like `int` and `char`, while complex types are types that +/// are built from primitive types, like pointers and arrays. +/// +/// Complex types are always interned, and thus should be unique in a specific +/// context. See [`CTyKind`] for more information. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CTy<'mx> { + /// The C `void` type. + Void, + /// The C boolean type. + Bool, + /// The C `char` type. + Char, + /// A signed integer type. + Int(CIntTy), + /// An unsigned integer type. + UInt(CUintTy), + /// A non-primitive C type, e.g. a pointer type. + /// + /// This is an interned reference to a complex type. + Ref(Interned<'mx, CTyKind<'mx>>), +} + +impl<'mx> CTy<'mx> { + /// Whether the type is a signed integer. + pub fn is_signed(self) -> bool { + matches!(self, CTy::Int(_)) + } + + /// The unsigned version of this type. + /// + /// ## Panic + /// + /// Panics if the type is not a signed integer. + pub fn to_unsigned(self) -> Self { + match self { + CTy::Int(ty) => CTy::UInt(ty.to_unsigned()), + _ => unreachable!(), + } + } + + /// Get the corresponding C type name. + /// + /// This function should be only used for primitive types. + /// + /// ## Panic + /// + /// Panics if the type is not a primitive type. + pub fn to_str(self) -> &'static str { + match self { + CTy::Void => "void", + CTy::Bool => "_Bool", + CTy::Char => "char", + CTy::Int(ty) => ty.to_str(), + CTy::UInt(ty) => ty.to_str(), + CTy::Ref(_) => unreachable!(), + } + } + + /// The maximum value of this type. From ``. + /// + /// This function should be only used for integer types (signed or unsigned). + /// + /// ## Panic + /// + /// Panics if the type is not an integer type. + pub fn max_value(self) -> &'static str { + match self { + CTy::Int(ty) => ty.max_value(), + CTy::UInt(ty) => ty.max_value(), + _ => unreachable!(), + } + } +} + +/// C primitive types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CIntTy { + Isize, + I8, + I16, + I32, + I64, +} + +impl CIntTy { + /// Get the unsigned version of this type. + pub fn to_unsigned(self) -> CUintTy { + match self { + CIntTy::Isize => CUintTy::Usize, + CIntTy::I8 => CUintTy::U8, + CIntTy::I16 => CUintTy::U16, + CIntTy::I32 => CUintTy::U32, + CIntTy::I64 => CUintTy::U64, + } + } + + /// Get the corresponding C type name. + pub fn to_str(self) -> &'static str { + match self { + CIntTy::Isize => "size_t", + CIntTy::I8 => "int8_t", + CIntTy::I16 => "int16_t", + CIntTy::I32 => "int32_t", + CIntTy::I64 => "int64_t", + } + } + + /// The maximum value of this type. From ``. + pub fn max_value(self) -> &'static str { + match self { + CIntTy::Isize => "SIZE_MAX", + CIntTy::I8 => "INT8_MAX", + CIntTy::I16 => "INT16_MAX", + CIntTy::I32 => "INT32_MAX", + CIntTy::I64 => "INT64_MAX", + } + } +} + +/// C primitive types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CUintTy { + Usize, + U8, + U16, + U32, + U64, +} + +impl CUintTy { + /// Get the corresponding C type name. + pub fn to_str(self) -> &'static str { + match self { + CUintTy::Usize => "size_t", + CUintTy::U8 => "uint8_t", + CUintTy::U16 => "uint16_t", + CUintTy::U32 => "uint32_t", + CUintTy::U64 => "uint64_t", + } + } + + /// The maximum value of this type. From ``. + pub fn max_value(self) -> &'static str { + match self { + CUintTy::Usize => "SIZE_MAX", + CUintTy::U8 => "UINT8_MAX", + CUintTy::U16 => "UINT16_MAX", + CUintTy::U32 => "UINT32_MAX", + CUintTy::U64 => "UINT64_MAX", + } + } +} + +/// Complex C types, e.g. pointers and arrays. +/// +/// This type is interned, and thus should be unique in a specific context. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CTyKind<'mx> { + /// A pointer type. + Pointer(CTy<'mx>), +} + +impl<'mx> ModuleCtx<'mx> { + /// Get the type of an signed integer + pub fn get_int_type(&self, int: IntTy) -> CTy<'mx> { + match int { + IntTy::Isize => CTy::Int(CIntTy::Isize), + IntTy::I8 => CTy::Int(CIntTy::I8), + IntTy::I16 => CTy::Int(CIntTy::I16), + IntTy::I32 => CTy::Int(CIntTy::I32), + IntTy::I64 => CTy::Int(CIntTy::I64), + IntTy::I128 => unimplemented!("i128 not supported yet"), + } + } + + /// Get the type of an unsigned integer + pub fn get_uint_type(&self, uint: UintTy) -> CTy<'mx> { + match uint { + UintTy::Usize => CTy::UInt(CUintTy::Usize), + UintTy::U8 => CTy::UInt(CUintTy::U8), + UintTy::U16 => CTy::UInt(CUintTy::U16), + UintTy::U32 => CTy::UInt(CUintTy::U32), + UintTy::U64 => CTy::UInt(CUintTy::U64), + UintTy::U128 => unimplemented!("u128 not supported yet"), + } + } +} + +/// Print a C declarator. +/// +/// A declarator is a type with an optional identifier and pointer indirections, +/// e.g. `int *x`. +/// +/// This function is necessary because the C declarator syntax is quite complex +/// when the type becomes more complex, e.g. `int (*x)[10]`. +/// +/// When `val` is `None`, this prints an abstract declarator, or in other words, +/// a standalone type without an identifier. +pub(crate) fn print_declarator(mut ty: CTy, val: Option, ctx: &mut PrinterCtx) { + enum DeclaratorPart<'mx> { + Ident(Option>), + Ptr, + } + + impl Print for DeclaratorPart<'_> { + fn print_to(&self, ctx: &mut PrinterCtx) { + match self { + DeclaratorPart::Ident(val) => { + if let &Some(val) = val { + val.print_to(ctx); + } + } + DeclaratorPart::Ptr => { + ctx.word("*"); + } + } + } + } + + let mut decl_parts = std::collections::VecDeque::new(); + decl_parts.push_front(DeclaratorPart::Ident(val)); + while let CTy::Ref(kind) = ty { + match kind.0 { + CTyKind::Pointer(_) => decl_parts.push_front(DeclaratorPart::Ptr), + } + ty = match kind.0 { + CTyKind::Pointer(ty) => *ty, + }; + } + + ctx.word(ty.to_str()); // `ty` should be a primitive type here + if val.is_some() { + ctx.nbsp(); + } + for part in decl_parts { + part.print_to(ctx); + } +} diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_decl_var.out b/crates/rustc_codegen_c_ast/tests/blessed/test_decl_var.out new file mode 100644 index 0000000..cce18c2 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_decl_var.out @@ -0,0 +1 @@ +int32_t _42 = 42; diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_binary.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_binary.out new file mode 100644 index 0000000..f8ef45b --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_binary.out @@ -0,0 +1 @@ +(1 + 2) diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_call.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_call.out new file mode 100644 index 0000000..22594bf --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_call.out @@ -0,0 +1 @@ +foo(1, 2) diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_cast.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_cast.out new file mode 100644 index 0000000..0f2cf07 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_cast.out @@ -0,0 +1 @@ +(int32_t) 42 diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_complex.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_complex.out new file mode 100644 index 0000000..78bcbdf --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_complex.out @@ -0,0 +1 @@ +foo(1, (int32_t) (1 + 2)).bar diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_member.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_member.out new file mode 100644 index 0000000..d13bcb9 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_member.out @@ -0,0 +1 @@ +_42.foo diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_expr_raw.out b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_raw.out new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_expr_raw.out @@ -0,0 +1 @@ +42 diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_function.out b/crates/rustc_codegen_c_ast/tests/blessed/test_function.out new file mode 100644 index 0000000..0aba89a --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_function.out @@ -0,0 +1,6 @@ +int32_t foo(int32_t _0) +{ + int32_t _1; + (_1 = 1); + return _1; +} diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_module.out b/crates/rustc_codegen_c_ast/tests/blessed/test_module.out new file mode 100644 index 0000000..893dcdc --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_module.out @@ -0,0 +1,13 @@ +#include + +// blessed test + +int32_t _42; +int32_t foo(int32_t _0); + +int32_t foo(int32_t _0) +{ + int32_t _1; + (_1 = 1); + return _1; +} diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_block.out b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_block.out new file mode 100644 index 0000000..90f2fc1 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_block.out @@ -0,0 +1 @@ +{ foo(1, 2); } diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_decl.out b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_decl.out new file mode 100644 index 0000000..cce18c2 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_decl.out @@ -0,0 +1 @@ +int32_t _42 = 42; diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_expr.out b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_expr.out new file mode 100644 index 0000000..e6e6415 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_expr.out @@ -0,0 +1 @@ +foo(1, 2); diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_if.out b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_if.out new file mode 100644 index 0000000..245f3d1 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_stmt_if.out @@ -0,0 +1 @@ +return foo(1, 2); diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_value_func.out b/crates/rustc_codegen_c_ast/tests/blessed/test_value_func.out new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_value_func.out @@ -0,0 +1 @@ +foo diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_value_local.out b/crates/rustc_codegen_c_ast/tests/blessed/test_value_local.out new file mode 100644 index 0000000..5377f9d --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_value_local.out @@ -0,0 +1 @@ +_42 diff --git a/crates/rustc_codegen_c_ast/tests/blessed/test_value_scalar.out b/crates/rustc_codegen_c_ast/tests/blessed/test_value_scalar.out new file mode 100644 index 0000000..d81cc07 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed/test_value_scalar.out @@ -0,0 +1 @@ +42 diff --git a/crates/rustc_codegen_c_ast/tests/blessed_test/mod.rs b/crates/rustc_codegen_c_ast/tests/blessed_test/mod.rs new file mode 100644 index 0000000..3561aa9 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/blessed_test/mod.rs @@ -0,0 +1,43 @@ +use std::path::Path; + +use rustc_codegen_c_ast::pretty::{Print, PrinterCtx}; +use rustc_codegen_c_ast::{ModuleArena, ModuleCtx}; + +/// Run a blessed test. +/// +/// When the environment variable `RUST_BLESS` is set, this function will store +/// the output of the test case in the corresponding file, otherwise it will +/// compare the output of the test case with the stored output, making sure they +/// are the same. +#[track_caller] +pub fn blessed_test(name: &str, bless: impl Fn() -> String) { + let test_case_path = Path::new("tests/blessed").join(name).with_extension("out"); + test_case_path.parent().map(std::fs::create_dir_all); + + let mut output = bless(); + if !output.ends_with('\n') { + output.push('\n'); // Ensure the output ends with a newline + } + + let expected_output = std::fs::read_to_string(&test_case_path).unwrap_or_default(); + if std::env::var("RUST_BLESS").is_ok() { + std::fs::write(test_case_path, output).unwrap(); + } else { + assert_eq!(output, expected_output, "blessed test '{name}' failed"); + } +} + +/// Run a blessed test for a printable value. +pub fn printer_test(name: &str, test: F) +where + F: for<'mx> Fn(ModuleCtx<'mx>) -> Box, // anyway to avoid the Box? +{ + blessed_test(name, || { + let module = ModuleArena::new("// blessed test"); + let ctx = ModuleCtx(&module); + + let mut pp = PrinterCtx::new(); + test(ctx).print_to(&mut pp); + pp.finish() + }); +} diff --git a/crates/rustc_codegen_c_ast/tests/decl.rs b/crates/rustc_codegen_c_ast/tests/decl.rs new file mode 100644 index 0000000..5cc4fa4 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/decl.rs @@ -0,0 +1,19 @@ +#![feature(rustc_private)] + +use blessed_test::*; +use rustc_codegen_c_ast::expr::CValue; +use rustc_type_ir::IntTy; + +extern crate rustc_driver; +extern crate rustc_type_ir; +mod blessed_test; + +#[test] +fn test_decl_var() { + printer_test("test_decl_var", |ctx| { + let ty = ctx.get_int_type(IntTy::I32); + let name = CValue::Local(42); + let value = ctx.value(CValue::Scalar(42)); + Box::new(ctx.var(name, ty, Some(value))) + }); +} diff --git a/crates/rustc_codegen_c_ast/tests/expr.rs b/crates/rustc_codegen_c_ast/tests/expr.rs new file mode 100644 index 0000000..3744315 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/expr.rs @@ -0,0 +1,84 @@ +#![feature(rustc_private)] + +use blessed_test::*; +use rustc_codegen_c_ast::expr::CValue; +use rustc_type_ir::IntTy; + +extern crate rustc_driver; +extern crate rustc_type_ir; +mod blessed_test; + +#[test] +fn test_value_scalar() { + printer_test("test_value_scalar", |_| Box::new(CValue::Scalar(42))); +} + +#[test] +fn test_value_local() { + printer_test("test_value_local", |_| Box::new(CValue::Local(42))); +} + +#[test] +fn test_value_func() { + printer_test("test_value_func", |_| Box::new(CValue::Func("foo"))); +} + +#[test] +fn test_expr_raw() { + printer_test("test_expr_raw", |ctx| Box::new(ctx.raw("42"))); +} + +#[test] +fn test_expr_binary() { + printer_test("test_expr_binary", |ctx| { + let lhs = ctx.value(CValue::Scalar(1)); + let rhs = ctx.value(CValue::Scalar(2)); + Box::new(ctx.binary(lhs, rhs, "+")) + }); +} + +#[test] +fn test_expr_cast() { + printer_test("test_expr_cast", |ctx| { + let ty = ctx.get_int_type(IntTy::I32); + let expr = ctx.value(CValue::Scalar(42)); + Box::new(ctx.cast(ty, expr)) + }); +} + +#[test] +fn test_expr_call() { + printer_test("test_expr_call", |ctx| { + let callee = ctx.value(CValue::Func("foo")); + let args = vec![ctx.value(CValue::Scalar(1)), ctx.value(CValue::Scalar(2))]; + Box::new(ctx.call(callee, args)) + }); +} + +#[test] +fn test_expr_member() { + printer_test("test_expr_member", |ctx| { + let expr = ctx.value(CValue::Local(42)); + Box::new(ctx.member(expr, "foo")) + }); +} + +#[test] +fn test_expr_complex() { + printer_test("test_expr_complex", |ctx| { + let lhs = ctx.value(CValue::Scalar(1)); + let rhs = ctx.value(CValue::Scalar(2)); + let expr = ctx.binary(lhs, rhs, "+"); + + let ty = ctx.get_int_type(IntTy::I32); + let cast = ctx.cast(ty, expr); + + let callee = ctx.value(CValue::Func("foo")); + let args = vec![ctx.value(CValue::Scalar(1)), cast]; + let call = ctx.call(callee, args); + + let member = ctx.member(call, "bar"); + + Box::new(member) + }); +} diff --git a/crates/rustc_codegen_c_ast/tests/func.rs b/crates/rustc_codegen_c_ast/tests/func.rs new file mode 100644 index 0000000..3185b8a --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/func.rs @@ -0,0 +1,26 @@ +#![feature(rustc_private)] + +use blessed_test::*; +use rustc_codegen_c_ast::expr::CValue; +use rustc_codegen_c_ast::func::{CFunc, CFuncKind}; +use rustc_type_ir::IntTy; + +extern crate rustc_driver; +extern crate rustc_type_ir; +mod blessed_test; + +#[test] +fn test_function() { + printer_test("test_function", |ctx| { + let func = ctx.func(CFuncKind::new( + "foo", + ctx.get_int_type(IntTy::I32), + vec![ctx.get_int_type(IntTy::I32)], + )); + let x = func.next_local_var(); + func.push_stmt(ctx.decl_stmt(ctx.var(x, ctx.get_int_type(IntTy::I32), None))); + func.push_stmt(ctx.expr_stmt(ctx.binary(ctx.value(x), ctx.value(CValue::Scalar(1)), "="))); + func.push_stmt(ctx.ret(Some(ctx.value(x)))); + Box::new(CFunc::new_unchecked(func)) + }); +} diff --git a/crates/rustc_codegen_c_ast/tests/module.rs b/crates/rustc_codegen_c_ast/tests/module.rs new file mode 100644 index 0000000..e1a65e8 --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/module.rs @@ -0,0 +1,32 @@ +#![feature(rustc_private)] + +use blessed_test::*; +use rustc_codegen_c_ast::expr::CValue; +use rustc_codegen_c_ast::func::{CFunc, CFuncKind}; +use rustc_type_ir::IntTy; + +extern crate rustc_driver; +extern crate rustc_type_ir; +mod blessed_test; + +#[test] +fn test_module() { + printer_test("test_module", |ctx| { + let module = ctx.module(); + module.push_include("stdio.h"); + + module.push_decl(ctx.var(CValue::Local(42), ctx.get_int_type(IntTy::I32), None)); + + let func = ctx.func(CFuncKind::new( + "foo", + ctx.get_int_type(IntTy::I32), + vec![ctx.get_int_type(IntTy::I32)], + )); + let x = func.next_local_var(); + func.push_stmt(ctx.decl_stmt(ctx.var(x, ctx.get_int_type(IntTy::I32), None))); + func.push_stmt(ctx.expr_stmt(ctx.binary(ctx.value(x), ctx.value(CValue::Scalar(1)), "="))); + func.push_stmt(ctx.ret(Some(ctx.value(x)))); + module.push_func(CFunc::new_unchecked(func)); + Box::new(module.clone()) + }); +} diff --git a/crates/rustc_codegen_c_ast/tests/stmt.rs b/crates/rustc_codegen_c_ast/tests/stmt.rs new file mode 100644 index 0000000..581e85e --- /dev/null +++ b/crates/rustc_codegen_c_ast/tests/stmt.rs @@ -0,0 +1,51 @@ +#![feature(rustc_private)] + +use blessed_test::*; +use rustc_codegen_c_ast::expr::CValue; +use rustc_type_ir::IntTy; + +extern crate rustc_driver; +extern crate rustc_type_ir; +mod blessed_test; + +#[test] +fn test_stmt_expr() { + printer_test("test_stmt_expr", |ctx| { + let callee = ctx.value(CValue::Func("foo")); + let args = vec![ctx.value(CValue::Scalar(1)), ctx.value(CValue::Scalar(2))]; + let expr = ctx.call(callee, args); + Box::new(ctx.expr_stmt(expr)) + }); +} + +#[test] +fn test_stmt_decl() { + printer_test("test_stmt_decl", |ctx| { + let ty = ctx.get_int_type(IntTy::I32); + let name = CValue::Local(42); + let value = ctx.value(CValue::Scalar(42)); + let decl = ctx.var(name, ty, Some(value)); + Box::new(ctx.decl_stmt(decl)) + }); +} + +#[test] +fn test_stmt_block() { + printer_test("test_stmt_block", |ctx| { + let callee = ctx.value(CValue::Func("foo")); + let args = vec![ctx.value(CValue::Scalar(1)), ctx.value(CValue::Scalar(2))]; + let expr = ctx.call(callee, args); + let stmt = ctx.expr_stmt(expr); + Box::new(ctx.compound(vec![stmt])) + }); +} + +#[test] +fn test_stmt_ret() { + printer_test("test_stmt_if", |ctx| { + let callee = ctx.value(CValue::Func("foo")); + let args = vec![ctx.value(CValue::Scalar(1)), ctx.value(CValue::Scalar(2))]; + let expr = ctx.call(callee, args); + Box::new(ctx.ret(Some(expr))) + }); +}