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>
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:
size=N— buffer size in bytesint,float,double, … — data type for parsing & dump formattingdump— print results after executionfill=V— fill buffer with a constant valuerange=S:I:E— fill with arithmetic sequencenoinit— allocate without initialising (output-only buffers)null— pass a NULL pointerhex— use hexadecimal for input/outputro/wo— setCL_MEM_READ_ONLY/CL_MEM_WRITE_ONLY
// 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:
const char*— semantic errors (e.g. "Expected argument header <…>")ifstream::iostate—eofbitfor unexpected EOF,failbitfor parse failures
The PARSING macro updates m_parsing before each read, so error messages include context like "Line 7: Failed to parse argument 2: outputBuf".
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().
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.
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:
fill=V— fills the entire buffer with value V repeatedrange=S:I:E— fills with an arithmetic sequence starting at S, incrementing by I, up to (but not including) E
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
| State | Entry Condition | Key Actions | Exit |
|---|---|---|---|
| Constructed | Simulation() default ctor | Members zeroed; no resources held | load() called |
| Loading | load(filename) | Opens file, reads header, builds program, creates kernel, parses arguments | Returns true or throws |
| Ready | load() returns true | All arguments bound, NDRange validated | run() called |
| Running | run(dumpGlobalMemory) | KernelInvocation::run executes work-groups | Execution completes |
| Dumping | After kernel finishes | Iterates m_dumpArguments, prints results | All dumps done |
| Destroyed | Destructor or scope exit | Deletes Context, Program, Kernel | — |
Edge Cases & Common Pitfalls
| Scenario | Behaviour | Resolution |
|---|---|---|
Missing program file referenced in .sim | ifstream throws; error message includes filename | Ensure the .cl / .bc path is relative to cwd or absolute |
Zero-dimension NDRange (e.g. 0 1 1) | Zero work-groups → no execution, no output | All three dimensions must be ≥ 1 |
| Work-group size larger than NDRange | Uniform check fails → abort with message | WG size must evenly divide NDRange per dimension |
noinit buffer read before kernel writes | Reads uninitialised memory — contents undefined | Only use noinit for output-only buffers |
| Argument count mismatch | EOF error during parseArgument — "Unexpected EOF" | Provide exactly as many argument blocks as the kernel signature demands |
Non-UTF-8 characters in .sim | Parser reads bytes as-is; may corrupt string arguments | Use 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
- CLI Interface — how command-line flags drive the Simulation class
- Async Command Queue — the event-driven alternative used by the full runtime path
- OpenCL API Layer — how the runtime wraps Core objects that Simulation uses directly
- Kernel Module Overview — parent page with links to all kernel sub-topics