plugins — Analysis & Debugging Plugins
12 files · ~5 600 lines of C++ · 6 plugins extending core::Plugin
Think of Oclgrind's core simulator as an airport control tower that monitors every aircraft movement. The plugins module is the set of specialist ground-crew teams plugged into the tower's radio feed — one team inspects cargo manifests (MemCheck), another watches for runway conflicts (RaceDetector), a third verifies that no empty containers are shipped (Uninitialized), a fourth logs flight statistics (InstructionCounter), a fifth lets you walk out onto the tarmac with binoculars (InteractiveDebugger), and a sixth writes everything to the logbook (Logger). Each team subscribes only to the radio channels it cares about; the tower broadcasts events and every registered team responds independently.
Architecture & Class Hierarchy
Every plugin inherits from Plugin (defined in core/Plugin.h)
and selectively overrides virtual hook methods. The core runtime calls these hooks
at well-defined points during simulation — memory accesses, kernel boundaries,
work-group barriers, and log messages.
classDiagram
class Plugin {
#m_context : const Context*
+instructionExecuted()
+memoryLoad()
+memoryStore()
+memoryAtomicLoad()
+memoryAtomicStore()
+memoryAllocated()
+memoryDeallocated()
+memoryMap()
+memoryUnmap()
+kernelBegin()
+kernelEnd()
+workGroupBegin()
+workGroupComplete()
+workGroupBarrier()
+workItemBegin()
+workItemComplete()
+hostMemoryLoad()
+hostMemoryStore()
+log()
+isThreadSafe() bool
}
class MemCheck {
-m_mapRegions : list~MapRegion~
+instructionExecuted()
+memoryLoad()
+memoryStore()
+memoryAtomicLoad()
+memoryAtomicStore()
+memoryMap()
+memoryUnmap()
-checkArrayAccess()
-checkLoad()
-checkStore()
-logInvalidAccess()
}
class RaceDetector {
-m_globalAccesses : map
-m_allowUniformWrites : bool
+kernelBegin()
+kernelEnd()
+memoryLoad()
+memoryStore()
+memoryAtomicLoad()
+memoryAtomicStore()
+memoryAllocated()
+memoryDeallocated()
+workGroupBarrier()
+workGroupBegin()
+workGroupComplete()
-registerAccess()
-syncWorkItems()
}
class Uninitialized {
-shadowContext : ShadowContext
+instructionExecuted()
+hostMemoryStore()
+kernelBegin()
+kernelEnd()
+memoryMap()
+workItemBegin()
+workItemComplete()
+workGroupBegin()
+workGroupComplete()
}
class InstructionCounter {
-m_instructionCounts : vector
-m_memopBytes : vector
+instructionExecuted()
+kernelBegin()
+kernelEnd()
+workGroupBegin()
+workGroupComplete()
}
class InteractiveDebugger {
-m_breakpoints : map
-m_continue : bool
+instructionExecuted()
+kernelBegin()
+kernelEnd()
+log()
+isThreadSafe() bool
}
class Logger {
-m_log : ostream*
-m_maxErrors : unsigned
+log()
}
Plugin <|-- MemCheck
Plugin <|-- RaceDetector
Plugin <|-- Uninitialized
Plugin <|-- InstructionCounter
Plugin <|-- InteractiveDebugger
Plugin <|-- Logger
Plugin Hook Dispatch Flow
When the simulator executes an instruction or accesses memory, it iterates over registered plugins and invokes the matching virtual hook. The sequence below shows a simplified memory-store dispatch touching MemCheck, RaceDetector, and Uninitialized.
sequenceDiagram
participant WI as WorkItem
participant Core as Core Runtime
participant MC as MemCheck
participant RD as RaceDetector
participant UN as Uninitialized
participant IC as InstructionCounter
WI->>Core: execute store instruction
Core->>IC: instructionExecuted(workItem, inst, result)
Core->>MC: instructionExecuted(workItem, inst, result)
Note over MC: checkArrayAccess via GEP chain
Core->>UN: instructionExecuted(workItem, inst, result)
Note over UN: propagate shadow bits
Core->>MC: memoryStore(memory, workItem, addr, size, data)
Note over MC: checkStore → isAddressValid, flags
Core->>RD: memoryStore(memory, workItem, addr, size, data)
Note over RD: registerAccess → shadow map
rect rgb(40,50,60)
Note over Core: At barrier
Core->>RD: workGroupBarrier(workGroup, flags)
Note over RD: syncWorkItems across work-items
end
Key Classes & Types
| Class / Type | File | Role |
|---|---|---|
Plugin | core/Plugin.h | Abstract base class; defines 19 virtual hook methods and isThreadSafe() |
MemCheck | MemCheck.h/.cpp | Validates memory bounds, read/write permissions, alignment, and map-region conflicts on every load/store |
MemCheck::MapRegion | MemCheck.h | Tracks mapped host-pointer regions (address, offset, size, READ/WRITE type) |
RaceDetector | RaceDetector.h/.cpp | Shadow memory tracking for data-race detection between work-items using happens-before with barriers |
RaceDetector::MemoryAccess | RaceDetector.h | Compact 10-byte record: entity, instruction pointer, bit-flags (set/store/atomic/wg), store-data byte |
RaceDetector::WorkGroupState | RaceDetector.h | Per-work-group access maps for local and global memory; pool-allocated for speed |
RaceDetector::Race | RaceDetector.h | Describes a detected race: address-space, address, two conflicting MemoryAccess records |
Uninitialized | Uninitialized.h/.cpp | Shadow-bit propagation plugin — detects reads of uninitialized memory via per-value shadow state |
ShadowFrame | Uninitialized.h | One stack frame of shadow values keyed by llvm::Value*; pushed/popped on function calls |
ShadowValues | Uninitialized.h | Stack of ShadowFrames; delegates get/set to the top frame |
ShadowMemory | Uninitialized.h | Parallel shadow address space; mirrors allocations with "clean"/"poisoned" byte masks |
ShadowWorkItem | Uninitialized.h | Owns private ShadowMemory + ShadowValues for one work-item |
ShadowWorkGroup | Uninitialized.h | Owns local ShadowMemory shared by all work-items in the group |
ShadowContext | Uninitialized.h | Top-level manager of global shadow memory, work-item and work-group shadow state, and memory pool |
InstructionCounter | InstructionCounter.h/.cpp | Per-opcode histogram grouped by function; printed at kernel end via thread-safe merging |
InteractiveDebugger | InteractiveDebugger.h/.cpp | GDB-like interface: breakpoints, stepping, memory/variable inspection, work-item switching; returns isThreadSafe()=false |
Logger | Logger.h/.cpp | Redirects log() messages to a file specified by OCLGRIND_LOG; respects OCLGRIND_MAX_ERRORS |
Plugin–Core Interactions
| Plugin | Hooks Used | Interaction with Core |
|---|---|---|
MemCheck | instructionExecuted, memoryLoad, memoryStore, memoryAtomicLoad/Store, memoryMap/Unmap |
Calls Memory::isAddressValid(), Memory::getBuffer(), and Context::logError() to report out-of-bounds, permission, and map-conflict errors |
RaceDetector | kernelBegin/End, memoryLoad/Store, memoryAtomicLoad/Store, memoryAllocated/Deallocated, workGroupBarrier, workGroupBegin/Complete |
Maintains shadow AccessMaps per work-group; on barrier calls syncWorkItems to check races within group; on workGroupComplete merges into global map under mutex |
Uninitialized | instructionExecuted, hostMemoryStore, kernelBegin/End, memoryMap, workItemBegin/Complete, workGroupBegin/Complete |
Mirrors every memory operation in ShadowMemory; propagates "poisoned" bits through arithmetic via SimpleOr / VectorOr; calls logUninitializedCF() on tainted branch conditions |
InstructionCounter | instructionExecuted, kernelBegin/End, workGroupBegin/Complete |
Thread-local counters per opcode; merged under mutex at workGroupComplete; printed to stderr at kernelEnd |
InteractiveDebugger | instructionExecuted, kernelBegin/End, log |
Pauses execution on breakpoints or single-step; reads stdin for commands (print, mem, step, workitem, etc.); forces single-threaded mode via isThreadSafe()=false |
Logger | log |
Opens file from OCLGRIND_LOG env var; writes every log() call to that stream; stops after OCLGRIND_MAX_ERRORS messages |
Configuration & Environment Variables
| Variable | Type | Default | Effect |
|---|---|---|---|
OCLGRIND_INST_COUNTS | bool | off | Enables InstructionCounter plugin — prints per-opcode histogram at kernel end |
OCLGRIND_DATA_RACES | bool | on | Enables RaceDetector plugin for data-race detection between work-items |
OCLGRIND_UNINITIALIZED | bool | off | Enables Uninitialized plugin — shadow-bit tracking for uninitialized memory reads |
OCLGRIND_INTERACTIVE | bool | off | Launches InteractiveDebugger (GDB-like) on kernel entry |
OCLGRIND_LOG | path | stderr | Sets output file for Logger plugin; when set, all diagnostic messages go to this file |
OCLGRIND_MAX_ERRORS | int | 1000 | Maximum number of error messages before Logger suppresses further output |
OCLGRIND_UNIFORM_WRITES | bool | off | When set, RaceDetector disallows uniform writes (same value to same address from multiple work-items) |
OCLGRIND_PLUGINS | path-list | — | Colon-separated list of shared-library paths to load as external plugins at runtime |
OCLGRIND_CHECK_API | bool | on | Enables MemCheck plugin — validates memory bounds, permissions, alignment |
OCLGRIND_BUILD_OPTIONS | string | — | Extra OpenCL build options passed before kernel compilation (may affect plugin behaviour) |
OCLGRIND_NUM_THREADS | int | cores | Worker threads; InteractiveDebugger forces this to 1 via isThreadSafe()=false |
Code Examples
Plugin Base Class (core/Plugin.h)
class Plugin
{
public:
Plugin(const Context* context);
virtual ~Plugin();
// Memory access hooks
virtual void memoryLoad(const Memory* memory,
const WorkItem* workItem,
size_t address, size_t size) {}
virtual void memoryStore(const Memory* memory,
const WorkItem* workItem,
size_t address, size_t size,
const uint8_t* storeData) {}
// Lifecycle hooks
virtual void kernelBegin(const KernelInvocation* ki) {}
virtual void kernelEnd(const KernelInvocation* ki) {}
virtual void workGroupBarrier(const WorkGroup* wg,
uint32_t flags) {}
// Thread-safety — override to return false to
// force single-threaded simulation
virtual bool isThreadSafe() const;
protected:
const Context* m_context;
};
MemCheck — Bounds Validation on Load
void MemCheck::checkLoad(const Memory* memory,
size_t address, size_t size) const
{
if (!memory->isAddressValid(address, size))
{
logInvalidAccess(true, memory->getAddressSpace(),
address, size);
return;
}
if (memory->getBuffer(address)->flags & CL_MEM_WRITE_ONLY)
{
m_context->logError("Invalid read from write-only buffer");
}
// Check if memory is currently mapped for writing
for (auto& region : m_mapRegions)
{
if (region.type == MapRegion::WRITE &&
address < region.address + region.size &&
address + size >= region.address)
{
m_context->logError(
"Invalid read from buffer mapped for writing");
}
}
}
RaceDetector — Barrier Synchronisation
void RaceDetector::workGroupBarrier(
const WorkGroup* workGroup, uint32_t flags)
{
if (flags & CLK_LOCAL_MEM_FENCE)
{
syncWorkItems(workGroup->getLocalMemory(),
STATE(workGroup),
STATE(workGroup).wiLocal);
}
if (flags & CLK_GLOBAL_MEM_FENCE)
{
syncWorkItems(m_context->getGlobalMemory(),
STATE(workGroup),
STATE(workGroup).wiGlobal);
}
}
Directory Map
Deep-Dive Topics
Memory Checking L3
MemCheck internals — bounds validation, permission checks, map-region tracking, GEP-chain array-access analysis, and error reporting paths.
Race Detection L3
RaceDetector shadow maps, happens-before semantics with barriers, per-work-group pool-allocated access records, global mutex-bank merging, and uniform-write policies.
Interactive Debugging L3
InteractiveDebugger command set (backtrace, break, continue, delete, info, list, mem, next, print, step, workitem), breakpoint management, single-thread enforcement.