[clang] [CIR] Upstream simple function bodies (PR #127674)

David Olsen via cfe-commits cfe-commits at lists.llvm.org
Tue Feb 18 09:46:12 PST 2025


https://github.com/dkolsen-pgi created https://github.com/llvm/llvm-project/pull/127674

Enable ClangIR generation for very simple functions. The functions have to return `void` or an integral type, contain only compound statements or `return` statements, and `return` statement expressions can only be integral literals of the correct type. The functions can have parameters, but those are currently ignored because there is no way to access them.

This change intentionally focuses on breadth (introducing scopes, statements, and expressions) rather than depth, because it enables people to work on upstreaming in parallel without interference.

The new ClangIR ops in this change are `ReturnOp`, `YieldOp`, `ScopeOp`, and `TrapOp`. These operations are complete (except for the `ParentOneOf` property) and shouldn't require further upstreaming changes. Significant additions were made to `FuncOp`, adding a type and a region, but that operation is still far from complete.

The classes `ScalarExprEmitter` and `CIRGenFunction`, along with the `emit*` functions in `CIRGenFunction` that generate ClangIR for statements, are new in this change. All of these are very incomplete and will be filled out in later upstreaming patches.

Existing test `hello.c` is removed and replaced by the new test `func-simple.cpp`. This tests all forms of functions that are currently supported.

>From c0f10395ec6bff05bdb9eb04ed2c1f349c647d50 Mon Sep 17 00:00:00 2001
From: David Olsen <dolsen at nvidia.com>
Date: Tue, 18 Feb 2025 09:41:35 -0800
Subject: [PATCH] [CIR] Upstream simple function bodies

Enable ClangIR generation for very simple functions. The functions have
to return `void` or an integral type, contain only compound statements
or `return` statements, and `return` statement expressions can only be
integral literals of the correct type. The functions can have
parameters, but those are currently ignored because there is no way to
access them.

This change intentionally focuses on breadth (introducing scopes,
statements, and expressions) rather than depth, because it enables
people to work on upstreaming in parallel without interference.

The new ClangIR ops in this change are `ReturnOp`, `YieldOp`, `ScopeOp`,
and `TrapOp`. These operations are complete (except for the
`ParentOneOf` property) and shouldn't require further upstreaming
changes. Significant additions were made to `FuncOp`, adding a type and
a region, but that operation is still far from complete.

The classes `ScalarExprEmitter` and `CIRGenFunction`, along with the
`emit*` functions in `CIRGenFunction` that generate ClangIR for
statements, are new in this change. All of these are very incomplete and
will be filled out in later upstreaming patches.

Existing test `hello.c` is removed and replaced by the new test
`func-simple.cpp`. This tests all forms of functions that are currently
supported.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 238 ++++++++++++++++++-
 clang/include/clang/CIR/TypeEvaluationKind.h |  21 ++
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp   |  70 ++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp     | 203 ++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h       | 134 +++++++++++
 clang/lib/CIR/CodeGen/CIRGenModule.cpp       |  74 +++++-
 clang/lib/CIR/CodeGen/CIRGenModule.h         |  25 ++
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp         | 128 ++++++++++
 clang/lib/CIR/CodeGen/CIRGenTypes.cpp        |  21 +-
 clang/lib/CIR/CodeGen/CIRGenTypes.h          |  14 +-
 clang/lib/CIR/CodeGen/CMakeLists.txt         |   3 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 233 +++++++++++++++++-
 clang/test/CIR/func-simple.cpp               |  53 +++++
 clang/test/CIR/global-var-simple.cpp         |   2 +-
 clang/test/CIR/hello.c                       |   5 -
 15 files changed, 1197 insertions(+), 27 deletions(-)
 create mode 100644 clang/include/clang/CIR/TypeEvaluationKind.h
 create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
 create mode 100644 clang/lib/CIR/CodeGen/CIRGenFunction.cpp
 create mode 100644 clang/lib/CIR/CodeGen/CIRGenFunction.h
 create mode 100644 clang/lib/CIR/CodeGen/CIRGenStmt.cpp
 create mode 100644 clang/test/CIR/func-simple.cpp
 delete mode 100644 clang/test/CIR/hello.c

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index b15e0415360ea..45d39807b35c8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -11,8 +11,8 @@
 ///
 //===----------------------------------------------------------------------===//
 
-#ifndef LLVM_CLANG_CIR_DIALECT_IR_CIROPS
-#define LLVM_CLANG_CIR_DIALECT_IR_CIROPS
+#ifndef CLANG_CIR_DIALECT_IR_CIROPS_TD
+#define CLANG_CIR_DIALECT_IR_CIROPS_TD
 
 include "clang/CIR/Dialect/IR/CIRDialect.td"
 include "clang/CIR/Dialect/IR/CIRTypes.td"
@@ -115,6 +115,165 @@ def ConstantOp : CIR_Op<"const",
   let hasFolder = 1;
 }
 
+//===----------------------------------------------------------------------===//
+// ReturnOp
+//===----------------------------------------------------------------------===//
+
+def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp"]>,
+                                 Terminator]> {
+  let summary = "Return from function";
+  let description = [{
+    The "return" operation represents a return operation within a function.
+    The operation takes an optional operand and produces no results.
+    The operand type must match the signature of the function that contains
+    the operation.
+
+    ```mlir
+      func @foo() -> i32 {
+        ...
+        cir.return %0 : i32
+      }
+    ```
+  }];
+
+  // The return operation takes an optional input operand to return. This
+  // value must match the return type of the enclosing function.
+  let arguments = (ins Variadic<CIR_AnyType>:$input);
+
+  // The return operation only emits the input in the format if it is present.
+  let assemblyFormat = "($input^ `:` type($input))? attr-dict ";
+
+  // Allow building a ReturnOp with no return operand.
+  let builders = [
+    OpBuilder<(ins), [{ build($_builder, $_state, std::nullopt); }]>
+  ];
+
+  // Provide extra utility definitions on the c++ operation class definition.
+  let extraClassDeclaration = [{
+    bool hasOperand() { return getNumOperands() != 0; }
+  }];
+
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
+// YieldOp
+//===----------------------------------------------------------------------===//
+
+def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
+                               ParentOneOf<["ScopeOp"]>]> {
+  let summary = "Represents the default branching behaviour of a region";
+  let description = [{
+    The `cir.yield` operation terminates regions on different CIR operations,
+    and it is used to represent the default branching behaviour of a region.
+    Said branching behaviour is determinted by the parent operation. For
+    example, a yield in a `switch-case` region implies a fallthrough, while
+    a yield in a `cir.if` region implies a branch to the exit block, and so
+    on.
+
+    In some cases, it might yield an SSA value and the semantics of how the
+    values are yielded is defined by the parent operation. For example, a
+    `cir.ternary` operation yields a value from one of its regions.
+
+    As a general rule, `cir.yield` must be explicitly used whenever a region has
+    more than one block and no terminator, or within `cir.switch` regions not
+    `cir.return` terminated.
+
+    Examples:
+    ```mlir
+    cir.if %4 {
+      ...
+      cir.yield
+    }
+
+    cir.switch (%5) [
+      case (equal, 3) {
+        ...
+        cir.yield
+      }, ...
+    ]
+
+    cir.scope {
+      ...
+      cir.yield
+    }
+
+    %x = cir.scope {
+      ...
+      cir.yield %val
+    }
+
+    %y = cir.ternary {
+      ...
+      cir.yield %val : i32
+    } : i32
+    ```
+  }];
+
+  let arguments = (ins Variadic<CIR_AnyType>:$args);
+  let assemblyFormat = "($args^ `:` type($args))? attr-dict";
+  let builders = [
+    OpBuilder<(ins), [{ /* nothing to do */ }]>,
+  ];
+}
+
+//===----------------------------------------------------------------------===//
+// ScopeOp
+//===----------------------------------------------------------------------===//
+
+def ScopeOp : CIR_Op<"scope", [
+       DeclareOpInterfaceMethods<RegionBranchOpInterface>,
+       RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> {
+  let summary = "Represents a C/C++ scope";
+  let description = [{
+    `cir.scope` contains one region and defines a strict "scope" for all new
+    values produced within its blocks.
+
+    The region can contain an arbitrary number of blocks but usually defaults
+    to one and can optionally return a value (useful for representing values
+    coming out of C++ full-expressions) via `cir.yield`:
+
+
+    ```mlir
+    %rvalue = cir.scope {
+      ...
+      cir.yield %value
+    }
+    ```
+
+    The blocks can be terminated by `cir.yield`, `cir.return` or `cir.throw`.
+    If `cir.scope` yields no value, the `cir.yield` can be left out, and
+    will be inserted implicitly.
+  }];
+
+  let results = (outs Optional<CIR_AnyType>:$results);
+  let regions = (region AnyRegion:$scopeRegion);
+
+  let hasVerifier = 1;
+  let skipDefaultBuilders = 1;
+  let assemblyFormat = [{
+    custom<OmittedTerminatorRegion>($scopeRegion) (`:` type($results)^)? attr-dict
+  }];
+
+  let extraClassDeclaration = [{
+    /// Determine whether the scope is empty, meaning it contains a single block
+    /// terminated by a cir.yield.
+    bool isEmpty() {
+      auto &entry = getRegion().front();
+      return getRegion().hasOneBlock() &&
+        llvm::isa<YieldOp>(entry.front());
+      }
+    }];
+
+  let builders = [
+    // Scopes for yielding values.
+    OpBuilder<(ins
+              "llvm::function_ref<void(mlir::OpBuilder &, mlir::Type &, mlir::Location)>":$scopeBuilder)>,
+    // Scopes without yielding values.
+    OpBuilder<(ins "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$scopeBuilder)>
+  ];
+}
+
 //===----------------------------------------------------------------------===//
 // GlobalOp
 //===----------------------------------------------------------------------===//
@@ -158,25 +317,86 @@ def GlobalOp : CIR_Op<"global"> {
 // FuncOp
 //===----------------------------------------------------------------------===//
 
-// TODO(CIR): For starters, cir.func has only name, nothing else.  The other
-// properties of a function will be added over time as more of ClangIR is
-// upstreamed.
+// TODO(CIR): FuncOp is still a tiny shell of what it will become.  Many more
+// properties and attributes will be added as upstreaming continues.
 
-def FuncOp : CIR_Op<"func"> {
+def FuncOp : CIR_Op<"func", [
+  AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
+  IsolatedFromAbove
+]> {
   let summary = "Declare or define a function";
   let description = [{
     The `cir.func` operation defines a function, similar to the `mlir::FuncOp`
     built-in.
   }];
 
-  let arguments = (ins SymbolNameAttr:$sym_name);
+  let arguments = (ins SymbolNameAttr:$sym_name,
+                       TypeAttrOf<CIR_FuncType>:$function_type,
+                       OptionalAttr<DictArrayAttr>:$arg_attrs,
+                       OptionalAttr<DictArrayAttr>:$res_attrs);
+
+  let regions = (region AnyRegion:$body);
 
   let skipDefaultBuilders = 1;
 
-  let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name)>];
+  let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name,
+                                 "FuncType":$type)>];
+
+  let extraClassDeclaration = [{
+    /// Returns the region on the current operation that is callable. This may
+    /// return null in the case of an external callable object, e.g. an external
+    /// function.
+    ::mlir::Region *getCallableRegion();
+
+    /// Returns the results types that the callable region produces when
+    /// executed.
+    llvm::ArrayRef<mlir::Type> getCallableResults() {
+      return getFunctionType().getReturnTypes();
+    }
+
+    /// Returns the argument types of this function.
+    llvm::ArrayRef<mlir::Type> getArgumentTypes() {
+       return getFunctionType().getInputs();
+    }
+
+    /// Returns 0 or 1 result type of this function (0 in the case of a function
+    /// returing void)
+    llvm::ArrayRef<mlir::Type> getResultTypes() {
+       return getFunctionType().getReturnTypes();
+    }
+
+    /// Hook for OpTrait::FunctionOpInterfaceTrait, called after verifying that
+    /// the 'type' attribute is present and checks if it holds a function type.
+    /// Ensures getType, getNumFuncArguments, and getNumFuncResults can be
+    /// called safely.
+    llvm::LogicalResult verifyType();
+
+    //===------------------------------------------------------------------===//
+    // SymbolOpInterface Methods
+    //===------------------------------------------------------------------===//
+
+    bool isDeclaration();
+  }];
 
   let hasCustomAssemblyFormat = 1;
   let hasVerifier = 1;
 }
 
-#endif // LLVM_CLANG_CIR_DIALECT_IR_CIROPS
+//===----------------------------------------------------------------------===//
+// TrapOp
+//===----------------------------------------------------------------------===//
+
+def TrapOp : CIR_Op<"trap", [Terminator]> {
+  let summary = "Exit the program abnormally";
+  let description = [{
+    The cir.trap operation causes the program to exit abnormally. The
+    implementations may implement this operation with different mechanisms. For
+    example, an implementation may implement this operation by calling abort,
+    while another implementation may implement this operation by executing an
+    illegal instruction.
+  }];
+
+  let assemblyFormat = "attr-dict";
+}
+
+#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
diff --git a/clang/include/clang/CIR/TypeEvaluationKind.h b/clang/include/clang/CIR/TypeEvaluationKind.h
new file mode 100644
index 0000000000000..5d65eeb9d25b9
--- /dev/null
+++ b/clang/include/clang/CIR/TypeEvaluationKind.h
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_CIR_TYPEEVALUATIONKIND_H
+#define CLANG_CIR_TYPEEVALUATIONKIND_H
+
+namespace cir {
+
+// This is copied from clang/lib/CodeGen/CodeGenFunction.h.  That file (1) is
+// not available as an include from ClangIR files, and (2) has lots of stuff
+// that we don't want in ClangIR.
+enum TypeEvaluationKind { TEK_Scalar, TEK_Complex, TEK_Aggregate };
+
+} // namespace cir
+
+#endif // CLANG_CIR_TYPEEVALUATIONKIND_H
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
new file mode 100644
index 0000000000000..b802705ca8fdc
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Emit Expr nodes with scalar CIR types as CIR code.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenFunction.h"
+
+#include "clang/AST/Expr.h"
+#include "clang/AST/StmtVisitor.h"
+
+#include "mlir/IR/Value.h"
+
+#include <cassert>
+
+using namespace clang;
+using namespace clang::CIRGen;
+
+namespace {
+
+class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
+  CIRGenFunction &cgf;
+  CIRGenBuilderTy &builder;
+  bool ignoreResultAssign;
+
+public:
+  ScalarExprEmitter(CIRGenFunction &cgf, CIRGenBuilderTy &builder,
+                    bool ira = false)
+      : cgf(cgf), builder(builder), ignoreResultAssign(ira) {}
+
+  //===--------------------------------------------------------------------===//
+  //                            Visitor Methods
+  //===--------------------------------------------------------------------===//
+
+  mlir::Value Visit(Expr *e) {
+    return StmtVisitor<ScalarExprEmitter, mlir::Value>::Visit(e);
+  }
+
+  mlir::Value VisitStmt(Stmt *s) {
+    llvm_unreachable("Statement passed to ScalarExprEmitter");
+  }
+
+  mlir::Value VisitExpr(Expr *e) {
+    cgf.getCIRGenModule().errorNYI(
+        e->getSourceRange(), "scalar expression kind: ", e->getStmtClassName());
+    return {};
+  }
+
+  mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) {
+    mlir::Type type = cgf.convertType(e->getType());
+    return builder.create<cir::ConstantOp>(
+        cgf.getLoc(e->getExprLoc()), type,
+        builder.getAttr<cir::IntAttr>(type, e->getValue()));
+  }
+};
+} // namespace
+
+/// Emit the computation of the specified expression of scalar type.
+mlir::Value CIRGenFunction::emitScalarExpr(const Expr *e) {
+  assert(e && hasScalarEvaluationKind(e->getType()) &&
+         "Invalid scalar expression to emit");
+
+  return ScalarExprEmitter(*this, builder).Visit(const_cast<Expr *>(e));
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
new file mode 100644
index 0000000000000..a6d176166773d
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -0,0 +1,203 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Internal per-function state used for AST-to-ClangIR code gen
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenFunction.h"
+
+#include "clang/AST/GlobalDecl.h"
+
+#include <cassert>
+
+namespace clang::CIRGen {
+
+CIRGenFunction::CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
+                               bool suppressNewContext)
+    : CIRGenTypeCache(cgm), cgm{cgm}, builder(builder) {}
+
+CIRGenFunction::~CIRGenFunction() {}
+
+// This is copied from clang/lib/CodeGen/CodeGenFunction.cpp
+cir::TypeEvaluationKind CIRGenFunction::getEvaluationKind(QualType type) {
+  type = type.getCanonicalType();
+  while (true) {
+    switch (type->getTypeClass()) {
+#define TYPE(name, parent)
+#define ABSTRACT_TYPE(name, parent)
+#define NON_CANONICAL_TYPE(name, parent) case Type::name:
+#define DEPENDENT_TYPE(name, parent) case Type::name:
+#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(name, parent) case Type::name:
+#include "clang/AST/TypeNodes.inc"
+      llvm_unreachable("non-canonical or dependent type in IR-generation");
+
+    case Type::ArrayParameter:
+    case Type::HLSLAttributedResource:
+      llvm_unreachable("NYI");
+
+    case Type::Auto:
+    case Type::DeducedTemplateSpecialization:
+      llvm_unreachable("undeduced type in IR-generation");
+
+    // Various scalar types.
+    case Type::Builtin:
+    case Type::Pointer:
+    case Type::BlockPointer:
+    case Type::LValueReference:
+    case Type::RValueReference:
+    case Type::MemberPointer:
+    case Type::Vector:
+    case Type::ExtVector:
+    case Type::ConstantMatrix:
+    case Type::FunctionProto:
+    case Type::FunctionNoProto:
+    case Type::Enum:
+    case Type::ObjCObjectPointer:
+    case Type::Pipe:
+    case Type::BitInt:
+      return cir::TEK_Scalar;
+
+    // Complexes.
+    case Type::Complex:
+      return cir::TEK_Complex;
+
+    // Arrays, records, and Objective-C objects.
+    case Type::ConstantArray:
+    case Type::IncompleteArray:
+    case Type::VariableArray:
+    case Type::Record:
+    case Type::ObjCObject:
+    case Type::ObjCInterface:
+      return cir::TEK_Aggregate;
+
+    // We operate on atomic values according to their underlying type.
+    case Type::Atomic:
+      type = cast<AtomicType>(type)->getValueType();
+      continue;
+    }
+    llvm_unreachable("unknown type kind!");
+  }
+}
+
+mlir::Type CIRGenFunction::convertTypeForMem(QualType t) {
+  return cgm.getTypes().convertTypeForMem(t);
+}
+
+mlir::Type CIRGenFunction::convertType(QualType t) {
+  return cgm.getTypes().convertType(t);
+}
+
+mlir::Location CIRGenFunction::getLoc(SourceLocation srcLoc) {
+  // Some AST nodes might contain invalid source locations (e.g.
+  // CXXDefaultArgExpr), workaround that to still get something out.
+  if (srcLoc.isValid()) {
+    const SourceManager &sm = getContext().getSourceManager();
+    PresumedLoc pLoc = sm.getPresumedLoc(srcLoc);
+    StringRef filename = pLoc.getFilename();
+    return mlir::FileLineColLoc::get(builder.getStringAttr(filename),
+                                     pLoc.getLine(), pLoc.getColumn());
+  } else {
+    // Do our best...
+    assert(currSrcLoc && "expected to inherit some source location");
+    return *currSrcLoc;
+  }
+}
+
+mlir::Location CIRGenFunction::getLoc(SourceRange srcLoc) {
+  // Some AST nodes might contain invalid source locations (e.g.
+  // CXXDefaultArgExpr), workaround that to still get something out.
+  if (srcLoc.isValid()) {
+    mlir::Location beg = getLoc(srcLoc.getBegin());
+    mlir::Location end = getLoc(srcLoc.getEnd());
+    SmallVector<mlir::Location, 2> locs = {beg, end};
+    mlir::Attribute metadata;
+    return mlir::FusedLoc::get(locs, metadata, &getMLIRContext());
+  } else if (currSrcLoc) {
+    return *currSrcLoc;
+  }
+
+  // We're brave, but time to give up.
+  return builder.getUnknownLoc();
+}
+
+mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) {
+  SmallVector<mlir::Location, 2> locs = {lhs, rhs};
+  mlir::Attribute metadata;
+  return mlir::FusedLoc::get(locs, metadata, &getMLIRContext());
+}
+
+void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
+                                   cir::FuncOp fn, cir::FuncType funcType,
+                                   SourceLocation loc,
+                                   SourceLocation startLoc) {
+  assert(!curFn &&
+         "CIRGenFunction can only be used for one function at a time");
+
+  fnRetTy = returnType;
+  curFn = fn;
+
+  mlir::Block *entryBB = &fn.getBlocks().front();
+  builder.setInsertionPointToStart(entryBB);
+}
+
+void CIRGenFunction::finishFunction(SourceLocation endLoc) {}
+
+mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) {
+  auto result = mlir::LogicalResult::success();
+  if (const CompoundStmt *block = dyn_cast<CompoundStmt>(body))
+    emitCompoundStmtWithoutScope(*block);
+  else
+    result = emitStmt(body, /*useCurrentScope=*/true);
+  return result;
+}
+
+cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
+                                         cir::FuncType funcType) {
+  const auto funcDecl = cast<FunctionDecl>(gd.getDecl());
+  SourceLocation loc = funcDecl->getLocation();
+  Stmt *body = funcDecl->getBody();
+  SourceRange bodyRange =
+      body ? body->getSourceRange() : funcDecl->getLocation();
+
+  SourceLocRAIIObject fnLoc{*this, loc.isValid() ? getLoc(loc)
+                                                 : builder.getUnknownLoc()};
+
+  mlir::Block *entryBB = fn.addEntryBlock();
+  builder.setInsertionPointToStart(entryBB);
+
+  startFunction(gd, funcDecl->getReturnType(), fn, funcType, loc,
+                bodyRange.getBegin());
+  if (mlir::failed(emitFunctionBody(body))) {
+    fn.erase();
+    return nullptr;
+  }
+
+  // This code to insert a cir.return or cir.trap at the end of the function is
+  // temporary until the function return code, including
+  // CIRGenFunction::LexicalScope::emitImplicitReturn(), is upstreamed.
+  mlir::Block &lastBlock = fn.getRegion().back();
+  if (lastBlock.empty() || !lastBlock.mightHaveTerminator() ||
+      !lastBlock.getTerminator()->hasTrait<mlir::OpTrait::IsTerminator>()) {
+    builder.setInsertionPointToEnd(&lastBlock);
+    if (mlir::isa<cir::VoidType>(funcType.getReturnType())) {
+      builder.create<cir::ReturnOp>(getLoc(bodyRange.getEnd()));
+    } else {
+      builder.create<cir::TrapOp>(getLoc(bodyRange.getEnd()));
+    }
+  }
+
+  if (mlir::failed(fn.verifyBody()))
+    return nullptr;
+
+  finishFunction(bodyRange.getEnd());
+
+  return fn;
+}
+
+} // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
new file mode 100644
index 0000000000000..3adab9edd6121
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -0,0 +1,134 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Internal per-function state used for AST-to-ClangIR code gen
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_CIR_CODEGEN_CIRGENFUNCTION_H
+#define LLVM_CLANG_LIB_CIR_CODEGEN_CIRGENFUNCTION_H
+
+#include "CIRGenBuilder.h"
+#include "CIRGenModule.h"
+#include "CIRGenTypeCache.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/TypeEvaluationKind.h"
+
+#include "llvm/ADT/ScopedHashTable.h"
+
+namespace clang::CIRGen {
+
+class CIRGenFunction : public CIRGenTypeCache {
+public:
+  CIRGenModule &cgm;
+
+private:
+  /// The builder is a helper class to create IR inside a function. The
+  /// builder is stateful, in particular it keeps an "insertion point": this
+  /// is where the next operations will be introduced.
+  CIRGenBuilderTy &builder;
+
+public:
+  clang::QualType fnRetTy;
+
+  /// This is the current function or global initializer that is generated code
+  /// for.
+  mlir::Operation *curFn = nullptr;
+
+  clang::ASTContext &getContext() const { return cgm.getASTContext(); }
+
+  CIRGenBuilderTy &getBuilder() { return builder; }
+
+  CIRGenModule &getCIRGenModule() { return cgm; }
+  const CIRGenModule &getCIRGenModule() const { return cgm; }
+
+  mlir::Type convertTypeForMem(QualType T);
+
+  mlir::Type convertType(clang::QualType T);
+  mlir::Type convertType(const TypeDecl *T) {
+    return convertType(getContext().getTypeDeclType(T));
+  }
+
+  ///  Return the cir::TypeEvaluationKind of QualType \c T.
+  static cir::TypeEvaluationKind getEvaluationKind(clang::QualType T);
+
+  static bool hasScalarEvaluationKind(clang::QualType T) {
+    return getEvaluationKind(T) == cir::TEK_Scalar;
+  }
+
+  CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder,
+                 bool suppressNewContext = false);
+  ~CIRGenFunction();
+
+  CIRGenTypes &getTypes() const { return cgm.getTypes(); }
+
+  mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); }
+
+  /// Use to track source locations across nested visitor traversals.
+  /// Always use a `SourceLocRAIIObject` to change currSrcLoc.
+  std::optional<mlir::Location> currSrcLoc;
+  class SourceLocRAIIObject {
+    CIRGenFunction &cgf;
+    std::optional<mlir::Location> oldLoc;
+
+  public:
+    SourceLocRAIIObject(CIRGenFunction &cgf, mlir::Location value) : cgf(cgf) {
+      if (cgf.currSrcLoc)
+        oldLoc = cgf.currSrcLoc;
+      cgf.currSrcLoc = value;
+    }
+
+    /// Can be used to restore the state early, before the dtor
+    /// is run.
+    void restore() { cgf.currSrcLoc = oldLoc; }
+    ~SourceLocRAIIObject() { restore(); }
+  };
+
+  /// Helpers to convert Clang's SourceLocation to a MLIR Location.
+  mlir::Location getLoc(clang::SourceLocation SLoc);
+  mlir::Location getLoc(clang::SourceRange SLoc);
+  mlir::Location getLoc(mlir::Location lhs, mlir::Location rhs);
+
+  void finishFunction(SourceLocation EndLoc);
+  mlir::LogicalResult emitFunctionBody(const clang::Stmt *Body);
+
+  // Build CIR for a statement. useCurrentScope should be true if no
+  // new scopes need be created when finding a compound statement.
+  mlir::LogicalResult
+  emitStmt(const clang::Stmt *s, bool useCurrentScope,
+           llvm::ArrayRef<const Attr *> attrs = std::nullopt);
+
+  mlir::LogicalResult emitSimpleStmt(const clang::Stmt *s,
+                                     bool useCurrentScope);
+
+  void emitCompoundStmt(const clang::CompoundStmt &s);
+
+  void emitCompoundStmtWithoutScope(const clang::CompoundStmt &s);
+
+  mlir::LogicalResult emitReturnStmt(const clang::ReturnStmt &s);
+
+  /// Emit the computation of the specified expression of scalar type.
+  mlir::Value emitScalarExpr(const clang::Expr *e);
+  cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
+                           cir::FuncType funcType);
+
+  /// Emit code for the start of a function.
+  /// \param Loc       The location to be associated with the function.
+  /// \param StartLoc  The location of the function body.
+  void startFunction(clang::GlobalDecl gd, clang::QualType retTy,
+                     cir::FuncOp fn, cir::FuncType funcType,
+                     clang::SourceLocation loc, clang::SourceLocation startLoc);
+};
+
+} // namespace clang::CIRGen
+
+#endif
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 2615ae382cb8b..1dc58dfe8d204 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CIRGenModule.h"
+#include "CIRGenFunction.h"
 
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/DeclBase.h"
@@ -99,19 +100,30 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
 void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
                                                 mlir::Operation *op) {
   auto const *funcDecl = cast<FunctionDecl>(gd.getDecl());
-  if (clang::IdentifierInfo *identifier = funcDecl->getIdentifier()) {
-    auto funcOp = builder.create<cir::FuncOp>(
-        getLoc(funcDecl->getSourceRange()), identifier->getName());
-    theModule.push_back(funcOp);
-  } else {
+  if (funcDecl->getIdentifier() == nullptr) {
     errorNYI(funcDecl->getSourceRange().getBegin(),
              "function definition with a non-identifier for a name");
+    return;
+  }
+  cir::FuncType funcType =
+      cast<cir::FuncType>(convertType(funcDecl->getType()));
+
+  cir::FuncOp funcOp = dyn_cast_if_present<cir::FuncOp>(op);
+  if (!funcOp || funcOp.getFunctionType() != funcType) {
+    funcOp = getAddrOfFunction(gd, funcType, /*ForVTable=*/false,
+                               /*DontDefer=*/true, ForDefinition);
+  }
+
+  CIRGenFunction cgf(*this, builder);
+  {
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    cgf.generateCode(gd, funcOp, funcType);
   }
 }
 
 void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *vd,
                                            bool isTentative) {
-  mlir::Type type = getTypes().convertType(vd->getType());
+  mlir::Type type = convertType(vd->getType());
   if (clang::IdentifierInfo *identifier = vd->getIdentifier()) {
     auto varOp = builder.create<cir::GlobalOp>(getLoc(vd->getSourceRange()),
                                                identifier->getName(), type);
@@ -220,6 +232,56 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
   }
 }
 
+cir::FuncOp CIRGenModule::getAddrOfFunction(clang::GlobalDecl gd,
+                                            mlir::Type funcType, bool forVTable,
+                                            bool dontDefer,
+                                            ForDefinition_t isForDefinition) {
+  assert(!cast<FunctionDecl>(gd.getDecl())->isConsteval() &&
+         "consteval function should never be emitted");
+
+  if (!funcType) {
+    const auto *fd = cast<FunctionDecl>(gd.getDecl());
+    funcType = convertType(fd->getType());
+  }
+
+  cir::FuncOp func = getOrCreateCIRFunction(
+      cast<NamedDecl>(gd.getDecl())->getIdentifier()->getName(), funcType, gd,
+      forVTable, dontDefer, /*isThunk=*/false, isForDefinition);
+  return func;
+}
+
+cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
+    StringRef mangledName, mlir::Type funcType, GlobalDecl gd, bool forVTable,
+    bool dontDefer, bool isThunk, ForDefinition_t isForDefinition,
+    mlir::ArrayAttr extraAttrs) {
+  auto *funcDecl = llvm::cast_or_null<FunctionDecl>(gd.getDecl());
+  bool invalidLoc = !funcDecl ||
+                    funcDecl->getSourceRange().getBegin().isInvalid() ||
+                    funcDecl->getSourceRange().getEnd().isInvalid();
+  cir::FuncOp funcOp = createCIRFunction(
+      invalidLoc ? theModule->getLoc() : getLoc(funcDecl->getSourceRange()),
+      mangledName, mlir::cast<cir::FuncType>(funcType), funcDecl);
+  return funcOp;
+}
+
+cir::FuncOp
+CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
+                                cir::FuncType funcType,
+                                const clang::FunctionDecl *funcDecl) {
+  cir::FuncOp func;
+  {
+    mlir::OpBuilder::InsertionGuard guard(builder);
+
+    func = builder.create<cir::FuncOp>(loc, name, funcType);
+    theModule.push_back(func);
+  }
+  return func;
+}
+
+mlir::Type CIRGenModule::convertType(QualType type) {
+  return genTypes.convertType(type);
+}
+
 DiagnosticBuilder CIRGenModule::errorNYI(SourceLocation loc,
                                          llvm::StringRef feature) {
   unsigned diagID = diags.getCustomDiagID(
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 1c7ed63773900..63b7cc91a619f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -17,6 +17,8 @@
 #include "CIRGenTypeCache.h"
 #include "CIRGenTypes.h"
 
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/MLIRContext.h"
@@ -34,6 +36,8 @@ class VarDecl;
 
 namespace CIRGen {
 
+enum ForDefinition_t : bool { NotForDefinition = false, ForDefinition = true };
+
 /// This class organizes the cross-function state that is used while generating
 /// CIR code.
 class CIRGenModule : public CIRGenTypeCache {
@@ -78,16 +82,37 @@ class CIRGenModule : public CIRGenTypeCache {
 
   void emitTopLevelDecl(clang::Decl *decl);
 
+  /// Return the address of the given function. If funcType is non-null, then
+  /// this function will use the specified type if it has to create it.
+  // TODO: this is a bit weird as `GetAddr` given we give back a FuncOp?
+  cir::FuncOp
+  getAddrOfFunction(clang::GlobalDecl gd, mlir::Type funcType = nullptr,
+                    bool forVTable = false, bool dontDefer = false,
+                    ForDefinition_t isForDefinition = NotForDefinition);
+
   /// Emit code for a single global function or variable declaration. Forward
   /// declarations are emitted lazily.
   void emitGlobal(clang::GlobalDecl gd);
 
+  mlir::Type convertType(clang::QualType type);
+
   void emitGlobalDefinition(clang::GlobalDecl gd,
                             mlir::Operation *op = nullptr);
   void emitGlobalFunctionDefinition(clang::GlobalDecl gd, mlir::Operation *op);
   void emitGlobalVarDefinition(const clang::VarDecl *vd,
                                bool isTentative = false);
 
+  cir::FuncOp
+  getOrCreateCIRFunction(llvm::StringRef mangledName, mlir::Type funcType,
+                         clang::GlobalDecl gd, bool forVTable,
+                         bool dontDefer = false, bool isThunk = false,
+                         ForDefinition_t isForDefinition = NotForDefinition,
+                         mlir::ArrayAttr extraAttrs = {});
+
+  cir::FuncOp createCIRFunction(mlir::Location loc, llvm::StringRef name,
+                                cir::FuncType funcType,
+                                const clang::FunctionDecl *funcDecl);
+
   /// Helpers to emit "not yet implemented" error diagnostics
   DiagnosticBuilder errorNYI(SourceLocation, llvm::StringRef);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
new file mode 100644
index 0000000000000..f42f30cc5a433
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -0,0 +1,128 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Emit Stmt nodes as CIR code.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenBuilder.h"
+#include "CIRGenFunction.h"
+
+#include "mlir/IR/Builders.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Stmt.h"
+
+using namespace clang;
+using namespace clang::CIRGen;
+using namespace cir;
+
+void CIRGenFunction::emitCompoundStmtWithoutScope(const CompoundStmt &s) {
+  for (auto *curStmt : s.body()) {
+    if (emitStmt(curStmt, /*useCurrentScope=*/false).failed())
+      getCIRGenModule().errorNYI(curStmt->getSourceRange(), "statement");
+  }
+}
+
+void CIRGenFunction::emitCompoundStmt(const CompoundStmt &s) {
+  mlir::Location scopeLoc = getLoc(s.getSourceRange());
+  auto scope = builder.create<cir::ScopeOp>(
+      scopeLoc, [&](mlir::OpBuilder &b, mlir::Type &type, mlir::Location loc) {
+        emitCompoundStmtWithoutScope(s);
+      });
+
+  // This code to insert a cir.yield at the end of the scope is temporary until
+  // CIRGenFunction::LexicalScope::cleanup() is upstreamed.
+  if (!scope.getRegion().empty()) {
+    mlir::Block &lastBlock = scope.getRegion().back();
+    if (lastBlock.empty() || !lastBlock.mightHaveTerminator() ||
+        !lastBlock.getTerminator()->hasTrait<mlir::OpTrait::IsTerminator>()) {
+      builder.setInsertionPointToEnd(&lastBlock);
+      builder.create<cir::YieldOp>(getLoc(s.getEndLoc()));
+    }
+  }
+}
+
+// Build CIR for a statement. useCurrentScope should be true if no new scopes
+// need to be created when finding a compound statement.
+mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
+                                             bool useCurrentScope,
+                                             ArrayRef<const Attr *> attr) {
+  if (mlir::succeeded(emitSimpleStmt(s, useCurrentScope)))
+    return mlir::success();
+
+  // Only a subset of simple statements are supported at the moment.  When more
+  // kinds of statements are supported, a
+  //     switch (s->getStmtClass()) {
+  // will be added here.
+  return mlir::failure();
+}
+
+mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
+                                                   bool useCurrentScope) {
+  switch (s->getStmtClass()) {
+  default:
+    // Only compound and return statements are supported right now.
+    return mlir::failure();
+  case Stmt::CompoundStmtClass:
+    if (useCurrentScope)
+      emitCompoundStmtWithoutScope(cast<CompoundStmt>(*s));
+    else
+      emitCompoundStmt(cast<CompoundStmt>(*s));
+    break;
+  case Stmt::ReturnStmtClass:
+    return emitReturnStmt(cast<ReturnStmt>(*s));
+  }
+
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
+  mlir::Location loc = getLoc(s.getSourceRange());
+  const Expr *rv = s.getRetValue();
+
+  if (getContext().getLangOpts().ElideConstructors && s.getNRVOCandidate() &&
+      s.getNRVOCandidate()->isNRVOVariable()) {
+    getCIRGenModule().errorNYI(s.getSourceRange(),
+                               "named return value optimization");
+  } else if (!rv) {
+    // No return expression. Do nothing.
+    // TODO(CIR): In the future when function returns are fully implemented,
+    // this section will do nothing.  But for now a ReturnOp is necessary.
+    builder.create<ReturnOp>(loc);
+  } else if (rv->getType()->isVoidType()) {
+    // No return value. Emit the return expression for its side effects.
+    // TODO(CIR): Once emitAnyExpr(e) has been upstreamed, get rid of the check
+    // and just call emitAnyExpr(rv) here.
+    if (CIRGenFunction::hasScalarEvaluationKind(rv->getType())) {
+      emitScalarExpr(rv);
+    } else {
+      getCIRGenModule().errorNYI(s.getSourceRange(),
+                                 "non-scalar function return type");
+    }
+    builder.create<ReturnOp>(loc);
+  } else if (fnRetTy->isReferenceType()) {
+    getCIRGenModule().errorNYI(s.getSourceRange(),
+                               "function return type that is a reference");
+  } else {
+    mlir::Value value = nullptr;
+    switch (CIRGenFunction::getEvaluationKind(rv->getType())) {
+    case cir::TEK_Scalar:
+      value = emitScalarExpr(rv);
+      if (value) { // Change this to an assert once emitScalarExpr is complete
+        builder.create<ReturnOp>(loc, llvm::ArrayRef(value));
+      }
+      break;
+    default:
+      getCIRGenModule().errorNYI(s.getSourceRange(),
+                                 "non-scalar function return type");
+      break;
+    }
+  }
+
+  return mlir::success();
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index 8519854556b1c..551b43ef121b3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -3,6 +3,7 @@
 #include "CIRGenModule.h"
 
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/GlobalDecl.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/TargetInfo.h"
 
@@ -56,7 +57,7 @@ bool CIRGenTypes::isFuncTypeConvertible(const FunctionType *ft) {
   return true;
 }
 
-mlir::Type CIRGenTypes::ConvertFunctionTypeInternal(QualType qft) {
+mlir::Type CIRGenTypes::convertFunctionTypeInternal(QualType qft) {
   assert(qft.isCanonical());
   const FunctionType *ft = cast<FunctionType>(qft.getTypePtr());
   // First, check whether we can build the full fucntion type. If the function
@@ -198,7 +199,7 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
 
   case Type::FunctionNoProto:
   case Type::FunctionProto:
-    resultType = ConvertFunctionTypeInternal(type);
+    resultType = convertFunctionTypeInternal(type);
     break;
 
   case Type::BitInt: {
@@ -224,3 +225,19 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
   typeCache[ty] = resultType;
   return resultType;
 }
+
+mlir::Type CIRGenTypes::convertTypeForMem(clang::QualType qualType,
+                                          bool forBitField) {
+  assert(!qualType->isConstantMatrixType() && "Matrix types NYI");
+
+  mlir::Type convertedType = convertType(qualType);
+
+  assert(!forBitField && "Bit fields NYI");
+
+  // If this is a bit-precise integer type in a bitfield representation, map
+  // this integer to the target-specified size.
+  if (forBitField && qualType->isBitIntType())
+    assert(!qualType->isBitIntType() && "Bit field with type _BitInt NYI");
+
+  return convertedType;
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index 71427e1200027..f280e17ebddc6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -15,11 +15,14 @@
 
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
+#include "clang/AST/Type.h"
+
 #include "llvm/ADT/SmallPtrSet.h"
 
 namespace clang {
 class ASTContext;
 class FunctionType;
+class GlobalDecl;
 class QualType;
 class Type;
 } // namespace clang
@@ -40,8 +43,8 @@ class CIRGenTypes {
   clang::ASTContext &astContext;
   CIRGenBuilderTy &builder;
 
-  /// Heper for ConvertType.
-  mlir::Type ConvertFunctionTypeInternal(clang::QualType ft);
+  /// Heper for convertType.
+  mlir::Type convertFunctionTypeInternal(clang::QualType ft);
 
 public:
   CIRGenTypes(CIRGenModule &cgm);
@@ -61,6 +64,13 @@ class CIRGenTypes {
 
   /// Convert a Clang type into a mlir::Type.
   mlir::Type convertType(clang::QualType type);
+
+  /// Convert type T into an mlir::Type. This differs from convertType in that
+  /// it is used to convert to the memory representation for a type. For
+  /// example, the scalar representation for bool is i1, but the memory
+  /// representation is usually i8 or i32, depending on the target.
+  // TODO: convert this comment to account for MLIR's equivalence
+  mlir::Type convertTypeForMem(clang::QualType, bool forBitField = false);
 };
 
 } // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index 782b814d75daa..5602efae1ba41 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -8,7 +8,10 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
 
 add_clang_library(clangCIR
   CIRGenerator.cpp
+  CIRGenExprScalar.cpp
+  CIRGenFunction.cpp
   CIRGenModule.cpp
+  CIRGenStmt.cpp
   CIRGenTypes.cpp
 
   DEPENDS
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index f98d8b60f6ff8..10ad7fb4e6542 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -14,6 +14,7 @@
 
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
+#include "mlir/Interfaces/FunctionImplementation.h"
 #include "mlir/Support/LogicalResult.h"
 
 #include "clang/CIR/Dialect/IR/CIROpsDialect.cpp.inc"
@@ -34,6 +35,70 @@ void cir::CIRDialect::initialize() {
       >();
 }
 
+//===----------------------------------------------------------------------===//
+// Helpers
+//===----------------------------------------------------------------------===//
+
+// Check if a region's termination omission is valid and, if so, creates and
+// inserts the omitted terminator into the region.
+LogicalResult ensureRegionTerm(OpAsmParser &parser, Region &region,
+                               SMLoc errLoc) {
+  Location eLoc = parser.getEncodedSourceLoc(parser.getCurrentLocation());
+  OpBuilder builder(parser.getBuilder().getContext());
+
+  // Insert empty block in case the region is empty to ensure the terminator
+  // will be inserted
+  if (region.empty())
+    builder.createBlock(&region);
+
+  Block &block = region.back();
+  // Region is properly terminated: nothing to do.
+  if (!block.empty() && block.back().hasTrait<OpTrait::IsTerminator>())
+    return success();
+
+  // Check for invalid terminator omissions.
+  if (!region.hasOneBlock())
+    return parser.emitError(errLoc,
+                            "multi-block region must not omit terminator");
+
+  // Terminator was omitted correctly: recreate it.
+  builder.setInsertionPointToEnd(&block);
+  builder.create<cir::YieldOp>(eLoc);
+  return success();
+}
+
+// True if the region's terminator should be omitted.
+bool omitRegionTerm(mlir::Region &r) {
+  const auto singleNonEmptyBlock = r.hasOneBlock() && !r.back().empty();
+  const auto yieldsNothing = [&r]() {
+    auto y = dyn_cast<cir::YieldOp>(r.back().getTerminator());
+    return y && y.getArgs().empty();
+  };
+  return singleNonEmptyBlock && yieldsNothing();
+}
+
+//===----------------------------------------------------------------------===//
+// CIR Custom Parsers/Printers
+//===----------------------------------------------------------------------===//
+
+static mlir::ParseResult parseOmittedTerminatorRegion(mlir::OpAsmParser &parser,
+                                                      mlir::Region &region) {
+  auto regionLoc = parser.getCurrentLocation();
+  if (parser.parseRegion(region))
+    return failure();
+  if (ensureRegionTerm(parser, region, regionLoc).failed())
+    return failure();
+  return success();
+}
+
+static void printOmittedTerminatorRegion(mlir::OpAsmPrinter &printer,
+                                         cir::ScopeOp &op,
+                                         mlir::Region &region) {
+  printer.printRegion(region,
+                      /*printEntryBlockArgs=*/false,
+                      /*printBlockTerminators=*/!omitRegionTerm(region));
+}
+
 //===----------------------------------------------------------------------===//
 // ConstantOp
 //===----------------------------------------------------------------------===//
@@ -73,6 +138,92 @@ OpFoldResult cir::ConstantOp::fold(FoldAdaptor /*adaptor*/) {
   return getValue();
 }
 
+//===----------------------------------------------------------------------===//
+// ReturnOp
+//===----------------------------------------------------------------------===//
+
+static mlir::LogicalResult checkReturnAndFunction(cir::ReturnOp op,
+                                                  cir::FuncOp function) {
+  // ReturnOps currently only have a single optional operand.
+  if (op.getNumOperands() > 1)
+    return op.emitOpError() << "expects at most 1 return operand";
+
+  // Ensure returned type matches the function signature.
+  auto expectedTy = function.getFunctionType().getReturnType();
+  auto actualTy =
+      (op.getNumOperands() == 0 ? cir::VoidType::get(op.getContext())
+                                : op.getOperand(0).getType());
+  if (actualTy != expectedTy)
+    return op.emitOpError() << "returns " << actualTy
+                            << " but enclosing function returns " << expectedTy;
+
+  return mlir::success();
+}
+
+mlir::LogicalResult cir::ReturnOp::verify() {
+  // Returns can be present in multiple different scopes, get the
+  // wrapping function and start from there.
+  auto *fnOp = getOperation()->getParentOp();
+  while (!isa<cir::FuncOp>(fnOp))
+    fnOp = fnOp->getParentOp();
+
+  // Make sure return types match function return type.
+  if (checkReturnAndFunction(*this, cast<cir::FuncOp>(fnOp)).failed())
+    return failure();
+
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// ScopeOp
+//===----------------------------------------------------------------------===//
+
+/// Given the region at `index`, or the parent operation if `index` is None,
+/// return the successor regions. These are the regions that may be selected
+/// during the flow of control. `operands` is a set of optional attributes that
+/// correspond to a constant value for each operand, or null if that operand is
+/// not a constant.
+void cir::ScopeOp::getSuccessorRegions(
+    mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> &regions) {
+  // The only region always branch back to the parent operation.
+  if (!point.isParent()) {
+    regions.push_back(RegionSuccessor(getODSResults(0)));
+    return;
+  }
+
+  // If the condition isn't constant, both regions may be executed.
+  regions.push_back(RegionSuccessor(&getScopeRegion()));
+}
+
+void cir::ScopeOp::build(
+    OpBuilder &builder, OperationState &result,
+    function_ref<void(OpBuilder &, Type &, Location)> scopeBuilder) {
+  assert(scopeBuilder && "the builder callback for 'then' must be present");
+
+  OpBuilder::InsertionGuard guard(builder);
+  Region *scopeRegion = result.addRegion();
+  builder.createBlock(scopeRegion);
+
+  mlir::Type yieldTy;
+  scopeBuilder(builder, yieldTy, result.location);
+
+  if (yieldTy)
+    result.addTypes(TypeRange{yieldTy});
+}
+
+LogicalResult cir::ScopeOp::verify() {
+  if (getRegion().empty()) {
+    return emitOpError() << "cir.scope must not be empty since it should "
+                            "include at least an implicit cir.yield ";
+  }
+
+  mlir::Block &lastBlock = getRegion().back();
+  if (lastBlock.empty() || !lastBlock.mightHaveTerminator() ||
+      !lastBlock.getTerminator()->hasTrait<OpTrait::IsTerminator>())
+    return emitOpError() << "last block of cir.scope must be terminated";
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // GlobalOp
 //===----------------------------------------------------------------------===//
@@ -154,23 +305,101 @@ parseGlobalOpTypeAndInitialValue(OpAsmParser &parser, TypeAttr &typeAttr,
 //===----------------------------------------------------------------------===//
 
 void cir::FuncOp::build(OpBuilder &builder, OperationState &result,
-                        StringRef name) {
+                        StringRef name, FuncType type) {
+  result.addRegion();
   result.addAttribute(SymbolTable::getSymbolAttrName(),
                       builder.getStringAttr(name));
+  result.addAttribute(getFunctionTypeAttrName(result.name),
+                      TypeAttr::get(type));
 }
 
 ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) {
+  llvm::SMLoc loc = parser.getCurrentLocation();
+  mlir::Builder &builder = parser.getBuilder();
+
   StringAttr nameAttr;
   if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(),
                              state.attributes))
     return failure();
+  llvm::SmallVector<OpAsmParser::Argument, 8> arguments;
+  llvm::SmallVector<mlir::Type> resultTypes;
+  llvm::SmallVector<DictionaryAttr> resultAttrs;
+  bool isVariadic = false;
+  if (function_interface_impl::parseFunctionSignatureWithArguments(
+          parser, /*allowVariadic=*/true, arguments, isVariadic, resultTypes,
+          resultAttrs))
+    return failure();
+  llvm::SmallVector<mlir::Type> argTypes;
+  for (OpAsmParser::Argument &arg : arguments)
+    argTypes.push_back(arg.type);
+
+  if (resultTypes.size() > 1) {
+    return parser.emitError(
+        loc, "functions with multiple return types are not supported");
+  }
+
+  mlir::Type returnType =
+      (resultTypes.empty() ? cir::VoidType::get(builder.getContext())
+                           : resultTypes.front());
+
+  cir::FuncType fnType = cir::FuncType::get(argTypes, returnType, isVariadic);
+  if (!fnType)
+    return failure();
+  state.addAttribute(getFunctionTypeAttrName(state.name),
+                     TypeAttr::get(fnType));
+
+  // Parse the optional function body.
+  auto *body = state.addRegion();
+  OptionalParseResult parseResult = parser.parseOptionalRegion(
+      *body, arguments, /*enableNameShadowing=*/false);
+  if (parseResult.has_value()) {
+    if (failed(*parseResult))
+      return failure();
+    // Function body was parsed, make sure its not empty.
+    if (body->empty())
+      return parser.emitError(loc, "expected non-empty function body");
+  }
+
   return success();
 }
 
+bool cir::FuncOp::isDeclaration() {
+  // TODO(CIR): This function will actually do something once external function
+  // declarations and aliases are upstreamed.
+  return false;
+}
+
+mlir::Region *cir::FuncOp::getCallableRegion() {
+  // TODO(CIR): This function will have special handling for aliases and a
+  // check for an external function, once those features have been upstreamed.
+  return &getBody();
+}
+
 void cir::FuncOp::print(OpAsmPrinter &p) {
   p << ' ';
-  // For now the only property a function has is its name
   p.printSymbolName(getSymName());
+  cir::FuncType fnType = getFunctionType();
+  function_interface_impl::printFunctionSignature(
+      p, *this, fnType.getInputs(), fnType.isVarArg(), fnType.getReturnTypes());
+
+  // Print the body if this is not an external function.
+  Region &body = getOperation()->getRegion(0);
+  if (!body.empty()) {
+    p << ' ';
+    p.printRegion(body, /*printEntryBlockArgs=*/false,
+                  /*printBlockTerminators=*/true);
+  }
+}
+
+// Hook for OpTrait::FunctionLike, called after verifying that the 'type'
+// attribute is present.  This can check for preconditions of the
+// getNumArguments hook not failing.
+LogicalResult cir::FuncOp::verifyType() {
+  auto type = getFunctionType();
+  if (!isa<cir::FuncType>(type))
+    return emitOpError("requires '" + getFunctionTypeAttrName().str() +
+                       "' attribute of function type");
+  return success();
 }
 
 // TODO(CIR): The properties of functions that require verification haven't
diff --git a/clang/test/CIR/func-simple.cpp b/clang/test/CIR/func-simple.cpp
new file mode 100644
index 0000000000000..10c49bc506c87
--- /dev/null
+++ b/clang/test/CIR/func-simple.cpp
@@ -0,0 +1,53 @@
+// Simple functions
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o -  | FileCheck %s
+
+void empty() { }
+// CHECK: cir.func @empty() -> !cir.void {
+// CHECK:   cir.return
+// CHECK: }
+
+void voidret() { return; }
+// CHECK: cir.func @voidret() -> !cir.void {
+// CHECK:   cir.return
+// CHECK: }
+
+int intfunc() { return 42; }
+// CHECK: cir.func @intfunc() -> !cir.int<s, 32> {
+// CHECK:   %0 = cir.const #cir.int<42> : !cir.int<s, 32>
+// CHECK:   cir.return %0 : !cir.int<s, 32>
+// CHECK: }
+
+int scopes() {
+  {
+    {
+      return 99;
+    }
+  }
+}
+// CHECK: cir.func @scopes() -> !cir.int<s, 32> {
+// CHECK:   cir.scope {
+// CHECK:     cir.scope {
+// CHECK:       %0 = cir.const #cir.int<99> : !cir.int<s, 32>
+// CHECK:       cir.return %0 : !cir.int<s, 32>
+// CHECK:     }
+// CHECK:   }
+// CHECK:   cir.trap
+// CHECK: }
+
+long longfunc() { return 42l; }
+// CHECK: cir.func @longfunc() -> !cir.int<s, 64>
+// CHECK:   %0 = cir.const #cir.int<42> : !cir.int<s, 64>
+// CHECK:   cir.return %0 : !cir.int<s, 64>
+// CHECK: }
+
+unsigned unsignedfunc() { return 42u; }
+// CHECK: cir.func @unsignedfunc() -> !cir.int<u, 32>
+// CHECK:   %0 = cir.const #cir.int<42> : !cir.int<u, 32>
+// CHECK:   cir.return %0 : !cir.int<u, 32>
+// CHECK: }
+
+unsigned long long ullfunc() { return 42ull; }
+// CHECK: cir.func @ullfunc() -> !cir.int<u, 64>
+// CHECK:   %0 = cir.const #cir.int<42> : !cir.int<u, 64>
+// CHECK:   cir.return %0 : !cir.int<u, 64>
+// CHECK: }
diff --git a/clang/test/CIR/global-var-simple.cpp b/clang/test/CIR/global-var-simple.cpp
index ffcc3ef71a6c7..237070a5b7564 100644
--- a/clang/test/CIR/global-var-simple.cpp
+++ b/clang/test/CIR/global-var-simple.cpp
@@ -1,4 +1,4 @@
-// Global variables of intergal types
+// Global variables of scalar typees with initial values
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o -  | FileCheck %s
 
 char c;
diff --git a/clang/test/CIR/hello.c b/clang/test/CIR/hello.c
deleted file mode 100644
index 4b07c04994aa8..0000000000000
--- a/clang/test/CIR/hello.c
+++ /dev/null
@@ -1,5 +0,0 @@
-// Smoke test for ClangIR code generation
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o -  | FileCheck %s
-
-void foo() {}
-// CHECK: cir.func @foo



More information about the cfe-commits mailing list