GitFOSS
chore(arch): make it build/load kernel when using kernel.cr (crystal) too
+ 1984
- 70
@@ -1,57 +0,0 @@
-#!/bin/sh
-set -e
-
-# build.sh - builds the TinyCrystalOS prototype (kernel + ISO)
-# Place this at the project root (same dir as Makefile). Run: chmod +x build.sh && ./build.sh
-
-# Ensure required tools
-echo "Checking toolchain..."
-for cmd in gcc ld objcopy grub-mkrescue qemu-system-x86_64; do
-  if ! command -v "$cmd" >/dev/null 2>&1; then
-    echo "Warning: $cmd not found."
-  fi
-done
-
-# Build kernel and modules via Makefile
-echo "Building project using Makefile..."
-if [ ! -f Makefile ]; then
-  echo "Makefile not found in current directory."
-  exit 1
-fi
-
-make clean || true
-if ! make; then
-  echo "Build failed."
-  exit 1
-fi
-
-# Optionally create ISO if grub-mkrescue exists
-if command -v grub-mkrescue >/dev/null 2>&1; then
-  echo "Creating ISO with grub-mkrescue..."
-  # Ensure iso/boot/grub/grub.cfg exists (Makefile should have created it)
-  if [ ! -f iso/boot/grub/grub.cfg ]; then
-    echo "GRUB config not found, creating default grub.cfg..."
-    mkdir -p iso/boot/grub
-    cat > iso/boot/grub/grub.cfg <<'EOF'
-set timeout=0
-set default=0
-menuentry "CrystalOS" {
-  multiboot2 /boot/kernel.bin
-}
-EOF
-  fi
-  grub-mkrescue -o tinyos.iso iso || { echo "grub-mkrescue failed"; exit 1; }
-  echo "ISO created: tinyos.iso"
-else
-  echo "grub-mkrescue not available; skip ISO creation. kernel.bin produced."
-fi
-
-# Optionally create a compressed archive of the build
-ARCHIVE="tinycrystalos-build.tar.gz"
-echo "Packaging build artifacts into $ARCHIVE..."
-tar -czf "$ARCHIVE" kernel.bin kernel/*.o modules/*.o modules/*.c kernel/*.c kernel/*.S kernel/linker.ld || true
-echo "Packaged build artifacts."
-
-echo "Done."
-echo "To run in QEMU (if tinyos.iso was created):"
-echo "  qemu-system-x86_64 -cdrom tinyos.iso -m 512"

@@ -2,12 +2,12 @@
 
 # Flags
 
-kernelCompileFlags="-ffreestanding -nostdinc -nostdinc++ \
+CC_FLAGS="-ffreestanding -nostdinc -nostdinc++ \
 					-Wall -Wextra \
 					-o ./bin/kernel.bin -target i386-pc-none-elf \
 					-I ./kernel/"
-kernelLinkFlags="-nostdlib -Wl,--oformat=binary,-T./kernel/linker.ld"
-kernelFiles="./kernel/unityBuild.cpp ./build/kernelEntryPoint.o"
+LD_FLAGS="-nostdlib -Wl,--oformat=binary,-T./kernel/linker.ld"
+CPP_FILES="./kernel/unityBuild.cpp ./build/kernelEntryPoint.o"
 
 # Build
 

...
@@ -24,11 +24,11 @@ use_crystal_kernel=true
 
 if [ "$use_crystal_kernel" = true ]
 then
-	crystal build --no-debug -Dkernel --cross-compile --target i386-unknown-linux-elf --prelude=empty -o ./build/kernel ./kernel/kernel.cr
+	crystal build --no-debug -Dkernel --cross-compile --target i386-unknown-linux-elf --prelude=../prelude.cr -o ./build/kernel ./kernel/kernel.cr
 	# The object file should be at ./build/kernel.o, now link it with the entry point
 	ld -m elf_i386 -nostdlib -T ./kernel/linker.ld -o ./bin/kernel.bin ./build/kernelEntryPoint.o ./build/kernel.o || exit 1
 else
-	clang++ $kernelCompileFlags $kernelLinkFlags $kernelFiles || exit 1
+	clang++ $CC_FLAGS $LD_FLAGS $CPP_FILES || exit 1
 fi
 
 cp ./bin/kernel.bin ./iso/boot/kernel.bin || exit 1

@@ -1,5 +1,3 @@
-# require "../minrt/src/prelude"
-
 lib LibBootstrap
   @[Packed]
   struct StartInfo

...
@@ -21,23 +19,45 @@ fun kearly(info_ptr: LibBootstrap::StartInfo*)
   # IDT.init
 end
 
+def clear_screen
+  video_memory = Pointer(UInt8).new(0xB8000)
+  screen_size = 80 * 25
+
+  i = 0
+  while i < screen_size
+    video_memory[2 * i] = ' '.ord.to_u8
+    video_memory[2 * i + 1] = 0x07_u8
+    i += 1
+  end
+end
+
 fun kmain
   # IDT.enable_interrupts
   # kprint "Hello from Fluorite."
 
-  address = 0xb800 # Pointer(UInt8).reinterpret(0xb8000)
+  address = Pointer(UInt8).new(0xb8000)
 
   string = "[CrystalOS Kernel] Hello from Crystal"
-  string_size = 33 # string.size
+  # string_size = 33 # string.size
+
+  # clear_screen
 
   # Loop over each character in the string
-  # [0...string_size];each do |i|
+  # i = 0
+  # while i < string.size
   #   # Write the character
   #   address.value = string[i].to_u8
   #   address += 1
   #   # Write the attribute byte
   #   address.value = 0xA0.to_u8
   #   address += 1
+  #   i += 1
+  # end
+
+  # prevent bootloader to take back
+  # while true
+  #   asm("cli")
+  #   asm("hlt")
   # end
 end
 

@@ -0,0 +1,25 @@
+require "./prelude/atomic"
+require "./prelude/primitives"
+require "./prelude/puts"
+
+# order matters
+require "./prelude/crystal_core/object"
+require "./prelude/crystal_core/pointer"
+require "intrinsics"
+require "comparable"
+require "./prelude/crystal_core/panic"
+require "./prelude/crystal_core/slice"
+require "./prelude/crystal_core/int"
+
+# order not important
+require "annotations"
+require "nil"
+require "./prelude/crystal_core/char"
+require "./prelude/crystal_core/proc"
+require "./prelude/crystal_core/enum"
+require "./prelude/crystal_core/bool"
+require "./prelude/crystal_core/tuple"
+require "./prelude/crystal_core/range"
+require "./prelude/crystal_core/string"
+require "./prelude/crystal_core/reference"
+require "./prelude/crystal_core/static_array"

new file
prelude/atomic.cr
@@ -0,0 +1,248 @@
+# :nodoc:
+struct Atomic(T)
+  # Creates an Atomic with the given initial value.
+  def initialize(@value : T)
+    {% if !T.union? && (T == Char || T < Int::Primitive || T < Enum) %}
+      # Support integer types, enum types, or char (because it's represented as an integer)
+    {% elsif T < Reference || (T.union? && T.union_types.all? { |t| t == Nil || t < Reference }) %}
+      # Support reference types, or union types with only nil or reference types
+    {% else %}
+      {{ raise "Can only create Atomic with primitive integer types, reference types or nilable reference types, not #{T}" }}
+    {% end %}
+  end
+
+  # Compares this atomic's value with *cmp*:
+  #
+  # * if they are equal, sets the value to *new*, and returns `{old_value, true}`
+  # * if they are not equal the value remains the same, and returns `{old_value, false}`
+  #
+  # ```
+  # atomic = Atomic.new(1)
+  # atomic.compare_and_set(2, 3) # => {1, false}
+  # atomic.get                  # => 1
+  #
+  # atomic.compare_and_set(1, 3) # => {1, true}
+  # atomic.get                   # => 3
+  # ```
+  def compare_and_set(cmp : T, new : T) : {T, Bool}
+    # Check if it's a nilable reference type
+    {% if T.union? && T.union_types.all? { |t| t == Nil || t < Reference } %}
+      # If so, use addresses because LLVM < 3.9 doesn't support cmpxchg with pointers
+      address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent)
+      {address == 0 ? nil : Pointer(T).new(address).as(T), success}
+    # Check if it's a reference type
+    {% elsif T < Reference %}
+      # Use addresses again (but this can't return nil)
+      address, success = Ops.cmpxchg(pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(cmp.as(T).object_id), LibC::SizeT.new(new.as(T).object_id), :sequentially_consistent, :sequentially_consistent)
+      {Pointer(T).new(address).as(T), success}
+    {% else %}
+      # Otherwise, this is an integer type
+      Ops.cmpxchg(pointerof(@value), cmp, new, :sequentially_consistent, :sequentially_consistent)
+    {% end %}
+  end
+
+  # Performs `atomic_value += value`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(1)
+  # atomic.add(2) # => 1
+  # atomic.get    # => 3
+  # ```
+  def add(value : T)
+    Ops.atomicrmw(:add, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value -= value`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(9)
+  # atomic.sub(2) # => 9
+  # atomic.get    # => 7
+  # ```
+  def sub(value : T)
+    Ops.atomicrmw(:sub, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value &= value`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.and(3) # => 5
+  # atomic.get    # => 1
+  # ```
+  def and(value : T)
+    Ops.atomicrmw(:and, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value = ~(atomic_value & value)`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.nand(3) # => 5
+  # atomic.get     # => -2
+  # ```
+  def nand(value : T)
+    Ops.atomicrmw(:nand, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value |= value`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.or(2) # => 5
+  # atomic.get   # => 7
+  # ```
+  def or(value : T)
+    Ops.atomicrmw(:or, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value ^= value`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.xor(3) # => 5
+  # atomic.get    # => 6
+  # ```
+  def xor(value : T)
+    Ops.atomicrmw(:xor, pointerof(@value), value, :sequentially_consistent, false)
+  end
+
+  # Performs `atomic_value = max(atomic_value, value)`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.max(3) # => 5
+  # atomic.get    # => 5
+  #
+  # atomic.max(10) # => 5
+  # atomic.get     # => 10
+  # ```
+  def max(value : T)
+    {% if T < Int::Signed %}
+      Ops.atomicrmw(:max, pointerof(@value), value, :sequentially_consistent, false)
+    {% else %}
+      Ops.atomicrmw(:umax, pointerof(@value), value, :sequentially_consistent, false)
+    {% end %}
+  end
+
+  # Performs `atomic_value = min(atomic_value, value)`. Returns the old value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.min(10) # => 5
+  # atomic.get     # => 5
+  #
+  # atomic.min(3) # => 5
+  # atomic.get    # => 3
+  # ```
+  def min(value : T)
+    {% if T < Int::Signed %}
+      Ops.atomicrmw(:min, pointerof(@value), value, :sequentially_consistent, false)
+    {% else %}
+      Ops.atomicrmw(:umin, pointerof(@value), value, :sequentially_consistent, false)
+    {% end %}
+  end
+
+  # Atomically sets this atomic's value to *value*. Returns the **old** value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.swap(10) # => 5
+  # atomic.get      # => 10
+  # ```
+  def swap(value : T)
+    {% if T.union? && T.union_types.all? { |t| t == Nil || t < Reference } || T < Reference %}
+      address = Ops.atomicrmw(:xchg, pointerof(@value).as(LibC::SizeT*), LibC::SizeT.new(value.as(T).object_id), :sequentially_consistent, false)
+      Pointer(T).new(address).as(T)
+    {% else %}
+      Ops.atomicrmw(:xchg, pointerof(@value), value, :sequentially_consistent, false)
+    {% end %}
+  end
+
+  # Atomically sets this atomic's value to *value*. Returns the **new** value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.set(10) # => 10
+  # atomic.get     # => 10
+  # ```
+  def set(value : T)
+    Ops.store(pointerof(@value), value.as(T), :sequentially_consistent, true)
+    value
+  end
+
+  # **Non-atomically** sets this atomic's value to *value*. Returns the **new** value.
+  #
+  # ```
+  # atomic = Atomic.new(5)
+  # atomic.lazy_set(10) # => 10
+  # atomic.get          # => 10
+  # ```
+  def lazy_set(@value : T)
+  end
+
+  # Atomically returns this atomic's value.
+  def get
+    Ops.load(pointerof(@value), :sequentially_consistent, true)
+  end
+
+  # **Non-atomically** returns this atomic's value.
+  def lazy_get
+    @value
+  end
+
+  # :nodoc:
+  module Ops
+    # Defines methods that directly map to LLVM instructions related to atomic operations.
+
+    @[Primitive(:cmpxchg)]
+    def self.cmpxchg(ptr : T*, cmp : T, new : T, success_ordering : Symbol, failure_ordering : Symbol) : {T, Bool} forall T
+    end
+
+    @[Primitive(:atomicrmw)]
+    def self.atomicrmw(op : Symbol, ptr : T*, val : T, ordering : Symbol, singlethread : Bool) : T forall T
+    end
+
+    @[Primitive(:fence)]
+    def self.fence(ordering : Symbol, singlethread : Bool) : Nil
+    end
+
+    @[Primitive(:load_atomic)]
+    def self.load(ptr : T*, ordering : Symbol, volatile : Bool) : T forall T
+    end
+
+    @[Primitive(:store_atomic)]
+    def self.store(ptr : T*, value : T, ordering : Symbol, volatile : Bool) : Nil forall T
+    end
+  end
+end
+
+# An atomic flag, that can be set or not.
+#
+# Concurrency safe. If many fibers try to set the atomic in parallel, only one
+# will succeed.
+#
+# Example:
+# ```
+# flag = Atomic::Flag.new
+# flag.test_and_set # => true
+# flag.test_and_set # => false
+# flag.clear
+# flag.test_and_set # => true
+# ```
+struct Atomic::Flag
+  def initialize
+    @value = 0_u8
+  end
+
+  # Atomically tries to set the flag. Only succeeds and returns `true` if the
+  # flag wasn't previously set; returns `false` otherwise.
+  def test_and_set : Bool
+    Atomic::Ops.atomicrmw(:xchg, pointerof(@value), 1_u8, :sequentially_consistent, false) == 0_u8
+  end
+
+  # Atomically clears the flag.
+  def clear : Nil
+    Atomic::Ops.store(pointerof(@value), 0_u8, :sequentially_consistent, true)
+  end
+end

new file
prelude/crystal_core/array.cr
@@ -0,0 +1,141 @@
+require "./markable.cr"
+
+# A simple dynamic array, see [Crystal's documentation](https://crystal-lang.org/api/0.32.1/Array.html) for more detail.
+class Array(T) 
+  @size = 0
+  @capacity = 0
+  # getter size, capacity
+  # protected setter size
+
+  def size
+    @size
+  end
+
+  def size=(new_size)
+    abort "size must be smaller than capacity" if new_size > @capacity
+    @size = new_size
+  end
+
+  def capacity
+    @capacity
+  end
+
+  @buffer : T* = Pointer(T).null
+
+  def to_unsafe
+    @buffer
+  end
+
+  private def recalculate_capacity
+    @capacity = Allocator.block_size_for_ptr(@buffer) // sizeof(T)
+  end
+
+  private def expand(new_capacity)
+    if @size > new_capacity
+      abort "size must be smaller than capacity"
+    end
+    if @buffer.null?
+      @buffer = Pointer(T).malloc_atomic(new_capacity)
+    else
+      @buffer = @buffer.realloc(new_capacity.to_u64)
+    end
+    recalculate_capacity
+  end
+
+  def initialize(initial_capacity : Int = 0)
+    if initial_capacity > 0
+      @buffer = Pointer(T).malloc_atomic(initial_capacity.to_u64)
+      recalculate_capacity
+    end
+  end
+
+  def self.build(capacity : Int) : self
+    ary = Array(T).new(capacity)
+    ary.write_barrier do
+      ary.size = (yield ary.to_unsafe).to_i
+    end
+    ary
+  end
+
+  def self.new(size : Int, &block : Int32 -> T)
+    Array(T).build(size) do |buffer|
+      size.to_i.times do |i|
+        buffer[i] = yield i
+      end
+      size
+    end
+  end
+
+  def each(&block)
+    @size.times do |i|
+      yield @buffer[i]
+    end
+  end
+
+  def each_with_index(&block)
+    @size.times do |i|
+      yield @buffer[i], i
+    end
+  end
+
+  def reverse_each
+    i = size - 1
+    while i >= 0
+      yield @buffer[i]
+      i -= 1
+    end
+  end
+
+  def clone
+    Array(T).build(size) do |buffer|
+      size.times do |i|
+        buffer[i] = to_unsafe[i]
+      end
+      size
+    end
+  end
+
+  def [](idx : Int)
+    abort "accessing out of bounds!" unless 0 <= idx < @size
+    @buffer[idx]
+  end
+
+  def []?(idx : Int) : T?
+    return nil unless 0 <= idx < @size
+    @buffer[idx]
+  end
+
+  def []=(idx : Int, value : T)
+    abort "accessing out of bounds!" unless 0 <= idx < @size
+    @buffer[idx] = value
+  end
+
+  def push(value : T)
+    write_barrier do
+      if @size < @capacity
+        @buffer[@size] = value
+      else
+        expand(@size + 1)
+        @buffer[@size] = value
+      end
+      @size += 1
+    end
+  end
+
+  def clear
+    write_barrier do
+      @size = 0
+    end
+  end
+
+  @[NoInline]
+  def mark(&block : Void* ->)
+    return if @buffer.null?
+    yield @buffer.as(Void*)
+    {% unless T < Int || T < Struct %}
+      each do |obj|
+        yield obj.as(Void*)
+      end
+    {% end %}
+  end
+end

new file
prelude/crystal_core/bool.cr
@@ -0,0 +1,14 @@
+# :nodoc:
+struct Bool
+  def to_unsafe
+    self ? 1 : 0
+  end
+
+  def to_s(io)
+    if self
+      io.print "true"
+    else
+      io.print "false"
+    end
+  end
+end

new file
prelude/crystal_core/char.cr
@@ -0,0 +1,29 @@
+struct Char
+  def self.new(codepoint : Int)
+    codepoint.unsafe_chr
+  end
+
+  def ord
+    # Intrinsics.convert self, Int32
+    func = self
+    pointerof(func).unsafe_as(Int32)
+  end
+
+  def ==(other : Char)
+    ord == other.ord
+  end
+
+  def to_s(io)
+    io.putc ord.to_u8
+  end
+
+  def to_s
+    String.build(1) do |str|
+      str << self
+    end
+  end
+
+  def to_u8
+    ord.to_u8
+  end
+end

new file
prelude/crystal_core/enum.cr
@@ -0,0 +1,104 @@
+# :nodoc:
+struct Enum
+  def ==(other)
+    value == other.value
+  end
+
+  def !=(other)
+    value != other.value
+  end
+
+  def ===(other)
+    value == other.value
+  end
+
+  def |(other : self)
+    self.class.new(value | other.value)
+  end
+
+  def &(other : self)
+    self.class.new(value & other.value)
+  end
+
+  def ~
+    self.class.new(~value)
+  end
+
+  def includes?(other : self)
+    (value & other.value) != 0
+  end
+
+  # Returns the enum member that has the given value, or `nil` if
+  # no such member exists.
+  #
+  # ```
+  # Color.from_value?(0) # => Color::Red
+  # Color.from_value?(1) # => Color::Green
+  # Color.from_value?(2) # => Color::Blue
+  # Color.from_value?(3) # => nil
+  # ```
+  def self.from_value?(value : Int) : self?
+    {% if @type.annotation(Flags) %}
+      all_mask = {{@type}}::All.value
+      return if all_mask & value != value
+      return new(all_mask.class.new(value))
+    {% else %}
+      {% for member in @type.constants %}
+        return new({{@type.constant(member)}}) if {{@type.constant(member)}} == value
+      {% end %}
+    {% end %}
+    nil
+  end
+
+  # Returns the enum member that has the given value, or raises
+  # if no such member exists.
+  #
+  # ```
+  # Color.from_value(0) # => Color::Red
+  # Color.from_value(1) # => Color::Green
+  # Color.from_value(2) # => Color::Blue
+  # Color.from_value(3) # raises Exception
+  # ```
+  def self.from_value(value : Int) : self
+    from_value?(value) || raise "Unknown enum value"
+  end
+
+  def to_s(io) : Nil
+    {% if @type.annotation(Flags) %}
+      if value == 0
+        io.print "None"
+      else
+        found = false
+        {% for member in @type.constants %}
+          {% if member.stringify != "All" %}
+            if {{@type.constant(member)}} != 0 && value.bits_set? {{@type.constant(member)}}
+              io.print " | " if found
+              io.print {{member.stringify}}
+              found = true
+            end
+          {% end %}
+        {% end %}
+        io.print value unless found
+      end
+    {% else %}
+      io.print to_s
+    {% end %}
+    nil
+  end
+
+  def to_s
+    {% if @type.annotation(Flags) %}
+      value
+    {% else %}
+      # Can't use `case` here because case with duplicate values do
+      # not compile, but enums can have duplicates (such as `enum Foo; FOO = 1; BAR = 1; end`).
+      {% for member, i in @type.constants %}
+        if value == {{@type.constant(member)}}
+          return {{member.stringify}}
+        end
+      {% end %}
+
+      value
+    {% end %}
+  end
+end

new file
prelude/crystal_core/int.cr
@@ -0,0 +1,226 @@
+# :nodoc:
+struct Int
+  alias Signed = Int8 | Int16 | Int32 | Int64 | Int128
+  alias Unsigned = UInt8 | UInt16 | UInt32 | UInt64 | UInt128
+  alias Primitive = Signed | Unsigned
+
+  def times
+    x = 0
+    while x < self
+      yield x
+      x += 1
+    end
+  end
+
+  def //(other)
+    self.unsafe_div other
+  end
+
+  def %(other)
+    self.unsafe_mod other
+  end
+
+  def <<(other)
+    self.unsafe_shl other
+  end
+
+  def >>(other)
+    self.unsafe_shr other
+  end
+
+  def ~
+    self ^ -1
+  end
+
+  def ===(other)
+    self == other
+  end
+
+  def abs
+    self >= 0 ? self : self * -1
+  end
+
+  def div_ceil(other : Int)
+    (self + (other - 1)) // other
+  end
+
+  # bit manips
+  def find_first_zero : Int
+    Intrinsics.counttrailing32(~self.to_i32, true)
+  end
+
+  def nearest_power_of_2
+    n = self - 1
+    while (n & (n - 1)) != 0
+      n = n & (n - 1)
+    end
+    n << 1
+  end
+
+  def lowest_power_of_2
+    x = self
+    x = x | (x >> 1)
+    x = x | (x >> 2)
+    x = x | (x >> 4)
+    x = x | (x >> 8)
+    x = x | (x >> 16)
+    x - (x >> 1)
+  end
+
+  def hash(hasher)
+    hasher.hash self
+  end
+
+  # format
+  private BASE = "0123456789abcdefghijklmnopqrstuvwxyz"
+
+  def each_digit(base = 10, &block)
+    s = uninitialized UInt8[128]
+    sign = self < 0
+    n = self.abs
+    i = 0
+    while i < 128
+      s[i] = BASE.to_unsafe[n % base]
+      i += 1
+      break if (n //= base) == 0
+    end
+    if sign
+      yield '-'.ord.to_u8
+    end
+    i -= 1
+    while true
+      yield s[i]
+      break if i == 0
+      i -= 1
+    end
+  end
+
+  def to_s(base : Int = 10)
+    s = uninitialized UInt8[128]
+    sign = self < 0
+    n = self.abs
+    i = 0
+    while i < 128
+      s[i] = BASE.to_unsafe[n % base]
+      i += 1
+      break if (n //= base) == 0
+    end
+    if sign
+      s[i] = '-'.ord.to_u8
+    else
+      i -= 1
+    end
+    builder = String::Builder.new(i + 1)
+    while true
+      builder.write_byte s[i]
+      break if i == 0
+      i -= 1
+    end
+    builder.to_s
+  end
+
+  def to_s(io, base : Int = 10)
+    each_digit(base) do |ch|
+      io.putc ch
+    end
+  end
+
+  def to_usize
+    self.to_u64
+  end
+
+  def to_isize
+    self.to_i64
+  end
+end
+
+# :nodoc:
+struct Int8
+  def popcount
+    Intrinsics.popcount8 self
+  end
+end
+
+# :nodoc:
+struct UInt8
+  def popcount
+    Intrinsics.popcount8 self
+  end
+end
+
+# :nodoc:
+struct Int16
+  def popcount
+    Intrinsics.popcount16 self
+  end
+end
+
+# :nodoc:
+struct UInt16
+  def popcount
+    Intrinsics.popcount16 self
+  end
+end
+
+# :nodoc:
+struct Int32
+  def popcount
+    Intrinsics.popcount32 self
+  end
+end
+
+# :nodoc:
+struct UInt32
+  def popcount
+    Intrinsics.popcount32 self
+  end
+end
+
+# :nodoc:
+struct Int64
+  def popcount
+    Intrinsics.popcount64 self
+  end
+end
+
+# :nodoc:
+struct UInt64
+  def popcount
+    Intrinsics.popcount64 self
+  end
+end
+
+# :nodoc:
+alias ISize = Int64
+
+# :nodoc:
+alias USize = UInt64
+
+# :nodoc:
+struct Int128
+end
+
+# :nodoc:
+struct UInt128
+end
+
+module Math
+  extend self
+
+  # Returns the minimum between `a` and `b`.
+  def min(a, b)
+    a < b ? a : b
+  end
+
+  # Returns the maximum between `a` and `b`.
+  def max(a, b)
+    a > b ? a : b
+  end
+
+  # Clamps `x` to `min` and `max`.
+  def clamp(x, min, max)
+    return min if x < min
+    return max if x > max
+    x
+  end
+end

new file
prelude/crystal_core/markable.cr
@@ -0,0 +1,9 @@
+# :nodoc:
+class Markable
+  def write_barrier(&block)
+    yield
+  end
+
+  def mark(&block : Void* ->)
+  end
+end

new file
prelude/crystal_core/object.cr
@@ -0,0 +1,90 @@
+class Object
+  def not_nil!
+    self
+  end
+
+  {% for prefixes in { {"", "", "@", "#"}, {"class_", "self.", "@@", "."} } %}
+    {%
+      macro_prefix = prefixes[0].id
+      method_prefix = prefixes[1].id
+      var_prefix = prefixes[2].id
+      doc_prefix = prefixes[3].id
+    %}
+    macro {{macro_prefix}}getter(*names)
+      \{% for name in names %}
+        \{% if name.is_a?(TypeDeclaration) %}
+          def {{method_prefix}}\{{ name.var.id }} : \{{name.type}}
+            {{var_prefix}}\{{ name.var.id }}
+          end
+        \{% else %}
+          def {{method_prefix}}\{{ name.id }}
+            {{var_prefix}}\{{ name.id }}
+          end
+        \{% end %}
+      \{% end %}
+    end
+
+    macro {{macro_prefix}}getter!(*names)
+      \{% for name in names %}
+        \{% if name.is_a?(TypeDeclaration) %}
+          def {{method_prefix}}\{{ name.var.id }} : \{{name.type}}
+            {{var_prefix}}\{{ name.var.id }}.not_nil!
+          end
+        \{% else %}
+          def {{method_prefix}}\{{ name.id }}
+            {{var_prefix}}\{{ name.id }}.not_nil!
+          end
+        \{% end %}
+      \{% end %}
+    end
+
+    macro {{macro_prefix}}setter(*names)
+      \{% for name in names %}
+        def {{method_prefix}}\{{ name.id }}=({{var_prefix}}\{{ name.id }})
+        end
+      \{% end %}
+    end
+
+    macro {{macro_prefix}}property(*names)
+      \{% for name in names %}
+        def {{method_prefix}}\{{ name.id }}
+          {{var_prefix}}\{{ name.id }}
+        end
+        def {{method_prefix}}\{{ name.id }}=({{var_prefix}}\{{ name.id }})
+        end
+      \{% end %}
+    end
+  {% end %}
+
+  def unsafe_as(type : T.class) forall T
+    x = self
+    pointerof(x).as(T*).value
+  end
+
+  def as!(type : T.class) forall T
+    if self.is_a?(T)
+      self.unsafe_as type
+    else
+      abort "invalid type cast!"
+    end
+  end
+end
+
+# :nodoc:
+struct Nil
+  def not_nil!
+    abort "casting nil to not-nil!"
+  end
+
+  def to_s(io)
+    io.print "nil"
+  end
+
+  def ==(other)
+    false
+  end
+
+  def object_id
+    0u64
+  end
+end

new file
prelude/crystal_core/panic.cr
@@ -0,0 +1,33 @@
+def abort(message : String)
+  raise message
+end
+
+def panic(message : String)
+  raise message
+end
+
+def raise(message : String) #: NoReturn
+  # Console.print "\n\n-- fatal exception: ", message, " --\n"
+  # Console.print "-- halting --\n"
+  # Architecture.halt_processor
+  # return NoReturn
+end
+
+@[Raises]
+fun __crystal_raise(unwind_ex : Void*) #: NoReturn
+  # Console.print "\n\n-- fatal exception: raise called --\n"
+  # Console.print "-- halting --\n"
+  # Architecture.halt_processor
+  # return NoReturn
+end
+
+fun __crystal_raise_overflow #: NoReturn
+  # Console.print "\n\n-- fatal exception: overflow occured --\n"
+  # Console.print "-- halting --\n"
+  # Architecture.halt_processor
+  # return NoReturn
+end
+
+fun __crystal_get_exception(unwind_ex : Void*) : UInt64
+  0u64
+end

new file
prelude/crystal_core/pointer.cr
@@ -0,0 +1,49 @@
+struct Pointer(T)
+  def self.null
+    new 0u64
+  end
+
+  def null?
+    address == 0u64
+  end
+
+  def +(other : Int)
+    Pointer(T).new(address + other * sizeof(T))
+  end
+
+  def -(other : Int)
+    Pointer(T).new(address - other * sizeof(T))
+  end
+
+  def -(other : self)
+    (address - other.address) // sizeof(T)
+  end
+
+  def [](offset : Int)
+    (self + offset).value
+  end
+
+  def []=(offset : Int, value : T)
+    (self + offset).value = value
+  end
+
+  def ==(other : self)
+    address == other.address
+  end
+
+  def realloc(new_size : UInt64)
+    Pointer(T).new Intrinsics.realloc(address, new_size * sizeof(T))
+  end
+
+  def memcpy(dest : Pointer, src : Pointer, size : USize)
+    Intrinsics.memcpy dest.address, src.address, size
+  end
+
+  def memcmp(a : Pointer, b : Pointer, size : USize)
+    Intrinsics.memcmp a.address, b.address, size
+  end
+
+  def memset(dest : Pointer, value : UInt8, size : USize)
+    Intrinsics.memset dest.address, value, size
+  end
+end

new file
prelude/crystal_core/proc.cr
@@ -0,0 +1,26 @@
+# :nodoc:
+struct Proc
+  def self.new(pointer : Void*, closure_data : Void*)
+    func = {pointer, closure_data}
+    ptr = pointerof(func).as(self*)
+    ptr.value
+  end
+
+  def pointer
+    internal_representation[0]
+  end
+
+  def closure_data
+    internal_representation[1]
+  end
+
+  def closure?
+    !closure_data.null?
+  end
+
+  private def internal_representation
+    func = self
+    ptr = pointerof(func).as({Void*, Void*}*)
+    ptr.value
+  end
+end

new file
prelude/crystal_core/range.cr
@@ -0,0 +1,19 @@
+# :nodoc:
+struct Range(B, E)
+  def begin
+    @begin
+  end
+
+  def end
+    @end
+  end
+
+  getter exclusive
+
+  def size
+    @end - @begin
+  end
+
+  def initialize(@begin : B, @end : E, @exclusive : Bool = false)
+  end
+end

new file
prelude/crystal_core/reference.cr
@@ -0,0 +1,14 @@
+# :nodoc:
+class Reference
+  def ==(other : self)
+    same?(other)
+  end
+
+  def ==(other)
+    false
+  end
+
+  def same?(other : Reference)
+    object_id == other.object_id
+  end
+end

new file
prelude/crystal_core/slice.cr
@@ -0,0 +1,68 @@
+struct Slice(T)
+  getter size : UInt64
+  @size : UInt64
+
+  def initialize(@buffer : Pointer(T), size : Int)
+    @size = size.to_u64
+  end
+
+  def self.null
+    new Pointer(T).null, 0
+  end
+
+  def null?
+    @buffer.null?
+  end
+
+  def self.malloc(sz : Int)
+    new Pointer(T).malloc(sz.to_u64), sz
+  end
+
+  def self.malloc_atomic(sz : Int)
+    new Pointer(T).malloc_atomic(sz.to_u64), sz
+  end
+
+  def self.mmalloc_a(sz, allocator)
+    new allocator.malloc(sz * sizeof(T)).as(T*), sz
+  end
+
+  def [](idx : Int)
+    return nil if idx >= @size || idx < 0
+    @buffer[idx]
+  end
+
+  @[AlwaysInline]
+  def []=(idx : Int, value : T)
+    return value if idx >= @size || idx < 0
+    @buffer[idx] = value
+  end
+
+  def [](range : Range(Int, Int))
+    raise "Slice: out of range" if range.begin > range.end
+    Slice(T).new(@buffer + range.begin, range.size)
+  end
+
+  def to_unsafe
+    @buffer
+  end
+
+  def each(&block)
+    i = 0
+    while i < @size
+      yield @buffer[i]
+      i += 1
+    end
+  end
+
+  def hash(hasher)
+    hasher.hash self
+  end
+
+  def ==(other : String)
+    other == self
+  end
+
+  def to_s(io)
+    io.print "Slice(", @buffer, " ", @size, ")"
+  end
+end

new file
prelude/crystal_core/slice_write.cr
@@ -0,0 +1,41 @@
+class SliceWriter
+  getter slice, offset
+
+  def initialize(@slice : Slice(UInt8), @skip = -1)
+    @offset = 0
+  end
+
+  def putc(ch)
+    if @skip > 0
+      @skip -= 1
+      return
+    end
+    @slice[@offset] = ch
+    @offset += 1
+  end
+
+  def print(ch : Char)
+    putc(ch.ord.to_u8)
+  end
+
+  def print(ch : Int32)
+    putc(ch.to_u8)
+  end
+
+  def print(str : String)
+    str.each_byte do |ch|
+      putc(ch)
+    end
+  end
+
+  def print(*args)
+    args.each do |arg|
+      arg.to_s self
+    end
+  end
+
+  def <<(other)
+    other.to_s self
+    @offset != @slice.size
+  end
+end

new file
prelude/crystal_core/static_array.cr
@@ -0,0 +1,49 @@
+# :nodoc:
+struct StaticArray(T, N)
+  macro [](*args)
+    %array = uninitialized StaticArray(typeof({{*args}}), {{args.size}})
+    {% for arg, i in args %}
+      %array.to_unsafe[{{i}}] = {{arg}}
+    {% end %}
+    %array
+  end
+
+  def to_unsafe : Pointer(T)
+    pointerof(@buffer)
+  end
+
+  def []=(index : Int, value : T)
+    abort "setting out of bounds!" unless 0 <= index < N
+    to_unsafe[index] = value
+  end
+
+  def [](index : Int)
+    abort "setting out of bounds!" unless 0 <= index < N
+    to_unsafe[index]
+  end
+
+  def []?(index : Int)
+    return nil if index > N
+    to_unsafe[index]
+  end
+
+  def size
+    N
+  end
+
+  def each : Nil
+    {% for i in 0...N %}
+      yield self[{{i}}]
+    {% end %}
+  end
+
+  def each_with_index : Nil
+    {% for i in 0...N %}
+      yield self[{{i}}], {{i}}
+    {% end %}
+  end
+
+  def to_s(io)
+    io.print "StaticArray(", to_unsafe, " ", N, ")"
+  end
+end

new file
prelude/crystal_core/string.cr
@@ -0,0 +1,282 @@
+require "./int.cr"
+require "./pointer.cr"
+
+class String
+  TYPE_ID     = "".crystal_type_id
+  HEADER_SIZE = sizeof({Int32, Int32, Int32})
+
+  class Builder
+    @capacity : Int32 = 0
+    @bytesize : Int32 = 0
+    # getter capacity, bytesize
+
+    def capacity
+      @capacity
+    end
+
+    def bytesize
+      @bytesize
+    end
+
+    def initialize(capacity : Int32)
+      @buffer = Pointer(UInt8).malloc_atomic(capacity.to_u32 + 1 + HEADER_SIZE)
+      @capacity = capacity_for_ptr @buffer
+      @bytesize = 0
+      @finished = false
+    end
+
+    def initialize
+      @capacity = 0
+      @buffer = Pointer(UInt8).null
+      @bytesize = 0
+      @finished = false
+    end
+
+    private def capacity_for_ptr(ptr)
+      (Allocator.block_size_for_ptr(ptr) - HEADER_SIZE).to_i32
+    end
+
+    def buffer
+      @buffer + String::HEADER_SIZE
+    end
+
+    def empty?
+      @bytesize == 0
+    end
+
+    def back(amount : Int)
+      abort "overflow" if amount > @bytesize
+      @bytesize -= amount
+    end
+
+    def write_byte(other : UInt8)
+      if @bytesize == @capacity
+        if @buffer.null?
+          @buffer = Pointer(UInt8).malloc_atomic(5 + HEADER_SIZE)
+          @capacity = capacity_for_ptr @buffer
+        else
+          old_buffer = @buffer
+          old_size = @capacity.to_usize + 1 + HEADER_SIZE
+          @buffer = Pointer(UInt8).malloc_atomic(@bytesize.to_u32 + 1 + HEADER_SIZE)
+          @capacity = capacity_for_ptr @buffer
+          memcpy(@buffer, old_buffer, old_size)
+        end
+      end
+      buffer[@bytesize] = other
+      @bytesize += 1
+    end
+
+    def <<(other : String)
+      other.each_byte do |byte|
+        write_byte byte
+      end
+    end
+
+    def <<(other : Slice(UInt8))
+      other.each do |byte|
+        write_byte byte
+      end
+    end
+
+    def <<(other : Char)
+      if other.ord <= 0xFF
+        write_byte other.ord.to_u8
+      else
+        abort "TODO: support utf-8 for builder"
+      end
+    end
+
+    def putc(other : UInt8)
+      write_byte other
+    end
+
+    def <<(other)
+      other.to_s self
+    end
+
+    def to_s : String
+      abort "Can only invoke 'to_s' once on String::Builder" if @finished
+      @finished = true
+
+      write_byte 0u8
+
+      header = @buffer.as({Int32, Int32, Int32}*)
+      bytesize, length = String.calculate_length(buffer)
+      header.value = {String::TYPE_ID, bytesize, length}
+      @buffer.as(String)
+    end
+
+    def reset
+      @buffer = Pointer(UInt8).null
+      @capacity = 0
+      @bytesize = 0
+      @finished = false
+    end
+
+    def reset(capacity : Int32)
+      @buffer = Pointer(UInt8).malloc_atomic(capacity.to_u32 + 1 + HEADER_SIZE)
+      @capacity = capacity_for_ptr @buffer
+      @bytesize = 0
+      @finished = false
+    end
+  end
+
+  def self.new(capacity : Int)
+    str = Pointer(UInt8).malloc_atomic(capacity.to_u32 + HEADER_SIZE + 1)
+    buffer = str.as(String).to_unsafe
+    bytesize, size = yield buffer
+
+    unless 0 <= bytesize <= capacity
+      return nil
+    end
+
+    buffer[bytesize] = 0_u8
+
+    # TODO: Try to reclaim some memory if capacity is bigger than what was requested
+
+    str_header = str.as({Int32, Int32, Int32}*)
+    str_header.value = {TYPE_ID, bytesize.to_i, size.to_i}
+    str.as(String)
+  end
+
+  def self.new(bytes : Slice(UInt8))
+    (new(bytes.size + 1) { |buffer|
+      memcpy(buffer, bytes.to_unsafe, bytes.size.to_usize)
+      buffer[bytes.size] = 0u8
+      String.calculate_length(buffer)
+    }).not_nil!
+  end
+
+  def self.new(bytes : StaticArray)
+    (new(bytes.size + 1) { |buffer|
+      memcpy(buffer, bytes.to_unsafe, bytes.size.to_usize)
+      buffer[bytes.size] = 0u8
+      String.calculate_length(buffer)
+    }).not_nil!
+  end
+
+  def self.new(bytes : NullTerminatedSlice)
+    String.new Slice(UInt8).new(bytes.to_unsafe, bytes.size)
+  end
+
+  protected def self.calculate_length(buffer : UInt8*)
+    i = 0
+    length = 0
+    until (ch = buffer[i]) == 0u8
+      if ch >= 0b110_00000u8
+        i += 2
+      elsif ch >= 0b1110_0000u8
+        i += 3
+      elsif ch >= 0b11110_000u8
+        i += 4
+      else
+        i += 1
+      end
+      length += 1
+    end
+    {i, length}
+  end
+
+  private def mask_tail_char(ch : UInt8) : UInt32
+    (ch & 0b111111u8).to_u32
+  end
+
+  private def each_unicode_point
+    i = 0
+    points = 0
+    until (ch = to_unsafe[i]) == 0u8
+      point = 0u32
+      if ch >= 0b110_00000u8
+        point = (ch & 0b11111u8).to_u32 << 6 | mask_tail_char(to_unsafe[i + 1])
+        i += 2
+      elsif ch >= 0b1110_0000u8
+        point = (ch & 0b1111u8).to_u32 << 12 |
+                (mask_tail_char(to_unsafe[i + 1]) << 6) |
+                (mask_tail_char(to_unsafe[i + 2]))
+        i += 3
+      elsif ch >= 0b11110_000u8
+        point = (ch & 0b1111u8).to_u32 << 18 |
+                (mask_tail_char(to_unsafe[i + 1]) << 12) |
+                (mask_tail_char(to_unsafe[i + 2]) << 6) |
+                (mask_tail_char(to_unsafe[i + 3]))
+        i += 4
+      else
+        point = ch.to_u32
+        i += 1
+      end
+      yield point.unsafe_chr, points, i
+      points += 1
+    end
+  end
+
+  def size
+    @length
+  end
+
+  def bytesize
+    @bytesize
+  end
+
+  def to_unsafe : UInt8*
+    pointerof(@c)
+  end
+
+  def byte_slice
+    Slice(UInt8).new(to_unsafe, bytesize)
+  end
+
+  def each_char(&block)
+    each_unicode_point do |char|
+      yield char
+    end
+  end
+
+  def each_byte(&block)
+    @bytesize.times do |i|
+      yield to_unsafe[i]
+    end
+  end
+
+  def [](index : Int)
+    each_unicode_point do |char, i|
+      return char if i == index
+    end
+    0.unsafe_chr
+  end
+
+  def ==(other : self)
+    return true if same?(other)
+    return false unless bytesize == other.bytesize
+    memcmp(to_unsafe, other.to_unsafe, bytesize) == 0
+  end
+
+  def ==(other : Slice(UInt8))
+    return false unless bytesize == other.size
+    memcmp(to_unsafe, other.to_unsafe, bytesize) == 0
+  end
+
+  def ===(other)
+    self == other
+  end
+
+  def index(search)
+    each_unicode_point do |char, i|
+      return i if search == char
+    end
+    nil
+  end
+
+  def hash(hasher)
+    hasher.hash self
+  end
+
+  def to_s
+    self
+  end
+
+  def to_s(io)
+    each_byte do |char|
+      io.putc char
+    end
+  end
+end

new file
prelude/crystal_core/tuple.cr
@@ -0,0 +1,44 @@
+# :nodoc:
+struct Tuple
+  def self.new(*args : *T)
+    args
+  end
+
+  def each : Nil
+    {% for i in 0...T.size %}
+      yield self[{{i}}]
+    {% end %}
+  end
+
+  def to_s(io)
+    io.print "Tuple("
+    each do |x|
+      io.print x, ","
+    end
+    io.print ")"
+  end
+
+  def at(index : Int)
+    at(index) { nil }
+  end
+
+  def at(index : Int)
+    index += size if index < 0
+    {% for i in 0...T.size %}
+      return self[{{i}}] if {{i}} == index
+    {% end %}
+    yield
+  end
+
+  def [](index : Int)
+    at(index)
+  end
+
+  def []?(index : Int)
+    at(index) { nil }
+  end
+
+  def size
+    {{T.size}}
+  end
+end

new file
prelude/primitives.cr
@@ -0,0 +1,423 @@
+# Methods defined here are primitives because they either:
+# * can't be expressed in Crystal (need to be expressed in LLVM). For example unary
+#   and binary math operators fall into this category.
+# * should always be inlined with an LLVM instruction for performance reasons, even
+#   in non-release builds. An example of this is `Char#ord`, which could be implemented
+#   in Crystal by assigning `self` to a variable and casting a pointer to it to `Int32`,
+#   and then reading back the value.
+
+# :nodoc:
+class Object
+  # Returns the **runtime** `Class` of an object.
+  #
+  # ```
+  # 1.class       # => Int32
+  # "hello".class # => String
+  # ```
+  #
+  # Compare it with `typeof`, which returns the **compile-time** type of an object:
+  #
+  # ```
+  # random_value = rand # => 0.627423
+  # value = random_value < 0.5 ? 1 : "hello"
+  # value         # => "hello"
+  # value.class   # => String
+  # typeof(value) # => Int32 | String
+  # ```
+  @[Primitive(:class)]
+  def class
+  end
+
+  # :nodoc:
+  @[Primitive(:object_crystal_type_id)]
+  def crystal_type_id : Int32
+  end
+end
+
+class Reference
+  # Returns a `UInt64` that uniquely identifies this object.
+  #
+  # The returned value is the memory address of this object.
+  #
+  # ```
+  # string = "hello"
+  # string.object_id # => 4460249568
+  #
+  # pointer = Pointer(String).new(string.object_id)
+  # string2 = pointer.as(String)
+  # string2.object_id == string.object_id # => true
+  # ```
+  @[Primitive(:object_id)]
+  def object_id : UInt64
+  end
+end
+
+# :nodoc:
+class Class
+  # :nodoc:
+  @[Primitive(:class_crystal_instance_type_id)]
+  def crystal_instance_type_id : Int32
+  end
+end
+
+struct Bool
+  # Returns `true` if `self` is equal to *other*.
+  @[Primitive(:binary)]
+  def ==(other : Bool) : Bool
+  end
+
+  # Returns `true` if `self` is not equal to *other*.
+  @[Primitive(:binary)]
+  def !=(other : Bool) : Bool
+  end
+end
+
+struct Char
+  # Returns the codepoint of this char.
+  #
+  # The codepoint is the integer representation.
+  # The Universal Coded Character Set (UCS) standard, commonly known as Unicode,
+  # assigns names and meanings to numbers, these numbers are called codepoints.
+  #
+  # For values below and including 127 this matches the ASCII codes
+  # and thus its byte representation.
+  #
+  # ```
+  # 'a'.ord      # => 97
+  # '\0'.ord     # => 0
+  # '\u007f'.ord # => 127
+  # '☃'.ord      # => 9731
+  # ```
+  @[Primitive(:cast)]
+  def ord : Int32
+  end
+
+  {% for op, desc in {
+                       "==" => "equal to",
+                       "!=" => "not equal to",
+                       "<"  => "less than",
+                       "<=" => "less than or equal to",
+                       ">"  => "greater than",
+                       ">=" => "greater than or equal to",
+                     } %}
+    # Returns `true` if `self`'s codepoint is {{desc.id}} *other*'s codepoint.
+    @[Primitive(:binary)]
+    def {{op.id}}(other : Char) : Bool
+    end
+  {% end %}
+end
+
+# :nodoc:
+struct Symbol
+  # Returns `true` if `self` is equal to *other*.
+  @[Primitive(:binary)]
+  def ==(other : Symbol) : Bool
+  end
+
+  # Returns `true` if `self` is not equal to *other*.
+  @[Primitive(:binary)]
+  def !=(other : Symbol) : Bool
+  end
+
+  # Returns a unique number for this symbol.
+  @[Primitive(:cast)]
+  def to_i : Int32
+  end
+
+  # Returns the symbol's name as a String.
+  #
+  # ```
+  # :foo.to_s           # => "foo"
+  # :"hello world".to_s # => "hello world"
+  # ```
+  @[Primitive(:symbol_to_s)]
+  def to_s : String
+  end
+end
+
+struct Pointer(T)
+  # Allocates `size * sizeof(T)` bytes from the system's heap initialized
+  # to zero and returns a pointer to the first byte from that memory.
+  # The memory is allocated by the `GC`, so when there are
+  # no pointers to this memory, it will be automatically freed.
+  #
+  # ```
+  # # Allocate memory for an Int32: 4 bytes
+  # ptr = Pointer(Int32).malloc(1_u64)
+  # ptr.value # => 0
+  #
+  # # Allocate memory for 10 Int32: 40 bytes
+  # ptr = Pointer(Int32).malloc(10_u64)
+  # ptr[0] # => 0
+  # # ...
+  # ptr[9] # => 0
+  # ```
+  @[Primitive(:pointer_malloc)]
+  def self.malloc(size : UInt64)
+  end
+
+  # Returns a pointer that points to the given memory address.
+  # This doesn't allocate memory.
+  #
+  # ```
+  # ptr = Pointer(Int32).new(5678_u64)
+  # ptr.address # => 5678
+  # ```
+  @[Primitive(:pointer_new)]
+  def self.new(address : UInt64)
+  end
+
+  # Gets the value pointed by this pointer.
+  #
+  # ```
+  # ptr = Pointer(Int32).malloc(4)
+  # ptr.value = 42
+  # ptr.value # => 42
+  # ```
+  @[Primitive(:pointer_get)]
+  def value : T
+  end
+
+  # Sets the value pointed by this pointer.
+  #
+  # ```
+  # ptr = Pointer(Int32).malloc(4)
+  # ptr.value = 42
+  # ptr.value # => 42
+  # ```
+  @[Primitive(:pointer_set)]
+  def value=(value : T)
+  end
+
+  # Returns the address of this pointer.
+  #
+  # ```
+  # ptr = Pointer(Int32).new(1234)
+  # ptr.address # => 1234
+  # ```
+  @[Primitive(:pointer_address)]
+  def address : UInt64
+  end
+
+  # Tries to change the size of the allocation pointed to by this pointer to *size*,
+  # and returns that pointer.
+  #
+  # Since the space after the end of the block may be in use, realloc may find it
+  # necessary to copy the block to a new address where more free space is available.
+  # The value of realloc is the new address of the block.
+  # If the block needs to be moved, realloc copies the old contents.
+  #
+  # Remember to always assign the value of realloc.
+  #
+  # ```
+  # ptr = Pointer.malloc(4) { |i| i + 1 } # [1, 2, 3, 4]
+  # ptr = ptr.realloc(8_u8)
+  # ptr # [1, 2, 3, 4, 0, 0, 0, 0]
+  # ```
+  @[Primitive(:pointer_realloc)]
+  def realloc(size : UInt64) : self
+  end
+
+  # Returns a new pointer whose address is this pointer's address
+  # incremented by `other * sizeof(T)`.
+  #
+  # ```
+  # ptr = Pointer(Int32).new(1234)
+  # ptr.address # => 1234
+  #
+  # # An Int32 occupies four bytes
+  # ptr2 = ptr + 1_u64
+  # ptr2.address # => 1238
+  # ```
+  @[Primitive(:pointer_add)]
+  def +(offset : Int64) : self
+  end
+
+  # Returns how many T elements are there between this pointer and *other*.
+  # That is, this is `(self.address - other.address) / sizeof(T)`.
+  #
+  # ```
+  # ptr1 = Pointer(Int32).malloc(4)
+  # ptr2 = ptr1 + 2
+  # ptr2 - ptr1 # => 2
+  # ```
+  @[Primitive(:pointer_diff)]
+  def -(other : self) : Int64
+  end
+end
+
+struct Proc
+  # Invokes this `Proc` and returns the result.
+  #
+  # ```
+  # add = ->(x : Int32, y : Int32) { x + y }
+  # add.call(1, 2) # => 3
+  # ```
+  @[Primitive(:proc_call)]
+  @[Raises]
+  def call(*args : *T) : R
+  end
+end
+
+# All `Number` methods are defined on concrete structs (for example `Int32`, `UInt8`, etc.),
+# never on `Number`, `Int` or `Float` because we don't want to handle a primitive for
+# other types that could extend these types (for example `BigInt`): if we do that
+# a compiler crash will happen.
+#
+# A similar logic is applied to method arguments: they are always concrete, to avoid
+# unintentionally handling a `BigInt` and have a crash. We also can't have an argument
+# be a union, because the codegen primitives always consider primitive types,
+# never unions.
+
+{% begin %}
+  {% ints = %w(Int8 Int16 Int32 Int64 Int128 UInt8 UInt16 UInt32 UInt64 UInt128) %}
+  {% floats = %w(Float32 Float64) %}
+  {% nums = %w(Int8 Int16 Int32 Int64 Int128 UInt8 UInt16 UInt32 UInt64 UInt128 Float32 Float64) %}
+  {% binaries = {"+" => "adding", "-" => "subtracting", "*" => "multiplying"} %}
+
+  {% for num in nums %}
+    struct {{num.id}}
+      {% for name, type in {
+                             to_i: Int32, to_u: UInt32, to_f: Float64,
+                             to_i8: Int8, to_i16: Int16, to_i32: Int32, to_i64: Int64, to_i128: Int128,
+                             to_u8: UInt8, to_u16: UInt16, to_u32: UInt32, to_u64: UInt64, to_u128: UInt128,
+                             to_f32: Float32, to_f64: Float64,
+                           } %}
+        # Returns `self` converted to `{{type}}`.
+        # Raises `OverflowError` in case of overflow.
+        @[Primitive(:convert)]
+        @[Raises]
+        def {{name.id}} : {{type}}
+        end
+
+        # Returns `self` converted to `{{type}}`.
+        # In case of overflow a wrapping is performed.
+        # {% if ints.includes?(num) %} a wrapping is performed.
+        # {% elsif type < Int %} the result is undefined.
+        # {% else %} infinity is returned.
+        # {% end %}
+        @[Primitive(:unchecked_convert)]
+        def {{name.id}}! : {{type}}
+        end
+      {% end %}
+
+      {% for num2 in nums %}
+        {% for op, desc in {
+                             "==" => "equal to",
+                             "!=" => "not equal to",
+                             "<"  => "less than",
+                             "<=" => "less than or equal to",
+                             ">"  => "greater than",
+                             ">=" => "greater than or equal to",
+                           } %}
+          # Returns `true` if `self` is {{desc.id}} *other*.
+          @[Primitive(:binary)]
+          def {{op.id}}(other : {{num2.id}}) : Bool
+          end
+        {% end %}
+      {% end %}
+    end
+  {% end %}
+
+  {% for int in ints %}
+    struct {{int.id}}
+      # Returns a `Char` that has the unicode codepoint of `self`,
+      # without checking if this integer is in the range valid for
+      # chars (`0..0xd7ff` and `0xe000..0x10ffff`).
+      #
+      # You should never use this method unless `chr` turns out to
+      # be a bottleneck.
+      #
+      # ```
+      # 97.unsafe_chr # => 'a'
+      # ```
+      @[Primitive(:convert)]
+      def unsafe_chr : Char
+      end
+
+      {% for int2 in ints %}
+        {% for op, desc in binaries %}
+          # Returns the result of {{desc.id}} `self` and *other*.
+          # Raises `OverflowError` in case of overflow.
+          @[Primitive(:binary)]
+          @[Raises]
+          def {{op.id}}(other : {{int2.id}}) : self
+          end
+
+          # Returns the result of {{desc.id}} `self` and *other*.
+          # In case of overflow a wrapping is performed.
+          @[Primitive(:binary)]
+          def &{{op.id}}(other : {{int2.id}}) : self
+          end
+        {% end %}
+
+        # Returns the result of performing a bitwise OR of `self`'s and *other*'s bits.
+        @[Primitive(:binary)]
+        def |(other : {{int2.id}}) : self
+        end
+
+        # Returns the result of performing a bitwise AND of `self`'s and *other*'s bits.
+        @[Primitive(:binary)]
+        def &(other : {{int2.id}}) : self
+        end
+
+        # Returns the result of performing a bitwise XOR of `self`'s and *other*'s bits.
+        @[Primitive(:binary)]
+        def ^(other : {{int2.id}}) : self
+        end
+
+        # :nodoc:
+        @[Primitive(:binary)]
+        def unsafe_shl(other : {{int2.id}}) : self
+        end
+
+        # :nodoc:
+        @[Primitive(:binary)]
+        def unsafe_shr(other : {{int2.id}}) : self
+        end
+
+        # :nodoc:
+        @[Primitive(:binary)]
+        def unsafe_div(other : {{int2.id}}) : self
+        end
+
+        # :nodoc:
+        @[Primitive(:binary)]
+        def unsafe_mod(other : {{int2.id}}) : self
+        end
+      {% end %}
+
+      {% for float in floats %}
+        {% for op, desc in binaries %}
+          # Returns the result of {{desc.id}} `self` and *other*.
+          @[Primitive(:binary)]
+          def {{op.id}}(other : {{float.id}}) : {{float.id}}
+          end
+        {% end %}
+      {% end %}
+    end
+  {% end %}
+
+  {% for float in floats %}
+    struct {{float.id}}
+      {% for num in nums %}
+        {% for op, desc in binaries %}
+          # Returns the result of {{desc.id}} `self` and *other*.
+          @[Primitive(:binary)]
+          def {{op.id}}(other : {{num.id}}) : self
+          end
+        {% end %}
+
+        # Returns the float division of `self` and *other*.
+        @[Primitive(:binary)]
+        def fdiv(other : {{num.id}}) : self
+        end
+      {% end %}
+
+      # Returns the result of division `self` and *other*.
+      @[Primitive(:binary)]
+      def /(other : {{float.id}}) : {{float.id}}
+      end
+    end
+  {% end %}
+{% end %}

new file
prelude/puts.cr
@@ -0,0 +1,20 @@
+fun print_string(str : UInt8*)
+end
+
+def puts(str : String)
+  _str : Pointer(UInt8) = str.to_unsafe
+  video_memory = Pointer(UInt8).new(0xB8000_u64)
+  i = 0
+  ch = _str.value
+  while ch > 0
+    ch = (_str + i).value
+    break if ch == 0
+    (video_memory + (i * 2)).value = ch
+    (video_memory + (i * 2) + 1).value = 0x07_u8
+    i += 1
+  end
+end
+
+# Example usage with a static NUL-terminated string
+message = "[CrystalOS Kernel] Hello from C++\0"
+puts(message)

@@ -1 +0,0 @@
-int main() { return 0; }

file deleted
test_kernel.cr
@@ -1,2 +0,0 @@
-fun kmain
-end