# ============================================================================= # KERNEL.CASM (SYSTEM FILE) VERSION 2026-07-11 # ============================================================================= # You cannot modify this file. It is written using Chombit assembly language # instead of the Hybrix language. Your program starts running at the # @kernel.rom_start label, which initializes the runtime and then calls your # main::start() function. This file also implements some kernel functions. const kernel.constant.ram_region = $10_0000 const kernel.constant.rom_region = $c0_0000 const kernel.constant.io_region = $d0_0000 # These are defined by the compiler: # # const kernel.constant.heap_start_address # const kernel.constant.stack_start_address const io_offset.cpu_event_value = $d0_001c - $d0_0000 const io_offset.stack_guard = $d0_003d - $d0_0000 const io_offset.cpu_fail_handler = $d0_0020 - $d0_0000 const io_offset.cpu_trace_handler = $d0_0024 - $d0_0000 const io_offset.cpu_request_handler = $d0_0028 - $d0_0000 const io_offset.kernel_heap_start_address = $d0_0080 - $d0_0000 const io_offset.kernel_stack_start_address = $d0_0084 - $d0_0000 const io_offset.kernel_stack_end_address = $d0_0088 - $d0_0000 # ----------------------------------------------------------------------------- @kernel.rom_start: # Set up the stack move fp, (kernel.constant.stack_start_address + 4) # i:-4 move sp, fp add sp, 16 # locals # i:-4 GC root for globals block # b:0 temp byte # i:0 temp int # i:4 base address for writes # i:8 temp int # i:12 stack_start_address / end block address # ----- Initialize the kernel data move i:4, xhex (kernel.constant.io_region) # i:4 = $d0_0000 push int (kernel.constant.stack_start_address) pop i:12 store [i:4 + (io_offset.kernel_stack_start_address)], i:12 load i:0, [i:4] # read io::ram_end_address store [i:4 + (io_offset.kernel_stack_end_address)], i:0 push i:0 pop gp store [i:4 + (io_offset.stack_guard)], byte 1 # ----- Initialize CPU event handlers push int @kernel.cpu_fail_handler pop i:8 store [i:4 + (io_offset.cpu_fail_handler)], i:8 push int @kernel.cpu_trace_handler pop i:8 store [i:4 + (io_offset.cpu_trace_handler)], i:8 push int @kernel.cpu_request_handler pop i:8 store [i:4 + (io_offset.cpu_request_handler)], i:8 # ----- Initialize the heap push int (kernel.constant.heap_start_address) pop i:8 store [i:4 + (io_offset.kernel_heap_start_address)], i:8 # Globals block + 4 will be our GC root, which is saved on the stack as i:-4 move i:-4, i:8 add i:-4, 4 gpush i:-4 # Globals block: header byte move i:4, i:8 # i:4 = heap_start_address store [i:4], byte $ac # Globals block: descriptor pointer push int @descriptor.module pop i:8 store trio [i:4 + 1], i:8 # descriptor pointer discards high byte # Free block: header byte load unsigned i:0, trio [i:8] # read globals block size add i:4, i:0 # i:4 = free block store [i:4], byte $f0 # Free block: size add i:12, -8 # i:12 = end block address move i:8, i:12 subtract i:8, i:4 # end block address - free block address store trio [i:4 + 1], i:8 # End block: header byte store [i:12], byte $e0 # End block: live mark store [i:12 + 1], byte $a0 # End block: scan chunk store [i:12 + 2], i:4 # ----- Initialize any "data" declarations push return ip jump @kernel.init_globals add sp, -16 # free locals # Call main.start() push return ip jump @code.main.start fail 16 # 16 = main::start() function has returned fill 4 # ----------------------------------------------------------------------------- # array.copy_from() for dynamic arrays of bytes @kernel.array_1.copy_from: push fp move fp, sp add sp, 16 move i:0, i:-24 move i:4, i:-16 move i:8, i:-12 jump @kernel.array_4.copy_from_ending # ----------------------------------------------------------------------------- # array.copy_from() for dynamic arrays of pairs @kernel.array_2.copy_from: push fp move fp, sp add sp, 16 move i:0, i:-24 shift left i:0, 1 # (multiply by 2) move i:4, i:-16 shift left i:4, 1 move i:8, i:-12 shift left i:8, 1 jump @kernel.array_4.copy_from_ending # ----------------------------------------------------------------------------- # array.copy_from() for dynamic arrays of ints and pointers @kernel.array_4.copy_from: @kernel.array_p.copy_from: push fp move fp, sp add sp, 16 # i:-28 self (target) # i:-24 arg_target_index # i:-20 arg_source # i:-16 arg_source_index # i:-12 arg_num_elements # i:-8 return ip # i:-4 fp # i:0 target_offset # i:4 source_offset # i:8 num_bytes # i:12 temp move i:0, i:-24 shift left i:0, 2 # (multiply by 4) move i:4, i:-16 shift left i:4, 2 move i:8, i:-12 shift left i:8, 2 @kernel.array_4.copy_from_ending: compare i:-24, 0 if less fail 19 # 19 = array index out of bounds compare i:-16, 0 if less fail 19 compare i:-12, 0 if less fail 19 # Fail if target.size - num_elements < target_index load unsigned i:12, trio [i:-28] subtract i:12, i:-12 compare i:12, i:-24 if less fail 19 # Fail if source.size - num_elements < source_index load unsigned i:12, trio [i:-20] subtract i:12, i:-12 compare i:12, i:-16 if less fail 19 # Push target + target_offset + 3 bytes for the "size" trio add i:0, i:-28 add i:0, 3 push i:0 # Push source + source_offset + 3 bytes for the "size" trio add i:4, i:-20 add i:4, 3 push i:4 push i:8 push return ip jump @kernel.kernel.copy_memory_bytes add sp, (-12 -16) pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::copy_memory_bytes(target: int, source: int, num_bytes: int) @kernel.kernel.copy_memory_bytes: push fp move fp, sp add sp, 16 # i:-20 arg_target # i:-16 arg_source # i:-12 arg_num_bytes # i:-8 return ip # i:-4 fp # i:0 temp # i:4 target # i:8 source # i:12 source_end compare i:-12, 0 if zero jump @kernel.kernel.copy_memory_bytes.l_6 if less fail 20 # 20 = function was called with an invalid argument move i:4, i:-20 move i:8, i:-16 compare i:4, i:8 if greater unsigned jump @kernel.kernel.copy_memory_bytes.l_3 # --- copy forwards --- move i:12, i:8 add i:12, i:-12 # Avoid overshooting the end add i:12, -15 # Copy blocks of 16 bytes using an unrolled loop @kernel.kernel.copy_memory_bytes.l_0: compare i:8, i:12 if not less unsigned jump @kernel.kernel.copy_memory_bytes.l_1 load i:0, [i:8] store [i:4], i:0 load i:0, [i:8 + 4] store [i:4 + 4], i:0 load i:0, [i:8 + 8] store [i:4 + 8], i:0 load i:0, [i:8 + 12] store [i:4 + 12], i:0 add i:8, 16 add i:4, 16 jump @kernel.kernel.copy_memory_bytes.l_0 @kernel.kernel.copy_memory_bytes.l_1: # Undo the adjustment add i:12, 15 # Copy the remainder @kernel.kernel.copy_memory_bytes.l_2: compare i:8, i:12 if not less unsigned jump @kernel.kernel.copy_memory_bytes.l_6 load b:0, [i:8] add i:8, 1 store [i:4], b:0 add i:4, 1 jump @kernel.kernel.copy_memory_bytes.l_2 @kernel.kernel.copy_memory_bytes.l_3: # --- copy backwards --- # i:4 target # i:8 source # i:12 source_end # Move source and target to end add i:4, i:-12 add i:8, i:-12 # Avoid overshooting the end move i:12, i:-16 add i:12, 15 # Copy blocks of 16 bytes using an unrolled loop @kernel.kernel.copy_memory_bytes.l_4: compare i:8, i:12 if not greater unsigned jump @kernel.kernel.copy_memory_bytes.l_5 load i:0, [i:8 - 4] store [i:4 - 4], i:0 load i:0, [i:8 - 8] store [i:4 - 8], i:0 load i:0, [i:8 - 12] store [i:4 - 12], i:0 load i:0, [i:8 - 16] store [i:4 - 16], i:0 add i:8, -16 add i:4, -16 jump @kernel.kernel.copy_memory_bytes.l_4 @kernel.kernel.copy_memory_bytes.l_5: # Copy the remainder compare i:8, i:-16 if not greater unsigned jump @kernel.kernel.copy_memory_bytes.l_6 add i:8, -1 load b:0, [i:8] add i:4, -1 store [i:4], b:0 jump @kernel.kernel.copy_memory_bytes.l_5 @kernel.kernel.copy_memory_bytes.l_6: add sp, -16 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::set_memory_bytes(target: int, value: byte, num_bytes: int) @kernel.kernel.set_memory_bytes: push fp move fp, sp add sp, 12 # i:-17 arg_target # b:-13 arg_value # i:-12 arg_num_bytes # i:-8 return ip # i:-4 fp # i:0 target # i:4 target_end # i:8 fourValues compare i:-12, 0 if zero jump @kernel.kernel.set_memory_bytes.l_2 if less fail 20 # 20 = function was called with an invalid argument move i:0, i:-17 move i:4, i:-17 add i:4, i:-12 compare i:-12, 16 if less jump @kernel.kernel.set_memory_bytes.l_1 # Avoid overshooting the end add i:4, -15 # Duplicate arg_value into the four bytes of i:8 move b:8, b:-13 move b:9, b:-13 move b:10, b:-13 move b:11, b:-13 # Set blocks of 16 bytes using an unrolled loop @kernel.kernel.set_memory_bytes.l_0: store [i:0], i:8 store [i:0 + 4], i:8 store [i:0 + 8], i:8 store [i:0 + 12], i:8 add i:0, 16 compare i:0, i:4 if less unsigned jump @kernel.kernel.set_memory_bytes.l_0 # Undo the adjustment add i:4, 15 @kernel.kernel.set_memory_bytes.l_1: compare i:0, i:4 if not less unsigned jump @kernel.kernel.set_memory_bytes.l_2 store [i:0], b:-13 add i:0, 1 jump @kernel.kernel.set_memory_bytes.l_1 @kernel.kernel.set_memory_bytes.l_2: add sp, -12 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::set_memory_pairs(target: int, value: pair, num_pairs: int) @kernel.kernel.set_memory_pairs: push fp move fp, sp add sp, 8 # i:-18 arg_target # p:-14 arg_value # i:-12 arg_num_pairs # i:-8 return ip # i:-4 fp # i:0 target # i:4 target_end compare i:-12, 0 if less fail 20 # 20 = function was called with an invalid argument move i:0, i:-18 move i:4, i:-18 add i:4, i:-12 add i:4, i:-12 @kernel.kernel.set_memory_pairs.l_2: compare i:0, i:4 if not less unsigned jump @kernel.kernel.set_memory_pairs.l_3 store [i:0], p:-14 add i:0, 2 jump @kernel.kernel.set_memory_pairs.l_2 @kernel.kernel.set_memory_pairs.l_3: add sp, -8 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::compare_strings(a: char[], a_size: int, b: char[], b_size: int): int @kernel.kernel.compare_strings: push fp move fp, sp add sp, 14 # i:-28 arg_return # i:-24 arg_a # i:-20 arg_a_size # i:-16 arg_b # i:-12 arg_b_size # i:-8 return ip # i:-4 fp # i:0 a_ptr # i:4 b_ptr # i:8 remaining # b:12 a_byte # b:13 b_byte move i:0, i:-24 add i:0, 3 # skip the array size trio move i:4, i:-16 add i:4, 3 # skip the array size trio # remaining <- min(arg_a_size, arg_b_size) move i:8, i:-20 compare i:-12, i:8 if less move i:8, i:-12 compare i:8, 0 if less fail 20 # 20 = function was called with an invalid argument @kernel.kernel.compare_strings.l_0: compare i:8, 0 if zero jump @kernel.kernel.compare_strings.l_1 load b:12, [i:0] load b:13, [i:4] compare b:12, b:13 if not zero jump @kernel.kernel.compare_strings.l_2 add i:0, 1 add i:4, 1 add i:8, -1 jump @kernel.kernel.compare_strings.l_0 @kernel.kernel.compare_strings.l_1: move i:-28, i:-20 subtract i:-28, i:-12 jump @kernel.kernel.compare_strings.l_3 @kernel.kernel.compare_strings.l_2: convert unsigned i:-28, b:12 convert unsigned i:8, b:13 subtract i:-28, i:8 @kernel.kernel.compare_strings.l_3: add sp, -14 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::index_of_string(s: char[], s_size: int, # pattern: char[], pattern_size: int): int @kernel.kernel.index_of_string: push fp move fp, sp add sp, 30 # i:-28 return # i:-24 s # i:-20 s_size # i:-16 pattern # i:-12 pattern_size # i:-8 return ip # i:-4 fp # i:0 s_ptr # i:4 pattern_ptr # i:8 last_index # i:12 index # i:16 s_scan # i:20 pattern_scan # i:24 remaining # b:28 s_byte # b:29 pattern_byte compare i:-12, 0 if less fail 20 # 20 = function was called with an invalid argument if not zero jump @kernel.kernel.index_of_string.l_0 # If pattern is "", return 0 move i:-28, 0 jump @kernel.kernel.index_of_string.l_6 @kernel.kernel.index_of_string.l_0: compare i:-20, 0 if less fail 20 # 20 = function was called with an invalid argument compare i:-20, i:-12 if not less jump @kernel.kernel.index_of_string.l_1 # If pattern is longer then s, then return -1 move i:-28, -1 jump @kernel.kernel.index_of_string.l_6 @kernel.kernel.index_of_string.l_1: move i:0, i:-24 add i:0, 3 # skip size trio move i:4, i:-16 add i:4, 3 # skip size trio move i:8, i:-20 subtract i:8, i:-12 # last_index = s_size - pattern_size move i:12, 0 # index = 0 @kernel.kernel.index_of_string.l_2: compare i:12, i:8 if greater jump @kernel.kernel.index_of_string.l_5 move i:16, i:0 add i:16, i:12 # s_scan = s_ptr + index move i:20, i:4 # pattern_scan = pattern_ptr move i:24, i:-12 # remaining = pattern_size @kernel.kernel.index_of_string.l_3: compare i:24, 0 if zero jump @kernel.kernel.index_of_string.l_4 load b:28, [i:16] load b:29, [i:20] compare b:28, b:29 if not zero jump @kernel.kernel.index_of_string.l_7 add i:16, 1 add i:20, 1 add i:24, -1 jump @kernel.kernel.index_of_string.l_3 @kernel.kernel.index_of_string.l_4: move i:-28, i:12 jump @kernel.kernel.index_of_string.l_6 @kernel.kernel.index_of_string.l_5: move i:-28, -1 jump @kernel.kernel.index_of_string.l_6 @kernel.kernel.index_of_string.l_6: add sp, -30 pop fp pop ip fill 4 @kernel.kernel.index_of_string.l_7: add i:12, 1 jump @kernel.kernel.index_of_string.l_2 # ----------------------------------------------------------------------------- # malloc() is an internal api called by the compiler. # arg_alloc_size should not include the heap prefix/suffix overhead. # Unlike C++ malloc(), this fails instead of returning null when out of memory. @kernel.malloc: push fp move fp, sp add sp, 8 # i:-16 return value # i:-12 arg_alloc_size # i:-8 return ip # i:-4 fp # i:0 $d0_0000 # i:4 block size (arg_alloc_size + 4) move i:0, xhex (kernel.constant.io_region) # Add 4 bytes for the block prefix (header byte and size trio) move i:4, i:-12 add i:4, 4 # Try allocating push int 0 push i:4 push return ip jump @kernel.sweep_alloc add sp, -4 pop i:-16 compare i:-16, 0 if not zero jump @kernel.malloc.l_0 # Triggering automatic garbage collection trace 255 push return ip jump @kernel.mark_heap # Try allocating again push int 0 push i:4 push return ip jump @kernel.sweep_alloc add sp, -4 pop i:-16 compare i:-16, 0 if zero fail 17 # 17 = not enough memory to create this object @kernel.malloc.l_0: # Successfully allocated; zero the memory # i:-17 arg_target push i:-16 # b:-13 arg_value push byte 0 # i:-12 arg_num_bytes push i:-12 push return ip jump @kernel.kernel.set_memory_bytes add sp, -9 add sp, -8 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # malloc_dynamic_array_X() are internal APIs called by the compiler. # They allocate an array of non-pointers and initialize its array size trio. @kernel.malloc_dynamic_array_1: push fp move fp, sp add sp, 4 move i:0, i:-12 jump @kernel.malloc_dynamic_array_4_ending @kernel.malloc_dynamic_array_2: push fp move fp, sp add sp, 4 move i:0, i:-12 shift left i:0, 1 # multiply by 2 jump @kernel.malloc_dynamic_array_4_ending @kernel.malloc_dynamic_array_4: push fp move fp, sp add sp, 4 move i:0, i:-12 shift left i:0, 2 # multiply by 4 # i:-16 return value # i:-12 arg_num_elements # i:-8 return ip # i:-4 fp # i:0 alloc_size @kernel.malloc_dynamic_array_4_ending: add i:0, 3 # +3 bytes to store the array size trio push int 0 push i:0 push return ip jump @kernel.malloc add sp, -4 pop i:-16 # Write the array size trio store trio [i:-16], i:-12 add sp, -4 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # malloc_static_array_X() are internal APIs called by the compiler. # They allocate an array of non-pointers (with no array size trio). @kernel.malloc_static_array_1: push fp move fp, sp add sp, 4 move i:0, i:-12 jump @kernel.malloc_static_array_4_ending @kernel.malloc_static_array_2: push fp move fp, sp add sp, 4 move i:0, i:-12 shift left i:0, 1 # multiply by 2 jump @kernel.malloc_static_array_4_ending @kernel.malloc_static_array_4: push fp move fp, sp add sp, 4 move i:0, i:-12 shift left i:0, 2 # multiply by 4 # i:-16 return value # i:-12 arg_num_elements # i:-8 return ip # i:-4 fp # i:0 alloc_size @kernel.malloc_static_array_4_ending: push int 0 push i:0 push return ip jump @kernel.malloc add sp, -4 pop i:-16 add sp, -4 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # malloc_dynamic_array_p() is an internal API called by the compiler. # It allocates an array of pointers and initializes its array size trio. @kernel.malloc_dynamic_array_p: push fp move fp, sp add sp, 4 # i:-16 return value # i:-12 arg_num_elements # i:-8 return ip # i:-4 fp # i:0 alloc_size move i:0, i:-12 shift left i:0, 2 # multiply by 4 # Add 6 bytes for the pointer array block suffix. # Do not add 4 bytes for the block prefix, as @kernel.malloc already adds it. add i:0, (3+6) # +3 bytes to store the array size trio push int 0 push i:0 push return ip jump @kernel.malloc add sp, -4 pop i:-16 # Update the header byte $a0->$ad, $b0->$bd load b:0, [i:-16 - 4] or b:0, $0d store [i:-16 - 4], b:0 # Write the array size trio store trio [i:-16], i:-12 add sp, -4 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # malloc_static_array_p() is an internal API called by the compiler. # It allocates an array of pointers (with no array size trio). @kernel.malloc_static_array_p: push fp move fp, sp add sp, 4 # i:-16 return value # i:-12 arg_num_elements # i:-8 return ip # i:-4 fp # i:0 alloc_size move i:0, i:-12 shift left i:0, 2 # multiply by 4 # Add 6 bytes for the pointer array block suffix. # Do not add 4 bytes for the block prefix, as @kernel.malloc already adds it. add i:0, 6 push int 0 push i:0 push return ip jump @kernel.malloc add sp, -4 pop i:-16 # Update the header byte $a0->$a5, $b0->$b5 load b:0, [i:-16 - 4] or b:0, $05 store [i:-16 - 4], b:0 add sp, -4 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # malloc_pointer_class() is an internal API called by the compiler. # It calls malloc() and initializes the block to store a class with pointers. # arg_alloc_size should not include the heap prefix/suffix overhead @kernel.malloc_pointer_class: push fp move fp, sp add sp, 4 # i:-16 return value # i:-12 arg_descriptor # i:-8 return ip # i:-4 fp # Read the alloc size from the descriptor # i:0 = alloc_size load unsigned i:0, trio [i:-12] # Subtract 4 because @kernel.malloc will add it add i:0, -4 push int 0 push i:0 push return ip jump @kernel.malloc add sp, -4 pop i:-16 # Update the header byte $a0->$ac, $b0->$bc load b:0, [i:-16 - 4] add b:0, $0c store [i:-16 - 4], b:0 # Write the descriptor store trio [i:-16 - 3], i:-12 add sp, -4 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # kernel::collect_garbage() @kernel.kernel.collect_garbage: push fp move fp, sp add sp, 4 # Try to allocate an impossible object, which forces a full sweep # and then returns null move i:0, xhex $7fff_ffff push int 0 push i:0 push return ip jump @kernel.sweep_alloc add sp, -8 # Triggering manual garbage collection trace 254 # then mark the garbage push return ip jump @kernel.mark_heap add sp, -4 pop fp pop ip @kernel.cpu_request_handler: move fp, $d0_0000 compare i:28, 0 # $d0_001c = io::cpu_event_value if zero jump @kernel.cpu_trace_handler # ignore request 0 ("break") @kernel.cpu_fail_handler: move fp, $d0_0000 move i:144, sp move b:18, 0 # $d0_0012 = io::irq_wake_mask move b:61, 0 # $d0_003d = io::stack_guard # Point sp at the console move i:148, xhex $10_0000 add i:148, 66 move sp, i:148 push pair $44b7 # {:b } push int $c446_c441 # fa push int $c449_c44c # il push int $0484_00ac # > {cat} move sp, fp add sp, $0301 # $d0_0301 = io::matte_color push byte 4 add sp, 2 # $d0_0304 = io::paint_mode push byte 0 # Restore these registers for the debugger move sp, i:144 push i:24 # $d0_0018 = io::cpu_event_fp pop fp # Sleep forever (since we cleared io::irq_wake_mask above) sleep @kernel.cpu_trace_handler: move fp, $d0_0000 push i:20 # $d0_0014 = io::cpu_event_ip push i:24 # $d0_0018 = io::cpu_event_fp pop fp pop ip