This document describes the architecture and design decisions of ObjectIR.
- Multi-target compilation - Single IR → Multiple platforms
- OOP semantics preservation - Maintain high-level concepts
- Developer friendly - Easy to read, write, and understand
- Extensible - Simple to add backends and optimizations
- Type safe - Catch errors at IR level, not runtime
- Performant - Efficient representation and compilation
┌─────────────────────────────────────────────────────────────┐
│ Frontend Languages │
│ (Your Language, C#, Java, C++, TypeScript, etc.) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ObjectIR Core │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ IR Representation (Module, Types, Instructions) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Builder API (Fluent interface) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Validator (Type checking, stack validation) │ │
│ └──────────────────────────────────────────────────────┘ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Optimizer (Optional transformation passes) │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Backends │
│ ┌──────────┬──────────┬───────────┬──────────┬─────────┐ │
│ │ CIL │ JVM │ C++ │ JS/TS │ Lua │ │
│ └──────────┴──────────┴───────────┴──────────┴─────────┘ │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Target Runtimes │
│ (.NET, JVM, Native, Browser, Lua VM, ObjectIR runtime) │
└─────────────────────────────────────────────────────────────┘
The IR consists of several key types:
- Top-level container
- Contains types and metadata
- Version information
- ClassDefinition: Reference types with inheritance
- InterfaceDefinition: Contracts
- StructDefinition: Value types
- EnumDefinition: Named constants
- FieldDefinition: Data storage
- MethodDefinition: Executable code
- PropertyDefinition: Accessors
- GenericParameter: Type parameters
- Load/Store: Stack manipulation
- Arithmetic/Logic: Operations
- Calls: Method invocation
- Control Flow: Branching and loops
- Object Operations: Creation and casting
ObjectIR uses a nominal type system with:
TypeReference
├── Primitive (int32, float32, bool, etc.)
├── Reference (classes, interfaces)
├── Generic (List<T>, Dict<K,V>)
└── Constructed (arrays, pointers)
Type Safety Rules:
- All operations must have compatible types
- Stack must be balanced at control flow joins
- Generic type arguments must satisfy constraints
- No implicit conversions (except safe widening)
ObjectIR uses a hybrid model:
Stack-based (like CIL/JVM):
ldarg x
ldarg y
add
stloc result
Structured control flow (like high-level languages):
if (condition) {
...
} else {
...
}
This combines the benefits of both:
- Stack operations are compact and efficient
- Structured flow is readable and easy to optimize
Every instruction has a stack effect:
Instruction | Stack Before | Stack After
--------------------|--------------|-------------
ldarg x | ... | ..., value
add | ..., a, b | ..., result
stloc x | ..., value | ...
call F(T1,T2)->R | ..., arg1, arg2 | ..., result
The validator ensures stack consistency at all merge points (branches, loops).
ObjectIR defines a minimal standard library that all backends must support:
namespace System {
class List<T> {
method Add(item: T) -> void
method Remove(item: T) -> bool
method get_Item(index: int32) -> T
method get_Count() -> int32
// ... more methods
}
class Dict<K, V> {
method Add(key: K, value: V) -> void
method ContainsKey(key: K) -> bool
method TryGetValue(key: K, out value: V) -> bool
// ... more methods
}
class Set<T> { ... }
class String { ... }
class Optional<T> { ... }
}Backends map these to native implementations:
- C#:
System.Collections.Generic.* - Java:
java.util.* - C++:
std::vector,std::unordered_map - JavaScript:
Array,Map,Set
public interface IBackend
{
// Compile a module to the target format
void Compile(Module module, Stream output);
// Get supported standard library types
string[] GetSupportedTypes();
// Optional: Optimize for target
Module Optimize(Module module);
}- Type mapping - Map ObjectIR types to target types
- Instruction translation - Convert IR instructions to target code
- Control flow - Handle if/while/try structures
- Calling conventions - Match target ABI
- Standard library - Provide runtime support for List, etc.
- Memory management - Handle GC, reference counting, or manual
ObjectIR Module
↓
Type Mapping
ClassDefinition → TypeBuilder
MethodDefinition → MethodBuilder
↓
Instruction Translation
ldarg → IL.Emit(OpCodes.Ldarg)
if → IL.Emit(OpCodes.Br), labels
↓
Assembly Generation
Save to .dll or .exe
ObjectIR Module
↓
Type Mapping
ClassDefinition → class declaration
List<T> → std::vector<T>
↓
Instruction Translation
ldarg → variable access
add → + operator
if → if statement (preserve structure)
↓
Code Generation
Generate .h and .cpp files
Add standard library wrappers
IR Input
↓
┌────────────────────┐
│ Validation Pass │ ← Check type safety, stack balance
└────────────────────┘
↓
┌────────────────────┐
│ Constant Folding │ ← Evaluate constants at compile time
└────────────────────┘
↓
┌────────────────────┐
│ Dead Code Elim │ ← Remove unreachable code
└────────────────────┘
↓
┌────────────────────┐
│ Inlining │ ← Inline small methods
└────────────────────┘
↓
┌────────────────────┐
│ Backend Specific │ ← Target-specific optimizations
└────────────────────┘
↓
Optimized IR
ObjectIR is abstract about memory management:
- Reference types (classes) are assumed to be heap-allocated
- Value types (structs) can be stack or heap allocated
- Garbage collection is assumed for references (backends without GC must provide alternative)
Backends handle memory differently:
- C#/Java: Use native GC
- C++: Smart pointers (
shared_ptr,unique_ptr) - JavaScript: Use native GC
- Lua: Use native GC
ObjectIR supports reified generics (like C#, not Java):
class Container<T> {
field items: List<T>
method Add(item: T) -> void {
ldarg this
ldfld Container<T>.items
ldarg item
callvirt List<T>.Add(T) -> void
ret
}
}
At runtime, Container<int32> and Container<string> are distinct types.
Backend strategies:
- C#/Java: Native generic support
- C++: Template instantiation (monomorphization)
- JavaScript: Type erasure with runtime checks
try {
// Code that might throw
} catch (ExceptionType ex) {
// Handle exception
} finally {
// Always execute
}
Maps to:
- C#: Native try-catch-finally
- C++: try-catch (finally via RAII)
- JavaScript: try-catch-finally
- Java: try-catch-finally
ObjectIR supports custom metadata on types and members:
class MyClass {
[Serializable]
[Description("User data")]
field userData: string
}Metadata is preserved through compilation and available via reflection.
ObjectIR has both binary and text formats:
Text format (human-readable):
module MyApp
class Calculator {
field value: int32
method Add(x: int32) -> void {
ldarg this
ldarg x
add
stfld value
ret
}
}
Binary format (compact, fast to parse):
- Magic number:
0x4F424952("OBIR") - Version header
- Type table
- Method bodies (compressed instructions)
ObjectIR supports debugging through:
- Line numbers - Instructions track source location
- Local variable names - Preserved for debugging
- Debug symbols - Generate PDB (Windows) or DWARF (Unix)
- Source maps - For JavaScript backend
[External("System.Math")]
class Math {
static method Sqrt(x: float64) -> float64
}
[DllImport("user32.dll")]
static method MessageBox(hwnd: int32, text: string, caption: string, type: int32) -> int32
- Compact instruction encoding
- Shared string table for identifiers
- Type reference deduplication
- Parallel method compilation
- Incremental compilation support
- Cached type resolution
- Inline small methods
- Optimize hot paths
- Backend-specific optimizations
- Custom instructions - Add backend-specific hints
- Optimization passes - Plug in custom optimizers
- Backends - Implement
IBackendinterface - Standard library extensions - Add new generic types
- Metadata attributes - Custom attributes for tools
- All type operations validated
- No uninitialized memory access
- Bounds checking on arrays
- Backends can restrict:
- File system access
- Network access
- System calls
- Stack depth limits
- Loop bound analysis (optional)
- Resource usage tracking
| Feature | ObjectIR | LLVM IR | CIL | JVM Bytecode |
|---|---|---|---|---|
| Level | High (OOP) | Low | Medium | Medium |
| Control Flow | Structured | CFG | Stack+Labels | Stack+Labels |
| Generics | Reified | No | Reified | Erased |
| OOP | Native | Manual | Native | Native |
| Multi-target | Yes | Via backends | .NET only | JVM only |
| Readability | High | Medium | Low | Low |
- Text format parser
- Basic optimizer
- C# backend (CIL emission)
- JavaScript backend
- LLVM backend for native compilation
- JIT compilation support
- Advanced optimizations (loop unrolling, etc.)
- WebAssembly backend
- Async/await support
- SIMD instructions
- GPU compute kernels
- Formal verification tools
- Interactive debugger
See CONTRIBUTING.md for guidelines on:
- Adding backends
- Writing optimization passes
- Extending the standard library
- Documentation improvements