diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index d6e11e4516e2..b6e3beced986 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -411,7 +411,7 @@ void CIRGenFunction::buildVarDecl(const VarDecl &D) {
   }
 
   if (D.getType().getAddressSpace() == LangAS::opencl_local)
-    llvm_unreachable("OpenCL and address space are NYI");
+    return CGM.getOpenCLRuntime().buildWorkGroupLocalVarDecl(*this, D);
 
   assert(D.hasLocalStorage());
 
@@ -465,19 +465,19 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &D,
     Name = getStaticDeclName(*this, D);
 
   mlir::Type LTy = getTypes().convertTypeForMem(Ty);
-  assert(!MissingFeatures::addressSpace());
+  mlir::cir::AddressSpaceAttr AS =
+      builder.getAddrSpaceAttr(getGlobalVarAddressSpace(&D));
 
   // OpenCL variables in local address space and CUDA shared
   // variables cannot have an initializer.
   mlir::Attribute Init = nullptr;
-  if (Ty.getAddressSpace() == LangAS::opencl_local ||
-      D.hasAttr<CUDASharedAttr>() || D.hasAttr<LoaderUninitializedAttr>())
-    llvm_unreachable("OpenCL & CUDA are NYI");
-  else
+  if (D.hasAttr<CUDASharedAttr>() || D.hasAttr<LoaderUninitializedAttr>())
+    llvm_unreachable("CUDA is NYI");
+  else if (Ty.getAddressSpace() != LangAS::opencl_local)
     Init = builder.getZeroInitAttr(getTypes().ConvertType(Ty));
 
   mlir::cir::GlobalOp GV = builder.createVersionedGlobal(
-      getModule(), getLoc(D.getLocation()), Name, LTy, false, Linkage);
+      getModule(), getLoc(D.getLocation()), Name, LTy, false, Linkage, AS);
   // TODO(cir): infer visibility from linkage in global op builder.
   GV.setVisibility(getMLIRVisibilityFromCIRLinkage(Linkage));
   GV.setInitialValueAttr(Init);
@@ -492,7 +492,8 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &D,
   setGVProperties(GV, &D);
 
   // Make sure the result is of the correct type.
-  assert(!MissingFeatures::addressSpace());
+  if (AS != builder.getAddrSpaceAttr(Ty.getAddressSpace()))
+    llvm_unreachable("address space cast NYI");
 
   // Ensure that the static local gets initialized by making sure the parent
   // function gets emitted eventually.
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 5b3ca33cd24a..34a3a058d713 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -165,6 +165,10 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &context,
       builder.getContext(), astCtx.getTargetInfo().getMaxPointerWidth(),
       /*isSigned=*/true);
 
+  if (langOpts.OpenCL) {
+    createOpenCLRuntime();
+  }
+
   mlir::cir::sob::SignedOverflowBehavior sob;
   switch (langOpts.getSignedOverflowBehavior()) {
   case clang::LangOptions::SignedOverflowBehaviorTy::SOB_Defined:
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index b8ad57d788ae..b4d2f9dcbd00 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -15,6 +15,7 @@
 
 #include "CIRGenBuilder.h"
 #include "CIRGenCall.h"
+#include "CIRGenOpenCLRuntime.h"
 #include "CIRGenTypeCache.h"
 #include "CIRGenTypes.h"
 #include "CIRGenVTables.h"
@@ -102,6 +103,9 @@ class CIRGenModule : public CIRGenTypeCache {
   /// Holds information about C++ vtables.
   CIRGenVTables VTables;
 
+  /// Holds the OpenCL runtime
+  std::unique_ptr<CIRGenOpenCLRuntime> openCLRuntime;
+
   /// Holds the OpenMP runtime
   std::unique_ptr<CIRGenOpenMPRuntime> openMPRuntime;
 
@@ -697,6 +701,16 @@ class CIRGenModule : public CIRGenTypeCache {
   /// Print out an error that codegen doesn't support the specified decl yet.
   void ErrorUnsupported(const Decl *D, const char *Type);
 
+  /// Return a reference to the configured OpenMP runtime.
+  CIRGenOpenCLRuntime &getOpenCLRuntime() {
+    assert(openCLRuntime != nullptr);
+    return *openCLRuntime;
+  }
+
+  void createOpenCLRuntime() {
+    openCLRuntime.reset(new CIRGenOpenCLRuntime(*this));
+  }
+
   /// Return a reference to the configured OpenMP runtime.
   CIRGenOpenMPRuntime &getOpenMPRuntime() {
     assert(openMPRuntime != nullptr);
diff --git a/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.cpp b/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.cpp
new file mode 100644
index 000000000000..863caf8629d2
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.cpp
@@ -0,0 +1,29 @@
+//===-- CIRGenOpenCLRuntime.cpp - Interface to OpenCL Runtimes ------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This provides an abstract class for OpenCL CIR generation. Concrete
+// subclasses of this implement code generation for specific OpenCL
+// runtime libraries.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenOpenCLRuntime.h"
+#include "CIRGenFunction.h"
+
+#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
+
+using namespace clang;
+using namespace cir;
+
+CIRGenOpenCLRuntime::~CIRGenOpenCLRuntime() {}
+
+void CIRGenOpenCLRuntime::buildWorkGroupLocalVarDecl(CIRGenFunction &CGF,
+                                                     const VarDecl &D) {
+  return CGF.buildStaticVarDecl(D,
+                                mlir::cir::GlobalLinkageKind::InternalLinkage);
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.h b/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.h
new file mode 100644
index 000000000000..891b5bb5fb79
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenOpenCLRuntime.h
@@ -0,0 +1,46 @@
+//===-- CIRGenOpenCLRuntime.h - Interface to OpenCL Runtimes -----*- C++ -*-==//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This provides an abstract class for OpenCL CIR generation. Concrete
+// subclasses of this implement code generation for specific OpenCL
+// runtime libraries.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_CIR_CIRGENOPENCLRUNTIME_H
+#define LLVM_CLANG_LIB_CIR_CIRGENOPENCLRUNTIME_H
+
+namespace clang {
+
+class VarDecl;
+
+} // namespace clang
+
+namespace cir {
+
+class CIRGenFunction;
+class CIRGenModule;
+
+class CIRGenOpenCLRuntime {
+protected:
+  CIRGenModule &CGM;
+
+public:
+  CIRGenOpenCLRuntime(CIRGenModule &CGM) : CGM(CGM) {}
+  virtual ~CIRGenOpenCLRuntime();
+
+  /// Emit the IR required for a work-group-local variable declaration, and add
+  /// an entry to CGF's LocalDeclMap for D.  The base class does this using
+  /// CIRGenFunction::EmitStaticVarDecl to emit an internal global for D.
+  virtual void buildWorkGroupLocalVarDecl(CIRGenFunction &CGF,
+                                          const clang::VarDecl &D);
+};
+
+} // namespace cir
+
+#endif // LLVM_CLANG_LIB_CIR_CIRGENOPENCLRUNTIME_H
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index 552555779d46..97a8ad4f5ea8 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -31,6 +31,7 @@ add_clang_library(clangCIR
   CIRGenFunction.cpp
   CIRGenItaniumCXXABI.cpp
   CIRGenModule.cpp
+  CIRGenOpenCLRuntime.cpp
   CIRGenOpenCL.cpp
   CIRGenOpenMPRuntime.cpp
   CIRGenStmt.cpp
diff --git a/clang/test/CIR/CodeGen/OpenCL/static-vardecl.cl b/clang/test/CIR/CodeGen/OpenCL/static-vardecl.cl
new file mode 100644
index 000000000000..9ad8277012c4
--- /dev/null
+++ b/clang/test/CIR/CodeGen/OpenCL/static-vardecl.cl
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -cl-std=CL3.0 -O0 -fclangir -emit-cir -triple spirv64-unknown-unknown %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -cl-std=CL3.0 -O0 -fclangir -emit-llvm -triple spirv64-unknown-unknown %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
+
+kernel void test_static(int i) {
+  static global int b = 15;
+  // CIR-DAG: cir.global "private" internal dsolocal addrspace(offload_global) @test_static.b = #cir.int<15> : !s32i {alignment = 4 : i64}
+  // LLVM-DAG: @test_static.b = internal addrspace(1) global i32 15
+
+  local int c;
+  // CIR-DAG: cir.global "private" internal dsolocal addrspace(offload_local) @test_static.c : !s32i {alignment = 4 : i64}
+  // LLVM-DAG: @test_static.c = internal addrspace(3) global i32 undef
+
+  // CIR-DAG: %[[#ADDRB:]] = cir.get_global @test_static.b : !cir.ptr<!s32i, addrspace(offload_global)>
+  // CIR-DAG: %[[#ADDRC:]] = cir.get_global @test_static.c : !cir.ptr<!s32i, addrspace(offload_local)>
+
+  c = b;
+  // CIR:      %[[#LOADB:]] = cir.load %[[#ADDRB]] : !cir.ptr<!s32i, addrspace(offload_global)>, !s32i
+  // CIR-NEXT: cir.store %[[#LOADB]], %[[#ADDRC]] : !s32i, !cir.ptr<!s32i, addrspace(offload_local)>
+
+  // LLVM:     %[[#LOADB:]] = load i32, ptr addrspace(1) @test_static.b, align 4
+  // LLVM-NEXT: store i32 %[[#LOADB]], ptr addrspace(3) @test_static.c, align 4
+}