Focus

Simulation Lifecycle Deep-Dive

How the Simulation class in Simulation.h/.cpp loads a .sim file, builds a program, creates a kernel, sets arguments, and runs the compute workload.

End-to-End Flow

graph TD
  SF[.sim file] -->|load| SIM[Simulation::load]
  SIM -->|read| PF[Program file .cl / .bc]
  SIM -->|create| PRG[Core::Program]
  PRG -->|build| BC[LLVM bitcode]
  SIM -->|createKernel| KRN[Core::Kernel]
  SIM -->|parseArgument ×N| MEM[Core::Memory allocs]
  SIM -->|run| KI[KernelInvocation::run]
  KI -->|per work-group| WG[Work-group execution]
  KI -->|dump| OUT[stdout results]

.sim File Format

The simulation file is a line-oriented text format. Comments begin with #. The first six tokens are mandatory header fields:

// .sim file structure (parsed in Simulation::load)
//
// Line 1: program_file.cl     — path to OpenCL source or .bc bitcode
// Line 2: kernel_name         — entry-point kernel function
// Line 3: Gx Gy Gz           — global NDRange dimensions
// Line 4: Lx Ly Lz           — local work-group size
// Lines 5+: one <header> block per kernel argument
//
// Example:
//   vecadd.cl
//   vecAdd
//   1024 1 1
//   64 1 1
//   <int size=4096> 1 2 3 4 ...
//   <int size=4096> 10 20 30 40 ...
//   <int size=4096 noinit dump>
Bitcode detection: The loader reads the first two bytes — if they are 0x42 0x43 ("BC"), the file is loaded as LLVM bitcode via Program::createFromBitcodeFile(). Otherwise it's compiled from source with Program::build().

Argument Header Parsing

Each argument is wrapped in angle brackets with optional modifiers:

// Argument header examples
// Read-only integer buffer, 256 bytes, filled with 42:
//   <int size=256 ro fill=42>
//
// Output-only float buffer, dump at end:
//   <float size=1024 wo noinit dump>
//
// Scalar integer (no size = pass by value):
//   <int> 256

Simulation Class Members

classDiagram
  class Simulation {
    -Context* m_context
    -Kernel* m_kernel
    -Program* m_program
    -Size3 m_ndrange
    -Size3 m_wgsize
    -ifstream m_simfile
    -list~DumpArg~ m_dumpArguments
    +load(filename) bool
    +run(dumpGlobalMemory) void
    -parseArgument(index) void
    -get~T~(result) void
    -dumpArgument~T~(arg) void
  }

Error Handling

The load() method uses C++ exceptions internally. Two exception types are caught:

The PARSING macro updates m_parsing before each read, so error messages include context like "Line 7: Failed to parse argument 2: outputBuf".

Uniform work-groups: If m_kernel->requiresUniformWorkGroups() returns true, the loader verifies that the NDRange is exactly divisible by the work-group size — otherwise it aborts with a clear message.

Run & Dump

After load() succeeds, run() invokes KernelInvocation which iterates over work-groups. If dumpGlobalMemory is true, all global memory is printed. Additionally, any argument marked dump is formatted by dumpArgument<T>(), using the type from the header to cast memory contents.

Program Loading Strategies

The load() method supports two distinct paths depending on the input file:

Source Compilation Path

// Source loading from Simulation::load
progFile.seekg(0, ios_base::end);
size_t sz = progFile.tellg();
progFile.seekg(0, ios_base::beg);

char* data = new char[sz + 1];
progFile.read(data, sz + 1);
data[sz] = '\0';
m_program = new Program(m_context, data);
delete[] data;

if (!m_program->build(Program::BUILD, "")) {
  cerr << "Build failure:" << endl
       << m_program->getBuildLog() << endl;
  return false;
}

Bitcode Loading Path

If the file starts with the LLVM bitcode magic bytes 0x42 0x43 ("BC"), the entire build step is skipped and the pre-compiled IR is loaded directly via Program::createFromBitcodeFile().

Performance tip: For large kernels, pre-compiling to bitcode avoids the Clang compilation overhead on every simulation run. Use clang -emit-llvm -c -target spir64 to produce the .bc file.

DumpArg Structure

Each argument marked with dump in the header generates a DumpArg entry:

struct DumpArg {
  size_t      address;  // simulated global memory address
  size_t      size;     // buffer size in bytes
  ArgDataType type;     // TYPE_INT, TYPE_FLOAT, etc.
  std::string name;     // kernel argument name
  bool        hex;      // print in hexadecimal
};

After execution, dumpArgument<T>() reads from global memory and prints each element with its index. The template is instantiated for every ArgDataType to handle formatting differences (e.g. single-byte types are cast to int for display).

Input Parsing Templates

The get<T>() template reads typed values from the .sim file, handling line continuation and comments. If the current line buffer is exhausted, it reads the next line, strips comments (everything after #), and retries. This allows argument data to span multiple lines freely.

EOF handling: If the file ends before all arguments are parsed, an ifstream::eofbit exception is thrown with the message "Unexpected EOF when parsing <context>", where context is the last value of m_parsing.

Fill & Range Initialisers

Two convenience initialisers avoid writing every element explicitly:

Both use parseFill<T> and parseRange<T> templates that write directly to an unsigned char* buffer before storing it in simulated global memory.

Work-Group Size Validation

Before execution, the loader checks that the work-group size evenly divides the NDRange dimensions — but only if the kernel requires uniform work-groups. OpenCL 2.0+ relaxed this requirement, and Oclgrind reflects that via Kernel::requiresUniformWorkGroups().

Lifecycle State Table

StateEntry ConditionKey ActionsExit
ConstructedSimulation() default ctorMembers zeroed; no resources heldload() called
Loadingload(filename)Opens file, reads header, builds program, creates kernel, parses argumentsReturns true or throws
Readyload() returns trueAll arguments bound, NDRange validatedrun() called
Runningrun(dumpGlobalMemory)KernelInvocation::run executes work-groupsExecution completes
DumpingAfter kernel finishesIterates m_dumpArguments, prints resultsAll dumps done
DestroyedDestructor or scope exitDeletes Context, Program, Kernel—

Edge Cases & Common Pitfalls

ScenarioBehaviourResolution
Missing program file referenced in .simifstream throws; error message includes filenameEnsure the .cl / .bc path is relative to cwd or absolute
Zero-dimension NDRange (e.g. 0 1 1)Zero work-groups → no execution, no outputAll three dimensions must be ≥ 1
Work-group size larger than NDRangeUniform check fails → abort with messageWG size must evenly divide NDRange per dimension
noinit buffer read before kernel writesReads uninitialised memory — contents undefinedOnly use noinit for output-only buffers
Argument count mismatchEOF error during parseArgument — "Unexpected EOF"Provide exactly as many argument blocks as the kernel signature demands
Non-UTF-8 characters in .simParser reads bytes as-is; may corrupt string argumentsUse ASCII or UTF-8 encoded files only

Troubleshooting

Build failure during load

If Program::build() fails, the error message includes the Clang build log via m_program->getBuildLog(). Common causes include missing #include headers, unsupported OpenCL extensions, or syntax errors in the .cl source.

Unexpected EOF errors

The .sim parser reads arguments sequentially. If the file ends mid-argument (e.g. a size=4096 buffer with only 512 values), the get<T>() template throws an EOF exception. Verify that each buffer's data matches its declared size in bytes.

Related Topics