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.
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 / Type | File | Description |
|---|---|---|
_cl_platform_id | icd.h | Platform singleton; holds only the ICD dispatch void pointer |
_cl_device_id | icd.h | Virtual device descriptor with configurable globalMemSize, constantMemSize, localMemSize, maxWGSize |
_cl_context | icd.h | Wraps core::Context*; tracks error callback, properties, refCount, destructor callback stack |
_cl_command_queue | icd.h | Wraps core::Queue*; holds queue properties and refCount |
_cl_mem | icd.h | Memory object: tracks address, size, offset, flags, host pointer, parent (sub-buffers), destructor callbacks |
cl_image | icd.h | Extends _cl_mem with cl_image_format and cl_image_desc |
_cl_program | icd.h | Wraps core::Program*; tracks owning context and refCount |
_cl_kernel | icd.h | Wraps core::Kernel*; maintains memArgs map and imageArgs vector for argument tracking |
_cl_event | icd.h | Wraps core::Event*; stores command type, callback list, owning queue, refCount |
_cl_sampler | icd.h | Sampler state: addressing mode, filter mode, normalized coordinates, packed uint32_t sampler |
m_dispatchTable[256] | runtime.cpp | ICD function pointer array populated with DISPATCH_TABLE_ENTRY macro; 100+ entries |
APICallEntry | runtime.cpp | RAII guard that pushes/pops function name onto thread_local g_apiCallStack for error reporting |
4 — Directory Map
| File | Lines | Purpose |
|---|---|---|
runtime.cpp | ~6,400 | All cl* API function implementations & ICD dispatch table initialization |
icd.h | ~255 | ICD macro renames, wrapper struct definitions, forward declarations |
async_queue.h | ~19 | Async enqueue/retain/release function declarations |
async_queue.cpp | ~132 | Async command lifecycle: enqueue, retain objects, release & fire callbacks |
oclgrind.cpp | ~570 | Launcher binary: argument parsing, LD_PRELOAD/LD_LIBRARY_PATH injection, Windows DLL injection |
runtime.def | ~50 | Windows module definition file for exported symbols |
icd.def | ~5 | ICD export definition for clIcdGetPlatformIDsKHR |
5 — Module Interactions
| Direction | Target Module | Interface | Purpose |
|---|---|---|---|
| → | core::Context | new Context(), context->getGlobalMemory() | Create / destroy simulation context; access global memory |
| → | core::Queue | new Queue(ctx), queue->enqueue(cmd), queue->finish() | Submit commands; flush / finish synchronization barriers |
| → | core::Program | Program::createFromSource(), program->build() | Compile / link OpenCL C source to LLVM IR |
| → | core::Kernel | program->createKernel(name), kernel->setArg() | Instantiate kernels, bind arguments |
| → | core::Memory | memory->allocateBuffer(), memory->mapBuffer() | Allocate/free/map device memory regions |
| ← | ICD Loader | clIcdGetPlatformIDsKHR() | ICD discovery entry point; loader calls to detect platform |
| ← | OpenCL Application | cl* function calls | Application-facing API surface |
| ↔ | async_queue | asyncEnqueue(), asyncQueueRetain(), asyncQueueRelease() | Manage object lifetime across async command execution |
6 — Configuration & Environment Variables
| Variable | Default | Description |
|---|---|---|
OCLGRIND_GLOBAL_MEM_SIZE | 128 MiB | Total global memory reported by the virtual device |
OCLGRIND_CONSTANT_MEM_SIZE | 64 KiB | Maximum constant buffer size |
OCLGRIND_LOCAL_MEM_SIZE | 32 KiB | Per-workgroup local memory limit |
OCLGRIND_MAX_WGSIZE | 1024 | Maximum work-group size reported to applications |
OCLGRIND_CHECK_API | off | Print 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_RACES | off | Enable data-race detection plugin |
OCLGRIND_INTERACTIVE | off | Enable 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
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
clEnqueueNDRangeKernelwork-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_dispatchTablefunction pointer arrayOCLGRIND_ICDmacro rename strategy- Cross-platform launcher (
execvpvsCreateRemoteThread)