Oclgrind runtime — OpenCL ICD Runtime

Full OpenCL 1.2 API Implementation & ICD Dispatch Layer

The runtime module is Oclgrind’s public face: it implements every cl* function from the OpenCL specification, wrapping them around the internal Core simulation engine. It also provides the ICD (Installable Client Driver) dispatch table that lets OpenCL loaders discover and route API calls to Oclgrind, plus the oclgrind launcher binary that uses LD_PRELOAD injection to transparently redirect any OpenCL application.

5
Source Files
~7.4k
Lines of Code
100+
cl* Functions
9
Wrapper Structs

OpenCL 1.2 API ICD Dispatch Async Queue LD_PRELOAD Injection

📚 What is this module?

Think of the runtime module as a hotel concierge desk. An OpenCL application walks up and makes requests — “create a context,” “allocate a buffer,” “run this kernel.” The concierge (runtime) doesn’t do the work itself; it creates a tracking ticket (wrapper struct), validates the request, and delegates the actual heavy lifting to the hotel staff (the core module). The ICD dispatch table is like the hotel’s phone directory — it maps every possible guest request to the right concierge handler. The async queue is the concierge’s task board: commands are pinned, dependencies tracked, and work is dispatched in order, with completion callbacks notifying guests when their request is done.

Table of Contents

1 — Architecture & API Flow

Every OpenCL API call enters through the ICD dispatch table, which maps the function pointer to the Oclgrind implementation in runtime.cpp. The runtime function validates arguments, creates or updates the appropriate wrapper struct, delegates to the core simulation engine, and returns the result to the caller.

flowchart TD
    APP["OpenCL Application"]
    ICD["ICD Loader
libOpenCL.so"] DISP["m_dispatchTable[256]
icd.h / runtime.cpp"] RT["runtime.cpp
cl* implementations"] AQ["async_queue
asyncEnqueue / asyncQueueRelease"] CORE_CTX["core::Context"] CORE_Q["core::Queue"] CORE_K["core::Kernel"] CORE_M["core::Memory"] CORE_P["core::Program"] APP -->|"clEnqueue*()"| ICD ICD -->|"dispatch[N]"| DISP DISP --> RT RT -->|"create wrappers
validate args"| RT RT -->|"kernel / mem ops"| AQ AQ -->|"queue→enqueue(cmd)"| CORE_Q RT -->|"context mgmt"| CORE_CTX RT -->|"buffer alloc / free"| CORE_M RT -->|"build / compile"| CORE_P CORE_Q -->|"dispatch kernel"| CORE_K CORE_K -->|"read / write"| CORE_M style APP fill:#1a2332,stroke:#58a6ff,color:#c9d1d9 style ICD fill:#1a2332,stroke:#d29922,color:#c9d1d9 style DISP fill:#1c2128,stroke:#58a6ff,color:#c9d1d9 style RT fill:#1c2128,stroke:#3fb950,color:#c9d1d9 style AQ fill:#1c2128,stroke:#d29922,color:#c9d1d9 style CORE_CTX fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 style CORE_Q fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 style CORE_K fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 style CORE_M fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 style CORE_P fill:#0d1117,stroke:#58a6ff,color:#c9d1d9

Figure 1 — OpenCL API call flow from application through ICD dispatch into Core

2 — Async Command Queue

The AsyncQueue subsystem handles all enqueue operations. When a clEnqueue* call is made, the runtime creates a core::Command object, retains any referenced cl_mem or cl_kernel objects, attaches the event wait list, and hands it off to core::Queue::enqueue(). On completion, asyncQueueRelease fires callbacks, releases retained objects, and cleans up event references. Four static maps track the lifecycle: memObjectMap, kernelMap, eventMap, and waitListMap.

sequenceDiagram
    participant App as Application
    participant RT as runtime.cpp
    participant AQ as async_queue
    participant CQ as core::Queue
    participant EV as cl_event

    App->>RT: clEnqueueNDRangeKernel()
    RT->>RT: validate args, clone kernel
    RT->>AQ: asyncQueueRetain(cmd, kernel)
    AQ->>AQ: clRetainKernel + retain memArgs
    RT->>AQ: asyncEnqueue(queue, type, cmd, waitList)
    AQ->>AQ: attach wait-list events
    AQ->>CQ: queue→enqueue(cmd)
    CQ-->>AQ: return core::Event*
    AQ->>EV: create _cl_event wrapper
    AQ-->>RT: event ready
    RT-->>App: CL_SUCCESS + event

    Note over CQ: Command executes asynchronously

    CQ->>AQ: command complete callback
    AQ->>AQ: asyncQueueRelease(cmd)
    AQ->>AQ: release cl_mem, cl_kernel
    AQ->>EV: fire user callbacks
    AQ->>AQ: release wait-list events

Figure 2 — Sequence diagram of an async kernel enqueue through the command queue

3 — Key Structs & Types

Struct / TypeFileDescription
_cl_platform_idicd.hPlatform singleton; holds only the ICD dispatch void pointer
_cl_device_idicd.hVirtual device descriptor with configurable globalMemSize, constantMemSize, localMemSize, maxWGSize
_cl_contexticd.hWraps core::Context*; tracks error callback, properties, refCount, destructor callback stack
_cl_command_queueicd.hWraps core::Queue*; holds queue properties and refCount
_cl_memicd.hMemory object: tracks address, size, offset, flags, host pointer, parent (sub-buffers), destructor callbacks
cl_imageicd.hExtends _cl_mem with cl_image_format and cl_image_desc
_cl_programicd.hWraps core::Program*; tracks owning context and refCount
_cl_kernelicd.hWraps core::Kernel*; maintains memArgs map and imageArgs vector for argument tracking
_cl_eventicd.hWraps core::Event*; stores command type, callback list, owning queue, refCount
_cl_samplericd.hSampler state: addressing mode, filter mode, normalized coordinates, packed uint32_t sampler
m_dispatchTable[256]runtime.cppICD function pointer array populated with DISPATCH_TABLE_ENTRY macro; 100+ entries
APICallEntryruntime.cppRAII guard that pushes/pops function name onto thread_local g_apiCallStack for error reporting

4 — Directory Map

FileLinesPurpose
runtime.cpp~6,400All cl* API function implementations & ICD dispatch table initialization
icd.h~255ICD macro renames, wrapper struct definitions, forward declarations
async_queue.h~19Async enqueue/retain/release function declarations
async_queue.cpp~132Async command lifecycle: enqueue, retain objects, release & fire callbacks
oclgrind.cpp~570Launcher binary: argument parsing, LD_PRELOAD/LD_LIBRARY_PATH injection, Windows DLL injection
runtime.def~50Windows module definition file for exported symbols
icd.def~5ICD export definition for clIcdGetPlatformIDsKHR

5 — Module Interactions

DirectionTarget ModuleInterfacePurpose
→core::Contextnew Context(), context->getGlobalMemory()Create / destroy simulation context; access global memory
→core::Queuenew Queue(ctx), queue->enqueue(cmd), queue->finish()Submit commands; flush / finish synchronization barriers
→core::ProgramProgram::createFromSource(), program->build()Compile / link OpenCL C source to LLVM IR
→core::Kernelprogram->createKernel(name), kernel->setArg()Instantiate kernels, bind arguments
→core::Memorymemory->allocateBuffer(), memory->mapBuffer()Allocate/free/map device memory regions
←ICD LoaderclIcdGetPlatformIDsKHR()ICD discovery entry point; loader calls to detect platform
←OpenCL Applicationcl* function callsApplication-facing API surface
↔async_queueasyncEnqueue(), asyncQueueRetain(), asyncQueueRelease()Manage object lifetime across async command execution

6 — Configuration & Environment Variables

VariableDefaultDescription
OCLGRIND_GLOBAL_MEM_SIZE128 MiBTotal global memory reported by the virtual device
OCLGRIND_CONSTANT_MEM_SIZE64 KiBMaximum constant buffer size
OCLGRIND_LOCAL_MEM_SIZE32 KiBPer-workgroup local memory limit
OCLGRIND_MAX_WGSIZE1024Maximum work-group size reported to applications
OCLGRIND_CHECK_APIoffPrint API error diagnostics to stderr when set to 1
OCLGRIND_BUILD_OPTIONS—Additional flags passed to the OpenCL compiler on every build
OCLGRIND_TESTING—When set, launcher skips ../lib path resolution (uses exe directory directly)
LD_PRELOAD / DYLD_INSERT_LIBRARIES—Set by the oclgrind launcher to inject liboclgrind-rt.so
LD_LIBRARY_PATH / DYLD_LIBRARY_PATH—Prepended with Oclgrind lib directory by launcher
OCLGRIND_DATA_RACESoffEnable data-race detection plugin
OCLGRIND_INTERACTIVEoffEnable interactive debugger mode (-i flag)
OCLGRIND_PLUGINS—Colon-separated list of plugin shared libraries to load

7 — Code Examples

7.1 — Platform & Device Initialization (runtime.cpp)

The ICD entry point lazily creates the singleton platform and device, reading memory sizes from environment variables:

CL_API_ENTRY cl_int CL_API_CALL clIcdGetPlatformIDsKHR(
  cl_uint num_entries, cl_platform_id* platforms, cl_uint* num_platforms)
{
  REGISTER_API;
  if (platforms && num_entries < 1)
    ReturnError(NULL, CL_INVALID_VALUE);

  if (!m_platform) {
    m_platform = new _cl_platform_id;
    m_platform->dispatch = m_dispatchTable;

    m_device = new _cl_device_id;
    m_device->dispatch = m_dispatchTable;
    m_device->globalMemSize  = oclgrind::getEnvInt(
      "OCLGRIND_GLOBAL_MEM_SIZE", DEFAULT_GLOBAL_MEM_SIZE, false);
    m_device->localMemSize   = oclgrind::getEnvInt(
      "OCLGRIND_LOCAL_MEM_SIZE", DEFAULT_LOCAL_MEM_SIZE, false);
    m_device->maxWGSize      = oclgrind::getEnvInt(
      "OCLGRIND_MAX_WGSIZE", DEFAULT_MAX_WGSIZE, false);
  }
  if (platforms)     platforms[0] = m_platform;
  if (num_platforms) *num_platforms = 1;
  return CL_SUCCESS;
}

7.2 — Async Enqueue Lifecycle (async_queue.cpp)

When a command completes, asyncQueueRelease tears down retained references and fires user callbacks:

void asyncQueueRelease(Command* cmd)
{
  // Release retained memory objects
  if (memObjectMap.find(cmd) != memObjectMap.end()) {
    list<cl_mem> memObjects = memObjectMap[cmd];
    while (!memObjects.empty()) {
      clReleaseMemObject(memObjects.front());
      memObjects.pop_front();
    }
    memObjectMap.erase(cmd);
  }

  // Release kernel (if this was a kernel command)
  if (cmd->type == Command::KERNEL) {
    clReleaseKernel(kernelMap[cmd]);
    kernelMap.erase(cmd);
    delete ((KernelCommand*)cmd)->kernel;
  }

  // Fire user event callbacks
  cl_event event = eventMap[cmd];
  for (auto &cb : event->callbacks)
    cb.first(event, event->event->state, cb.second);

  // Release wait-list events
  for (auto &e : waitListMap[cmd])
    clReleaseEvent(e);
  waitListMap.erase(cmd);
  clReleaseEvent(event);
}

7.3 — Launcher LD_PRELOAD Injection (oclgrind.cpp)

The oclgrind binary constructs preload paths and uses execvp to replace itself with the target application:

// Construct LD_PRELOAD with the Oclgrind runtime library
string ldPreload = libdir + "/liboclgrind-rt." + LIB_EXTENSION;
const char* oldLdPreload = getenv(LD_PRELOAD_ENV);
if (oldLdPreload) {
  ldPreload += ":";
  ldPreload += oldLdPreload;
}

setEnvironment(LD_LIBRARY_PATH_ENV, ldLibraryPath.c_str());
setEnvironment(LD_PRELOAD_ENV, ldPreload.c_str());

// Replace process with target application
if (execvp(appArgs[0], appArgs) == -1) {
  cerr << "[Oclgrind] Failed to launch target application" << endl;
  exit(1);
}

8 — Deep-Dive Pages

Each card below links to a focused deep-dive page covering one subsystem of the runtime module in detail.

OpenCL API Layer ~6.4k LOC

The core of runtime.cpp: 100+ cl* function implementations, argument validation macros, the REGISTER_API call stack, error reporting, and the ICD dispatch table initialization.

  • Context, queue, buffer, program, kernel create/release
  • clEnqueueNDRangeKernel work-group dispatch
  • Image & sampler management
  • Event wait-list validation

Async Command Queue ~150 LOC

Thread-safe command lifecycle management via asyncEnqueue, asyncQueueRetain, and asyncQueueRelease. Uses four static maps to track retained objects across asynchronous command execution.

  • Object retain/release across async boundaries
  • Event creation & callback dispatch
  • Wait-list dependency tracking

ICD Dispatch & Launcher ~825 LOC

The icd.h header with OCLGRIND_ICD macro renames, wrapper struct definitions, and the oclgrind launcher binary that uses LD_PRELOAD (Linux/macOS) or DLL injection (Windows) to intercept OpenCL calls.

  • m_dispatchTable function pointer array
  • OCLGRIND_ICD macro rename strategy
  • Cross-platform launcher (execvp vs CreateRemoteThread)