kernel — Standalone Kernel Runner
CLI Tool Standalone Execution Simulation File Format Testing & Development
📚 What is this?
The kernel module provides the oclgrind-kernel command-line tool — a standalone entry point for executing OpenCL kernels without writing an OpenCL host application. Instead of calling clCreateContext, clBuildProgram, clSetKernelArg, etc., you describe everything in a simple text file (a “sim file”), and the Simulation class orchestrates the entire lifecycle for you.
💡 Real-world analogy
Imagine you are a chef who normally works in a large restaurant kitchen (the full OpenCL host program) — with waiters taking orders, a cashier, and a seating host. Now imagine a tiny food truck with a single window. You hand in a recipe card (the sim file) listing the dish name and ingredients, and the truck cooks it immediately — no waiters, no seating chart, no cashier. That food truck is oclgrind-kernel: a stripped-down shortcut to get straight to the cooking (kernel execution) without the ceremony of the full restaurant (runtime API).
Module Architecture
The kernel module consists of two key components: a Simulation class that manages the entire kernel execution lifecycle, and a CLI entry point that translates command-line flags into environment variables understood by the core engine.
flowchart TD CLI["oclgrind-kernel.cpp
CLI Entry Point"] PA["parseArguments()
Process 20+ flags"] SE["setEnvironment()
Map flags → OCLGRIND_* vars"] SIM["Simulation::load()
Parse sim file"] OP["Open program file
(source or bitcode)"] BUILD["Program::build()
Compile OpenCL C → LLVM IR"] BC["Program::createFromBitcodeFile()
Load precompiled bitcode"] KRN["Program::createKernel()
Extract named kernel"] ARGS["parseArgument() × N
Parse typed argument headers"] ALLOC["Memory::allocateBuffer()
Allocate global buffers"] RUN["Simulation::run()"] KI["KernelInvocation::run()
Execute NDRange"] DUMP["dumpArgument()
Print output buffers"] CLI --> PA PA --> SE PA -->|"simfile path"| SIM SIM --> OP OP -->|"magic 0x4243"| BC OP -->|"else source"| BUILD BC --> KRN BUILD --> KRN KRN --> ARGS ARGS --> ALLOC ALLOC --> RUN RUN --> KI KI --> DUMP
Simulation Lifecycle
sequenceDiagram
participant User as User / Shell
participant CLI as oclgrind-kernel
participant SIM as Simulation
participant CTX as Context
participant PRG as Program
participant KRN as Kernel
participant MEM as Memory
participant KI as KernelInvocation
User->>CLI: oclgrind-kernel [flags] sim.file
CLI->>CLI: parseArguments() → setEnvironment()
CLI->>SIM: Simulation()
SIM->>CTX: new Context()
CLI->>SIM: load("sim.file")
SIM->>SIM: Read program path, kernel name, NDRange, WGSize
alt Source (.cl)
SIM->>PRG: new Program(ctx, source)
SIM->>PRG: build(BUILD, "")
else Bitcode (.bc)
SIM->>PRG: createFromBitcodeFile(ctx, path)
end
SIM->>KRN: program→createKernel(name)
loop For each argument
SIM->>SIM: parseArgument(index) — read header tokens
SIM->>MEM: allocateBuffer(size, flags)
SIM->>MEM: store(data, address, size)
SIM->>KRN: setArgument(index, value)
end
CLI->>SIM: run(dumpGlobalMemory)
SIM->>KI: KernelInvocation::run(ctx, kernel, 3, offset, ndrange, wgsize)
KI-->>SIM: Execution complete
SIM->>SIM: dumpArgument() for each dump-flagged arg
SIM-->>CLI: return
CLI-->>User: Output results
Key Classes & Functions
| Symbol | File | Description |
|---|---|---|
Simulation | Simulation.h | Main class: owns Context, Kernel, and Program. Orchestrates load → parse → run lifecycle. |
Simulation::load() | Simulation.cpp | Parses the sim file: reads program path, kernel name, NDRange, work-group size, and all arguments. Returns false on any error. |
Simulation::run() | Simulation.cpp | Asserts all arguments are set, calls KernelInvocation::run(), then dumps flagged output buffers. |
Simulation::parseArgument() | Simulation.cpp | Parses a single argument: reads header tokens (size, type, fill, range, dump, ro/wo, null, noinit, hex), allocates memory, stores data. |
Simulation::get<T>() | Simulation.cpp | Template helper that reads a typed value from the sim file, skipping comments (#) and blank lines. |
Simulation::dumpArgument<T>() | Simulation.cpp | Reads back a buffer from global memory and prints element-by-element to stdout, supporting hex formatting. |
Simulation::parseFill<T>() | Simulation.cpp | Fills an argument buffer with a single repeated value (e.g., fill=0). |
Simulation::parseRange<T>() | Simulation.cpp | Fills an argument buffer with a range of values: start:increment:end. |
ArgDataType (enum) | Simulation.h | Enumerates 11 supported types: TYPE_NONE, TYPE_CHAR through TYPE_DOUBLE. |
DumpArg (struct) | Simulation.h | Stores address, size, type, name, and hex flag for an argument marked with dump. |
main() | oclgrind-kernel.cpp | Entry point: calls parseArguments(), creates Simulation, calls load() then run(). |
parseArguments() | oclgrind-kernel.cpp | Iterates argv, matches 20+ flags via strcmp, calls setEnvironment() for each. |
setEnvironment() | oclgrind-kernel.cpp | Cross-platform wrapper: calls setenv() on POSIX or _putenv_s() on Windows. |
printUsage() | oclgrind-kernel.cpp | Prints the full help text with all flags and descriptions to stdout. |
readValue<T>() | Simulation.cpp | Free-standing template utility that reads a typed value from an istream, with special 1-byte char handling. |
Interactions with Other Modules
| Dependency | Module | Direction | How & Why |
|---|---|---|---|
Context | core | kernel → core | Simulation creates and owns a Context instance, which provides global memory and plugin lifecycle management. |
Program | core | kernel → core | Simulation::load() either builds from source via Program::build() or loads precompiled bitcode via Program::createFromBitcodeFile(). |
Kernel | core | kernel → core | Obtained from Program::createKernel(). The Simulation calls getNumArguments(), getArgumentSize(), getArgumentTypeName(), and setArgument(). |
KernelInvocation | core | kernel → core | Simulation::run() calls KernelInvocation::run() to dispatch the NDRange execution with context, kernel, sizes. |
Memory | core | kernel → core | parseArgument() calls getGlobalMemory() to allocate buffers and store initial data. dumpArgument() calls load() to read results back. |
| Plugins (all) | plugins | core → plugins | Indirectly activated via environment variables. CLI flags like --data-races set OCLGRIND_DATA_RACES, which Context reads to load the corresponding plugin. |
| Runtime (ICD) | runtime | none | The kernel module bypasses the runtime entirely — it talks directly to core, providing a simpler alternative execution path. |
CLI Flags & Configuration
Every CLI flag maps directly to an OCLGRIND_* environment variable via the setEnvironment() helper. The core engine reads these variables at Context construction time.
| CLI Flag | Short | Env Variable | Argument | Description |
|---|---|---|---|---|
--build-options | — | OCLGRIND_BUILD_OPTIONS | OPTIONS | Additional options passed to the OpenCL compiler (Clang) |
--compute-units | — | OCLGRIND_COMPUTE_UNITS | UNITS | Override the number of compute units reported by the device |
--constant-mem-size | — | OCLGRIND_CONSTANT_MEM_SIZE | BYTES | Set the constant memory size of the simulated device |
--data-races | — | OCLGRIND_DATA_RACES | — | Enable data-race detection plugin (RaceDetector) |
--disable-pch | — | OCLGRIND_DISABLE_PCH | — | Disable precompiled headers (slower but avoids PCH issues) |
--dump-spir | — | OCLGRIND_DUMP_SPIR | — | Dump compiled SPIR / LLVM IR to temporary files for inspection |
--global-mem | -g | internal flag | — | Output entire global memory contents at exit |
--global-mem-size | — | OCLGRIND_GLOBAL_MEM_SIZE | BYTES | Set the global memory size of the simulated device |
--help | -h | — | — | Display usage information and exit |
--inst-counts | — | OCLGRIND_INST_COUNTS | — | Enable instruction counting plugin (opcode histograms) |
--interactive | -i | OCLGRIND_INTERACTIVE | — | Enable interactive debugger (GDB-like kernel debugging) |
--local-mem-size | — | OCLGRIND_LOCAL_MEM_SIZE | BYTES | Set the local memory size per work-group |
--log | — | OCLGRIND_LOG | LOGFILE | Redirect log and error messages to a file |
--max-errors | — | OCLGRIND_MAX_ERRORS | NUM | Limit the number of error/warning messages before suppression |
--max-wgsize | — | OCLGRIND_MAX_WGSIZE | WGSIZE | Override the maximum work-group size of the device |
--num-threads | — | OCLGRIND_NUM_THREADS | NUM | Set the number of worker threads for parallel work-group execution |
--pch-dir | — | OCLGRIND_PCH_DIR | DIR | Override directory containing precompiled OpenCL builtin headers |
--plugins | — | OCLGRIND_PLUGINS | PLUGINS | Load colon-separated list of additional plugin shared libraries |
--quick | -q | OCLGRIND_QUICK | — | Only run first and last work-group (fast smoke test) |
--uniform-writes | — | OCLGRIND_UNIFORM_WRITES | — | Don’t suppress uniform write-write data-races |
--uninitialized | — | OCLGRIND_UNINITIALIZED | — | Report usage of uninitialized memory values |
--version | -v | — | — | Display version information and exit |
Simulation File Format
The sim file is a plain-text format parsed by Simulation::load(). Lines starting with # are comments. The format is:
// Sim file structure (parsed by Simulation::load):
// Line 1: program file path (e.g., "vecadd.cl" or "vecadd.bc")
// Line 2: kernel name (e.g., "vecadd")
// Line 3: NDRange x y z (e.g., "1024 1 1")
// Line 4: work-group size x y z (e.g., "64 1 1")
// Lines 5+: argument headers + data, one per kernel argument
//
// Argument header format: <token token ...>
// Tokens: size=N, type (int/float/...), fill=V, range=S:I:E,
// dump, hex, noinit, null, ro, wo
Example Sim File
// vecadd.sim — add two vectors of 8 floats
vecadd.cl # OpenCL source file
vecadd # Kernel name
8 1 1 # NDRange: 8 work-items
8 1 1 # Work-group size: single group of 8
# Argument 0: input buffer A (read-only, 8 floats filled with 1.0)
<float size=32 ro fill=1.0>
# Argument 1: input buffer B (read-only, range 0..7)
<float size=32 ro range=0:1:7>
# Argument 2: output buffer C (write-only, dump results)
<float size=32 wo noinit dump>
Code Examples
Simulation Construction & Lifecycle
// From oclgrind-kernel.cpp — the complete main() flow
int main(int argc, char* argv[])
{
// Step 1: Parse CLI flags and set environment variables
if (!parseArguments(argc, argv))
return 1;
// Step 2: Create simulation (allocates Context)
Simulation simulation;
// Step 3: Load sim file (compile program, create kernel, parse args)
if (!simulation.load(simfile))
return 1;
// Step 4: Execute the kernel and dump results
simulation.run(outputGlobalMemory);
}
Argument Parsing — Header Token Processing
// Simplified from Simulation::parseArgument()
// Each argument has a header like: <float size=32 ro fill=1.0 dump>
void Simulation::parseArgument(size_t index)
{
size_t size = SIZE_MAX;
ArgDataType type = TYPE_NONE;
bool dump = false, hex = false, noinit = false, null = false;
string fill, range;
// Read header tokens between < and >
// Recognized tokens: char/uchar/.../double, size=N, fill=V,
// range=S:I:E, dump, hex, noinit, null, ro, wo
// After parsing: allocate buffer, store initial data
Memory* globalMemory = m_context->getGlobalMemory();
size_t address = globalMemory->allocateBuffer(size, flags);
globalMemory->store(data, address, size);
// Bind argument to kernel
m_kernel->setArgument(index, value);
}
Directory Map
Source tree for src/kernel/
src/kernel/
├── Simulation.h ← Class declaration: Simulation, ArgDataType enum, DumpArg struct
├── Simulation.cpp ← Implementation: load(), run(), parseArgument(), get<T>(), dumpArgument<T>()
│ parseFill<T>(), parseRange<T>(), parseArgumentData<T>(), readValue<T>()
│ ~780 lines — the core simulation engine
└── oclgrind-kernel.cpp ← CLI entry point: main(), parseArguments(), setEnvironment(), printUsage()
~317 lines — flag parsing and orchestration
Deep Dives
Simulation Lifecycle FOCUS
Step-by-step walkthrough of the load → parse → execute pipeline inside the Simulation class.
- Sim file parsing with line-by-line error recovery
- Bitcode vs. source detection (magic bytes
0x42 0x43) - Argument header tokenizer and type inference
- Buffer allocation, fill/range initializers, dump logic
CLI Interface FOCUS
How oclgrind-kernel bridges the gap between shell invocations and the core simulation engine.
- Complete flag → environment variable mapping
- Cross-platform
setEnvironment()(POSIX vs. Win32) - Error handling and usage output
- Integration with plugin activation