///|
struct CodeCache {
// Function index -> compiled function
functions : Map[Int, CompiledFunction]
// Total size of all cached code
mut total_size : Int
// Maximum cache size (0 = unlimited)
max_size : Int
// Cache statistics
mut hits : Int
mut misses : Int
}
///|
pub fn CodeCache::CodeCache(max_size : Int) -> CodeCache {
{ functions: {}, total_size: 0, max_size, hits: 0, misses: 0 }
}
///|
/// Look up a compiled function by index
pub fn CodeCache::get(self : CodeCache, func_idx : Int) -> CompiledFunction? {
match self.functions.get(func_idx) {
Some(func) =>
if func.valid {
self.hits = self.hits + 1
Some(func)
} else {
self.misses = self.misses + 1
None
}
None => {
self.misses = self.misses + 1
None
}
}
}
///|
/// Check if a function is cached and valid
pub fn CodeCache::contains(self : CodeCache, func_idx : Int) -> Bool {
match self.functions.get(func_idx) {
Some(func) => func.valid
None => false
}
}
///|
/// Insert a compiled function into the cache
pub fn CodeCache::insert(
self : CodeCache,
func_idx : Int,
func : CompiledFunction,
) -> Unit {
// Check if we need to evict
if self.max_size > 0 && self.total_size + func.code_size > self.max_size {
self.evict_lru()
}
// Remove old version if exists
if self.functions.get(func_idx) is Some(old) {
self.total_size = self.total_size - old.code_size
}
self.total_size = self.total_size + func.code_size
self.functions.set(func_idx, func)
}
///|
/// Invalidate a cached function
pub fn CodeCache::invalidate(self : CodeCache, func_idx : Int) -> Unit {
if self.functions.get(func_idx) is Some(func) {
func.invalidate()
}
}
///|
/// Remove a function from the cache
pub fn CodeCache::remove(self : CodeCache, func_idx : Int) -> Unit {
if self.functions.get(func_idx) is Some(func) {
self.total_size = self.total_size - func.code_size
self.functions.remove(func_idx)
}
}
///|
/// Clear all cached functions
pub fn CodeCache::clear(self : CodeCache) -> Unit {
self.functions.clear()
self.total_size = 0
}
///|
/// Evict least recently used function (simple strategy: first invalid, then smallest)
fn CodeCache::evict_lru(self : CodeCache) -> Unit {
// First try to evict invalid functions
let mut to_remove : Int? = None
for entry in self.functions {
let (idx, func) = entry
if !func.valid {
to_remove = Some(idx)
break
}
}
if to_remove is Some(idx) {
self.remove(idx)
return
}
// If no invalid functions, evict smallest
let mut smallest_idx : Int? = None
let mut smallest_size = 0x7FFFFFFF // Max int
for entry in self.functions {
let (idx, func) = entry
if func.code_size < smallest_size {
smallest_size = func.code_size
smallest_idx = Some(idx)
}
}
if smallest_idx is Some(idx) {
self.remove(idx)
}
}
///|
/// Get cache statistics
pub fn CodeCache::stats(self : CodeCache) -> (Int, Int, Int, Int) {
// (total_size, num_functions, hits, misses)
let mut num_funcs = 0
for _ in self.functions {
num_funcs = num_funcs + 1
}
(self.total_size, num_funcs, self.hits, self.misses)
}
///|
fn CodeCache::to_string(self : CodeCache) -> String {
let (size, count, hits, misses) = self.stats()
let hit_rate = if hits + misses > 0 {
hits * 100 / (hits + misses)
} else {
0
}
"CodeCache(\{count} functions, \{size} bytes, hit rate \{hit_rate}%)"
}
///|
pub impl Show for CodeCache with fn output(self, logger) {
logger.write_string(self.to_string())
}
// ============ Executable Memory Region ============
///|
/// Represents a region of memory that can hold executable code
/// (Abstraction - actual executable memory requires platform-specific implementation)
struct ExecutableRegion {
// Identifier for this region
id : Int
// Base address (simulated)
base_addr : Int
// Size of the region
size : Int
// Current write position
mut write_pos : Int
// The code bytes stored in this region
code : Array[Int]
// Whether the region is finalized (read-only)
mut finalized : Bool
}
///|
pub fn ExecutableRegion::ExecutableRegion(
id : Int,
size : Int,
) -> ExecutableRegion {
{
id,
base_addr: id * 0x10000, // Simulated base address
size,
write_pos: 0,
code: [],
finalized: false,
}
}
///|
/// Write code bytes to the region
pub fn ExecutableRegion::write(
self : ExecutableRegion,
bytes : Array[Int],
) -> Result[Int, String] {
if self.finalized {
return Err("Region is finalized, cannot write")
}
if self.write_pos + bytes.length() > self.size {
return Err("Region overflow")
}
let offset = self.write_pos
for b in bytes {
self.code.push(b)
}
self.write_pos = self.write_pos + bytes.length()
Ok(offset)
}
///|
/// Finalize the region (make it executable, no more writes)
pub fn ExecutableRegion::finalize(self : ExecutableRegion) -> Unit {
self.finalized = true
}
///|
/// Get the effective address for a given offset
pub fn ExecutableRegion::address_at(
self : ExecutableRegion,
offset : Int,
) -> Int {
self.base_addr + offset
}
///|
/// Get remaining space in the region
pub fn ExecutableRegion::remaining(self : ExecutableRegion) -> Int {
self.size - self.write_pos
}
///|
/// Read code at a given offset
pub fn ExecutableRegion::read_at(
self : ExecutableRegion,
offset : Int,
length : Int,
) -> Array[Int] {
let result : Array[Int] = []
let end = @cmp.minimum(offset + length, self.code.length())
for i in offset.. String {
let status = if self.finalized { "finalized" } else { "writable" }
"ExecutableRegion(\{self.id}, base=0x\{self.base_addr.to_hex()}, \{self.write_pos}/\{self.size} bytes, \{status})"
}
///|
pub impl Show for ExecutableRegion with fn output(self, logger) {
logger.write_string(self.to_string())
}
// ============ Memory Manager ============
///|
/// Manages executable memory regions
struct MemoryManager {
// All allocated regions
regions : Array[ExecutableRegion]
// Default region size
default_region_size : Int
// Next region ID
mut next_id : Int
// Current region for writing
mut current_region : Int?
}
///|
pub fn MemoryManager::MemoryManager(default_region_size : Int) -> MemoryManager {
{ regions: [], default_region_size, next_id: 0, current_region: None }
}
///|
/// Allocate a new executable region
pub fn MemoryManager::alloc_region(
self : MemoryManager,
size : Int,
) -> ExecutableRegion {
let region = ExecutableRegion::ExecutableRegion(self.next_id, size)
self.regions.push(region)
self.next_id = self.next_id + 1
self.current_region = Some(self.regions.length() - 1)
region
}
///|
/// Get the current region, allocating if needed
pub fn MemoryManager::get_current_region(
self : MemoryManager,
) -> ExecutableRegion {
match self.current_region {
Some(idx) => {
let region = self.regions[idx]
if region.finalized || region.remaining() < 256 {
// Need a new region
self.alloc_region(self.default_region_size)
} else {
region
}
}
None => self.alloc_region(self.default_region_size)
}
}
///|
/// Write code to the current region
pub fn MemoryManager::write_code(
self : MemoryManager,
code : @machv_emit.MachineCode,
) -> Result[(ExecutableRegion, Int), String] {
let bytes = code.get_bytes()
let region = self.get_current_region()
// Check if we need a larger region
if bytes.length() > region.remaining() {
// Allocate a new region large enough
let new_size = if bytes.length() > self.default_region_size {
bytes.length() * 2
} else {
self.default_region_size
}
let new_region = self.alloc_region(new_size)
match new_region.write(bytes) {
Ok(offset) => Ok((new_region, offset))
Err(e) => Err(e)
}
} else {
match region.write(bytes) {
Ok(offset) => Ok((region, offset))
Err(e) => Err(e)
}
}
}
///|
/// Finalize all regions
pub fn MemoryManager::finalize_all(self : MemoryManager) -> Unit {
for region in self.regions {
region.finalize()
}
}
///|
/// Get total allocated memory
pub fn MemoryManager::total_allocated(self : MemoryManager) -> Int {
let mut total = 0
for region in self.regions {
total = total + region.size
}
total
}
///|
/// Get total used memory
pub fn MemoryManager::total_used(self : MemoryManager) -> Int {
let mut total = 0
for region in self.regions {
total = total + region.write_pos
}
total
}
///|
fn MemoryManager::to_string(self : MemoryManager) -> String {
let used = self.total_used()
let allocated = self.total_allocated()
"MemoryManager(\{self.regions.length()} regions, \{used}/\{allocated} bytes used)"
}
///|
pub impl Show for MemoryManager with fn output(self, logger) {
logger.write_string(self.to_string())
}
// ============ JIT Runtime ============
///|
/// Complete JIT runtime combining code cache and memory management
struct JITRuntime {
// Code cache
cache : CodeCache
// Memory manager
memory : MemoryManager
// Compilation statistics
mut compilations : Int
mut recompilations : Int
}
///|
pub fn JITRuntime::JITRuntime(
cache_size : Int,
region_size : Int,
) -> JITRuntime {
{
cache: CodeCache(cache_size),
memory: MemoryManager(region_size),
compilations: 0,
recompilations: 0,
}
}
///|
/// Register a compiled function
pub fn JITRuntime::register(
self : JITRuntime,
func_idx : Int,
func : CompiledFunction,
) -> Result[Int, String] {
// Write code to memory
match self.memory.write_code(func.code) {
Ok((region, offset)) => {
// Update compilation stats
if self.cache.contains(func_idx) {
self.recompilations = self.recompilations + 1
} else {
self.compilations = self.compilations + 1
}
// Add to cache
self.cache.insert(func_idx, func)
// Return the effective address
Ok(region.address_at(offset))
}
Err(e) => Err(e)
}
}
///|
/// Look up a compiled function
pub fn JITRuntime::lookup(
self : JITRuntime,
func_idx : Int,
) -> CompiledFunction? {
self.cache.get(func_idx)
}
///|
/// Invalidate a function (will be recompiled on next call)
pub fn JITRuntime::invalidate(self : JITRuntime, func_idx : Int) -> Unit {
self.cache.invalidate(func_idx)
}
///|
/// Clear all compiled code
pub fn JITRuntime::clear(self : JITRuntime) -> Unit {
self.cache.clear()
// Note: Memory regions are not freed, just marked as invalid in cache
}
///|
/// Get runtime statistics
pub fn JITRuntime::stats(
self : JITRuntime,
) -> (Int, Int, Int, Int, Int, Int, Int) {
let (cache_size, cache_count, hits, misses) = self.cache.stats()
let mem_used = self.memory.total_used()
(
self.compilations,
self.recompilations,
cache_count,
cache_size,
hits,
misses,
mem_used,
)
}
///|
fn JITRuntime::to_string(self : JITRuntime) -> String {
let (comps, recomps, count, _, hits, misses, mem_used) = self.stats()
"JITRuntime(compiled=\{comps}, recompiled=\{recomps}, cached=\{count}, hits=\{hits}, misses=\{misses}, memory=\{mem_used})"
}
///|
pub impl Show for JITRuntime with fn output(self, logger) {
logger.write_string(self.to_string())
}
// ============ Helper Functions ============
///|
/// Convert an integer to hex string
fn Int::to_hex(self : Int) -> String {
@types.int_to_hex(self)
}