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)))
+ });
+}