plugins — Analysis & Debugging Plugins

12 files · ~5 600 lines of C++ · 6 plugins extending core::Plugin

💡 What is this?

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 / TypeFileRole
Plugincore/Plugin.hAbstract base class; defines 19 virtual hook methods and isThreadSafe()
MemCheckMemCheck.h/.cppValidates memory bounds, read/write permissions, alignment, and map-region conflicts on every load/store
MemCheck::MapRegionMemCheck.hTracks mapped host-pointer regions (address, offset, size, READ/WRITE type)
RaceDetectorRaceDetector.h/.cppShadow memory tracking for data-race detection between work-items using happens-before with barriers
RaceDetector::MemoryAccessRaceDetector.hCompact 10-byte record: entity, instruction pointer, bit-flags (set/store/atomic/wg), store-data byte
RaceDetector::WorkGroupStateRaceDetector.hPer-work-group access maps for local and global memory; pool-allocated for speed
RaceDetector::RaceRaceDetector.hDescribes a detected race: address-space, address, two conflicting MemoryAccess records
UninitializedUninitialized.h/.cppShadow-bit propagation plugin — detects reads of uninitialized memory via per-value shadow state
ShadowFrameUninitialized.hOne stack frame of shadow values keyed by llvm::Value*; pushed/popped on function calls
ShadowValuesUninitialized.hStack of ShadowFrames; delegates get/set to the top frame
ShadowMemoryUninitialized.hParallel shadow address space; mirrors allocations with "clean"/"poisoned" byte masks
ShadowWorkItemUninitialized.hOwns private ShadowMemory + ShadowValues for one work-item
ShadowWorkGroupUninitialized.hOwns local ShadowMemory shared by all work-items in the group
ShadowContextUninitialized.hTop-level manager of global shadow memory, work-item and work-group shadow state, and memory pool
InstructionCounterInstructionCounter.h/.cppPer-opcode histogram grouped by function; printed at kernel end via thread-safe merging
InteractiveDebuggerInteractiveDebugger.h/.cppGDB-like interface: breakpoints, stepping, memory/variable inspection, work-item switching; returns isThreadSafe()=false
LoggerLogger.h/.cppRedirects log() messages to a file specified by OCLGRIND_LOG; respects OCLGRIND_MAX_ERRORS

Plugin–Core Interactions

PluginHooks UsedInteraction with Core
MemCheckinstructionExecuted, memoryLoad, memoryStore, memoryAtomicLoad/Store, memoryMap/Unmap Calls Memory::isAddressValid(), Memory::getBuffer(), and Context::logError() to report out-of-bounds, permission, and map-conflict errors
RaceDetectorkernelBegin/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
UninitializedinstructionExecuted, 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
InstructionCounterinstructionExecuted, kernelBegin/End, workGroupBegin/Complete Thread-local counters per opcode; merged under mutex at workGroupComplete; printed to stderr at kernelEnd
InteractiveDebuggerinstructionExecuted, 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
Loggerlog Opens file from OCLGRIND_LOG env var; writes every log() call to that stream; stops after OCLGRIND_MAX_ERRORS messages

Configuration & Environment Variables

VariableTypeDefaultEffect
OCLGRIND_INST_COUNTSbooloffEnables InstructionCounter plugin — prints per-opcode histogram at kernel end
OCLGRIND_DATA_RACESboolonEnables RaceDetector plugin for data-race detection between work-items
OCLGRIND_UNINITIALIZEDbooloffEnables Uninitialized plugin — shadow-bit tracking for uninitialized memory reads
OCLGRIND_INTERACTIVEbooloffLaunches InteractiveDebugger (GDB-like) on kernel entry
OCLGRIND_LOGpathstderrSets output file for Logger plugin; when set, all diagnostic messages go to this file
OCLGRIND_MAX_ERRORSint1000Maximum number of error messages before Logger suppresses further output
OCLGRIND_UNIFORM_WRITESbooloffWhen set, RaceDetector disallows uniform writes (same value to same address from multiple work-items)
OCLGRIND_PLUGINSpath-list—Colon-separated list of shared-library paths to load as external plugins at runtime
OCLGRIND_CHECK_APIboolonEnables MemCheck plugin — validates memory bounds, permissions, alignment
OCLGRIND_BUILD_OPTIONSstring—Extra OpenCL build options passed before kernel compilation (may affect plugin behaviour)
OCLGRIND_NUM_THREADSintcoresWorker 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

src/plugins/ ├── InstructionCounter.h — per-opcode histogram plugin header ├── InstructionCounter.cpp — histogram collection & printing ├── InteractiveDebugger.h — GDB-like debugger plugin header ├── InteractiveDebugger.cpp — breakpoint & command loop impl ├── Logger.h — log-redirect plugin header ├── Logger.cpp — file output & error counting ├── MemCheck.h — memory-check plugin header ├── MemCheck.cpp — bounds/permission validation ├── RaceDetector.h — race-detector plugin header ├── RaceDetector.cpp — shadow-memory race logic ├── Uninitialized.h — uninitialized-memory plugin header + 6 Shadow* classes └── Uninitialized.cpp — shadow-bit propagation engine (~2 000 lines)

Deep-Dive Topics

Each card below links to a dedicated L3 page (when available) covering the topic in full depth.

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.

↑