.cr
Crystal
(text/x-crystal)
# Minimal bump allocator + memset/memcpy for i386 bare-metal Crystal

# Exports C symbols: malloc, free, memset, memcpy, calloc

# 32-bit addresses/sizes (see linker.ld)
HEAP_START = 0x00108000_u32
HEAP_SIZE  = 0x00008000_u32

struct HeapState
  @heap_base : UInt32 = HEAP_START
  @heap_top  : UInt32 = HEAP_START + HEAP_SIZE
  @heap_ptr  : UInt32 = HEAP_START
  @inited    : Bool   = false
  
  def heap_base
    @heap_base
  end
  def heap_base=(v : UInt32)
    @heap_base = v
  end
  
  def heap_top
    @heap_top
  end
  def heap_top=(v : UInt32)
    @heap_top = v
  end
  
  def heap_ptr
    @heap_ptr
  end
  def heap_ptr=(v : UInt32)
    @heap_ptr = v
  end
  
  def inited
    @inited
  end
  def inited=(v : Bool)
    @inited = v
  end
end

# top-level var binding (becomes a global/static)
HEAP_STATE = HeapState.new

module TinyAllocI386
  def self.init
    return if HEAP_STATE.inited
    HEAP_STATE.heap_ptr = HEAP_STATE.heap_base
    HEAP_STATE.inited = true
  end

  def self.align_up32(v : UInt32, align : UInt32) : UInt32
    mask = align - 1_u32
    (v + mask) & ~mask
  end

  def self.malloc32(size : UInt32, align : UInt32 = 8_u32) : Pointer(UInt8)?
    init_if_needed
    return nil if size == 0_u32
    a = align_up32(HEAP_STATE.heap_ptr, align)
    new_ptr = a + size
    if new_ptr > HEAP_STATE.heap_top
      return nil
    end
    HEAP_STATE.heap_ptr = new_ptr
    Pointer(UInt8).new(a.to_u64)
  end

  def self.reset_heap
    HEAP_STATE.heap_ptr = HEAP_STATE.heap_base
  end

  def self.memset_bytes(ptr : Pointer(UInt8), c : UInt8, n : UInt32) : Pointer(UInt8)
    i = 0_u32
    while i < n
      (ptr + i).value = c
      i += 1
    end
    ptr
  end

  def self.memcpy_bytes(dst : Pointer(UInt8), src : Pointer(UInt8), n : UInt32) : Pointer(UInt8)
    i = 0_u32
    while i < n
      (dst + i).value = (src + i).value
      i += 1
    end
    dst
  end

  def self.calloc32(nmemb : UInt32, size : UInt32, align : UInt32 = 8_u32) : Pointer(UInt8)?
    total = nmemb * size
    p = malloc32(total, align)
    return nil if p.nil?
    memset_bytes(p, 0_u8, total)
    p
  end
end

# Expose C symbols via a lib block (no @[Export] needed)
lib CExports
  fun malloc(size : UInt32) : Pointer(Void)
  fun free(ptr : Pointer(Void))
  fun memset(ptr : Pointer(Void), c : Int32, n : UInt32) : Pointer(Void)
  fun memcpy(dest : Pointer(Void), src : Pointer(Void), n : UInt32) : Pointer(Void)
  fun calloc(nmemb : UInt32, size : UInt32) : Pointer(Void)
end

# Providd implementations
def malloc(size : UInt32) : Pointer(Void)
  TinyAllocI386.malloc32(size).as(Pointer(Void))
end

def free(ptr : Pointer(Void))
  # no-op
end

def memset(ptr : Pointer(Void), c : Int32, n : UInt32) : Pointer(Void)
  TinyAllocI386.memset_bytes(Pointer(UInt8).new(ptr.to_i), c.to_u8, n)
  ptr
end

def memcpy(dest : Pointer(Void), src : Pointer(Void), n : UInt32) : Pointer(Void)
  TinyAllocI386.memcpy_bytes(
    Pointer(UInt8).new(dest.to_i),
    Pointer(UInt8).new(src.to_i),
    n
  )
  dest
end

def calloc(nmemb : UInt32, size : UInt32) : Pointer(Void)
  TinyAllocI386.calloc32(nmemb, size).as(Pointer(Void))
end