# ============================================================================= # GC.CASM (SYSTEM FILE) VERSION 2025-11-29 # ============================================================================= # You cannot modify this file. It is written using Chombit assembly language # instead of the Hybrix language. It implements the Hybrix garbage collector. # ----------------------------------------------------------------------------- # sweep_alloc() is an internal api called by malloc() to try to allocate a # block of the requested size. As it is scanning the heap, it will free any # garbage that was marked by mark_heap(). If allocation fails, then the # return value will be null, and all marked garbage will have been freed. @kernel.sweep_alloc: # i:-16 return value # i:-12 arg_alloc_size # i:-8 return ip # i:-4 fp alias i:-16 = i:kernel.sweep_alloc.arg_return alias i:-12 = i:kernel.sweep_alloc.arg_alloc_size alias i:0 = i:kernel.sweep_alloc.i alias i:4 = i:kernel.sweep_alloc.io_region alias i:8 = i:kernel.sweep_alloc.heap_end_block alias i:12 = i:kernel.sweep_alloc.scan_chunk alias i:16 = i:kernel.sweep_alloc.current_chunk alias i:20 = i:kernel.sweep_alloc.current_block_size alias i:24 = i:kernel.sweep_alloc.current_alloc_size alias i:28 = i:kernel.sweep_alloc.gather_chunk alias i:32 = i:kernel.sweep_alloc.gather_size alias b:36 = b:kernel.sweep_alloc.b alias b:37 = b:kernel.sweep_alloc.live_mark alias b:38 = b:kernel.sweep_alloc.dead_mark alias b:39 = b:kernel.sweep_alloc.coalescing alias b:40 = b:kernel.sweep_alloc.reached_scan_chunk alias b:41 = b:kernel.sweep_alloc.header_byte # ------ push fp move fp, sp add sp, 42 move i:kernel.sweep_alloc.io_region, xhex (kernel.constant.io_region) load i:kernel.sweep_alloc.heap_end_block, [i:kernel.sweep_alloc.io_region + (io_offset.kernel_stack_start_address)] add i:kernel.sweep_alloc.heap_end_block, -8 load b:kernel.sweep_alloc.live_mark, [i:kernel.sweep_alloc.heap_end_block + 1] # If livemark = 0xa0, then deadmark = 0xb0; if livemark = 0xb0, then deadmark = 0xa0 move b:kernel.sweep_alloc.dead_mark, b:kernel.sweep_alloc.live_mark xor b:kernel.sweep_alloc.dead_mark, $10 load i:kernel.sweep_alloc.scan_chunk, [i:kernel.sweep_alloc.heap_end_block + 2] move i:kernel.sweep_alloc.current_chunk, i:kernel.sweep_alloc.scan_chunk move i:kernel.sweep_alloc.gather_chunk, 0 move i:kernel.sweep_alloc.gather_size, 0 move b:kernel.sweep_alloc.coalescing, 0 move b:kernel.sweep_alloc.reached_scan_chunk, 0 load b:kernel.sweep_alloc.header_byte, [i:kernel.sweep_alloc.current_chunk] @kernel.sweep_alloc.l_01_loop: move b:kernel.sweep_alloc.b, b:kernel.sweep_alloc.header_byte and b:kernel.sweep_alloc.b, $f0 compare b:kernel.sweep_alloc.b, b:kernel.sweep_alloc.dead_mark if zero jump @kernel.sweep_alloc.l_02_free_block compare b:kernel.sweep_alloc.header_byte, $f0 if not zero jump @kernel.sweep_alloc.l_03_else @kernel.sweep_alloc.l_02_free_block: # This is a dead/free block compare i:kernel.sweep_alloc.gather_chunk, 0 if zero move i:kernel.sweep_alloc.gather_chunk, i:kernel.sweep_alloc.current_chunk if not zero # We will merge two or more chunks when we reach the next block that is not dead/free move b:kernel.sweep_alloc.coalescing, 1 jump @kernel.sweep_alloc.l_04_if_eo @kernel.sweep_alloc.l_03_else: # We've reached an allocated block. (It cannot possibly be the 0xe0 block.) compare i:kernel.sweep_alloc.gather_chunk, 0 if zero jump @kernel.sweep_alloc.l_04_if_eo compare i:kernel.sweep_alloc.gather_size, i:kernel.sweep_alloc.arg_alloc_size if not less unsigned # We will allocate at gatherchunk jump @kernel.sweep_alloc.l_09_break # Mark gatherchunk as dead/free store [i:kernel.sweep_alloc.gather_chunk], byte $f0 # If multiple chunks were gathered, this coalesces them. # If the freed chunk is a pointer class block, this replaces the descriptor with the block size. store trio [i:kernel.sweep_alloc.gather_chunk + 1], i:kernel.sweep_alloc.gather_size # Resume scanning for a large enough free chunk move i:kernel.sweep_alloc.gather_chunk, 0 move i:kernel.sweep_alloc.gather_size, 0 move b:kernel.sweep_alloc.coalescing, 0 @kernel.sweep_alloc.l_04_if_eo: compare b:kernel.sweep_alloc.header_byte, $e0 if not zero jump @kernel.sweep_alloc.l_05_else_not_e0 # Reached the end, wrap around to the beginning load i:kernel.sweep_alloc.current_chunk, [i:kernel.sweep_alloc.io_region + (io_offset.kernel_heap_start_address)] jump @kernel.sweep_alloc.l_08_finish_loop @kernel.sweep_alloc.l_05_else_not_e0: compare b:kernel.sweep_alloc.reached_scan_chunk, 0 if zero jump @kernel.sweep_alloc.l_06_read_header compare b:kernel.sweep_alloc.coalescing, 0 if not zero jump @kernel.sweep_alloc.l_06_read_header # If the previous scanchunk got coalesced, then we may need to move it to the currentchunk store [i:kernel.sweep_alloc.heap_end_block + 2], i:kernel.sweep_alloc.current_chunk # We completed a full loop without finding a large enough free chunk, so out of memory move i:kernel.sweep_alloc.arg_return, 0 jump @kernel.sweep_alloc.l_11_return @kernel.sweep_alloc.l_06_read_header: load unsigned i:kernel.sweep_alloc.current_alloc_size, trio [i:kernel.sweep_alloc.current_chunk + 1] move b:kernel.sweep_alloc.b, b:kernel.sweep_alloc.header_byte and b:kernel.sweep_alloc.b, $ef # ~ $10 compare b:kernel.sweep_alloc.b, $ac if not zero jump @kernel.sweep_alloc.l_07_advance load unsigned i:kernel.sweep_alloc.current_alloc_size, trio [i:kernel.sweep_alloc.current_alloc_size] @kernel.sweep_alloc.l_07_advance: # The actual block size is rounded up to the nearest multiple of 4. This avoids the need for # a representation of free blocks that are smaller than 4-byte header. (It would not be # difficult to implement tiny free blocks, but research suggests that doing so increases # heap fragmentation. Also, aligning to multiples of 4 makes hex dumps easier to read.) move i:kernel.sweep_alloc.current_block_size, i:kernel.sweep_alloc.current_alloc_size add i:kernel.sweep_alloc.current_block_size, 3 and i:kernel.sweep_alloc.current_block_size, xhex $fffffffc # ~ $3 compare i:kernel.sweep_alloc.gather_chunk, 0 if not zero add i:kernel.sweep_alloc.gather_size, i:kernel.sweep_alloc.current_block_size add i:kernel.sweep_alloc.current_chunk, i:kernel.sweep_alloc.current_block_size @kernel.sweep_alloc.l_08_finish_loop: compare i:kernel.sweep_alloc.current_chunk, i:kernel.sweep_alloc.scan_chunk if zero # We completed a full loop, however we might be coalescing the scanchunk move b:kernel.sweep_alloc.reached_scan_chunk, 1 load b:kernel.sweep_alloc.header_byte, [i:kernel.sweep_alloc.current_chunk] jump @kernel.sweep_alloc.l_01_loop @kernel.sweep_alloc.l_09_break: # Allocate at gatherchunk store [i:kernel.sweep_alloc.gather_chunk], b:kernel.sweep_alloc.live_mark store trio [i:kernel.sweep_alloc.gather_chunk + 1], i:kernel.sweep_alloc.arg_alloc_size # currentchunk will now be the chunk after gatherchunk # Is there any remainder? move i:kernel.sweep_alloc.i, i:kernel.sweep_alloc.arg_alloc_size add i:kernel.sweep_alloc.i, 3 and i:kernel.sweep_alloc.i, xhex $fffffffc # ~ $3 move i:kernel.sweep_alloc.current_block_size, i:kernel.sweep_alloc.gather_size subtract i:kernel.sweep_alloc.current_block_size, i:kernel.sweep_alloc.i if not greater jump @kernel.sweep_alloc.l_10_no_remainder # There is a remainder, so make current_chunk into a free chunk move i:kernel.sweep_alloc.current_chunk, i:kernel.sweep_alloc.gather_chunk add i:kernel.sweep_alloc.current_chunk, i:kernel.sweep_alloc.i store [i:kernel.sweep_alloc.current_chunk], byte $f0 store trio [i:kernel.sweep_alloc.current_chunk + 1], i:kernel.sweep_alloc.current_block_size @kernel.sweep_alloc.l_10_no_remainder: # Update scanchunk to be the next chunk after gatherchunk store [i:kernel.sweep_alloc.heap_end_block + 2], i:kernel.sweep_alloc.current_chunk # Header size is 4 move i:kernel.sweep_alloc.arg_return, i:kernel.sweep_alloc.gather_chunk add i:kernel.sweep_alloc.arg_return, 4 @kernel.sweep_alloc.l_11_return: add sp, -42 pop fp pop ip fill 4 # ----------------------------------------------------------------------------- # mark_heap() is an internal api called by malloc(). It crawls all reachable # pointers, marking their blocks as in use. Then it toggles the live mark, so # that the remaining blocks are now marked as garbage. This assumes that all # blocks were initially marked as reachable or unused, i.e. it assumes that # all previous garbage has been freed by sweep_alloc(). Thus, it's only safe # to call mark_heap() immediately after sweep_alloc() returns null. @kernel.mark_heap: alias i:0 = i:kernel.mark_heap.i alias i:4 = i:kernel.mark_heap.heap_end_block alias i:8 = i:kernel.mark_heap.current_address alias i:12 = i:kernel.mark_heap.current_predecessor_pointer_address alias i:16 = i:kernel.mark_heap.next_visit_address # Only used when current_kind=$00 alias i:20 = i:kernel.mark_heap.current_gp_record # array_length and array_index are also used for visiting classes alias i:24 = i:kernel.mark_heap.array_length alias i:28 = i:kernel.mark_heap.array_index alias i:32 = i:kernel.mark_heap.descriptor_address alias i:36 = i:kernel.mark_heap.gp alias i:40 = i:kernel.mark_heap.io_region alias p:44 = p:kernel.mark_heap.temp alias b:45 = b:kernel.mark_heap.temp alias b:46 = b:kernel.mark_heap.temp2 alias b:47 = b:kernel.mark_heap.current_kind alias b:48 = b:kernel.mark_heap.old_live_mark # ------ push fp move fp, sp add sp, 49 move i:kernel.mark_heap.io_region, xhex (kernel.constant.io_region) load i:kernel.mark_heap.heap_end_block, [i:kernel.mark_heap.io_region + (io_offset.kernel_stack_start_address)] add i:kernel.mark_heap.heap_end_block, -8 load b:kernel.mark_heap.old_live_mark, [i:kernel.mark_heap.heap_end_block + 1] # Flip the live block move b:kernel.mark_heap.temp, b:kernel.mark_heap.old_live_mark xor b:kernel.mark_heap.temp, $10 store [i:kernel.mark_heap.heap_end_block + 1], b:kernel.mark_heap.temp # $00=simple object, $05=static array, $0d=dynamic array, $0c=pointer class, $10=gstack move b:kernel.mark_heap.current_kind, $10 move i:kernel.mark_heap.current_address, 0 move i:kernel.mark_heap.current_predecessor_pointer_address, 0 move i:kernel.mark_heap.next_visit_address, 0 load i:kernel.mark_heap.current_gp_record, [i:kernel.mark_heap.io_region + (io_offset.kernel_stack_end_address)] move i:kernel.mark_heap.array_length, 0 move i:kernel.mark_heap.array_index, 0 move i:kernel.mark_heap.descriptor_address, 0 push gp pop i:kernel.mark_heap.gp @kernel.mark_heap.l_1_loop: # Obtain the next address compare b:kernel.mark_heap.current_kind, $10 if not zero jump @kernel.mark_heap.l_2_else_if_0d # Visiting the top-level gp stack compare i:kernel.mark_heap.current_gp_record, i:kernel.mark_heap.gp if not greater unsigned # Navigating up from the gp stack, so exit the loop jump @kernel.mark_heap.l_12_return # gstack_word_size=3 add i:kernel.mark_heap.current_gp_record, -3 load unsigned i:kernel.mark_heap.next_visit_address, trio [i:kernel.mark_heap.current_gp_record] load i:kernel.mark_heap.next_visit_address, [i:kernel.mark_heap.next_visit_address] jump @kernel.mark_heap.l_5_goto_visit_down @kernel.mark_heap.l_2_else_if_0d: compare b:kernel.mark_heap.current_kind, $0d if not zero jump @kernel.mark_heap.l_3_else_if_05 # Visiting a dynamic pointer array's members compare i:kernel.mark_heap.array_index, i:kernel.mark_heap.array_length if not less # Reached end of the array jump @kernel.mark_heap.l_8_goto_visit_up move i:kernel.mark_heap.i, i:kernel.mark_heap.array_index shift left i:kernel.mark_heap.i, 2 add i:kernel.mark_heap.i, i:kernel.mark_heap.current_address load i:kernel.mark_heap.next_visit_address, [i:kernel.mark_heap.i + 3] # +3 length byte add i:kernel.mark_heap.array_index, 1 store trio [i:kernel.mark_heap.current_predecessor_pointer_address + 3], i:kernel.mark_heap.array_index jump @kernel.mark_heap.l_5_goto_visit_down @kernel.mark_heap.l_3_else_if_05: compare b:kernel.mark_heap.current_kind, $05 if not zero jump @kernel.mark_heap.l_4_else # Visiting a fixed pointer array's members compare i:kernel.mark_heap.array_index, i:kernel.mark_heap.array_length if not less # Reached end of the array jump @kernel.mark_heap.l_8_goto_visit_up move i:kernel.mark_heap.i, i:kernel.mark_heap.array_index shift left i:kernel.mark_heap.i, 2 add i:kernel.mark_heap.i, i:kernel.mark_heap.current_address load i:kernel.mark_heap.next_visit_address, [i:kernel.mark_heap.i] # (no length byte) add i:kernel.mark_heap.array_index, 1 store trio [i:kernel.mark_heap.current_predecessor_pointer_address + 3], i:kernel.mark_heap.array_index jump @kernel.mark_heap.l_5_goto_visit_down @kernel.mark_heap.l_4_else: # Visiting a pointer class's members compare i:kernel.mark_heap.array_index, i:kernel.mark_heap.array_length if not less # Reached end of the fields jump @kernel.mark_heap.l_8_goto_visit_up move i:kernel.mark_heap.i, i:kernel.mark_heap.array_index multiply i:kernel.mark_heap.i, 3 add i:kernel.mark_heap.i, i:kernel.mark_heap.descriptor_address load unsigned i:kernel.mark_heap.i, trio [i:kernel.mark_heap.i + (3 + 2)] # field offset add i:kernel.mark_heap.i, i:kernel.mark_heap.current_address load i:kernel.mark_heap.next_visit_address, [i:kernel.mark_heap.i] add i:kernel.mark_heap.array_index, 1 convert p:kernel.mark_heap.temp, i:kernel.mark_heap.array_index store [i:kernel.mark_heap.current_predecessor_pointer_address + 3], p:kernel.mark_heap.temp @kernel.mark_heap.l_5_goto_visit_down: compare i:kernel.mark_heap.next_visit_address, 0 if zero # Null pointer jump @kernel.mark_heap.l_1_loop move i:kernel.mark_heap.i, i:kernel.mark_heap.next_visit_address shift right unsigned i:kernel.mark_heap.i, 20 # (kernel.constant.ram_region >>> 20) = $1 compare i:kernel.mark_heap.i, $1 if not zero # Not in our memory region jump @kernel.mark_heap.l_1_loop load b:kernel.mark_heap.temp, [i:kernel.mark_heap.next_visit_address - 4] # next header byte move b:kernel.mark_heap.temp2, b:kernel.mark_heap.temp and b:kernel.mark_heap.temp2, $f0 compare b:kernel.mark_heap.temp2, b:kernel.mark_heap.old_live_mark if not zero # Already visited jump @kernel.mark_heap.l_1_loop # Mark it as visited move b:kernel.mark_heap.temp2, b:kernel.mark_heap.temp xor b:kernel.mark_heap.temp2, $10 store [i:kernel.mark_heap.next_visit_address - 4], b:kernel.mark_heap.temp2 # Next kind can be: $00, $05, $0d, $0c and b:kernel.mark_heap.temp, $0f if zero # We don't trace into simple objects. # Preserve b:kernel.mark_heap.current_kind and continue iterating the existing object. jump @kernel.mark_heap.l_1_loop move b:kernel.mark_heap.current_kind, b:kernel.mark_heap.temp compare b:kernel.mark_heap.current_kind, $0d if not zero jump @kernel.mark_heap.l_6_if_05 # Navigated down to a dynamic pointer array # Save currentaddress as the predecessor for this object load unsigned i:kernel.mark_heap.current_predecessor_pointer_address, trio [i:kernel.mark_heap.next_visit_address - 3] # alloc size add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.next_visit_address add i:kernel.mark_heap.current_predecessor_pointer_address, (-4 + -6) # Space optimization: discard the high byte, because it is always $00 store trio [i:kernel.mark_heap.current_predecessor_pointer_address], i:kernel.mark_heap.current_address load unsigned i:kernel.mark_heap.array_length, trio [i:kernel.mark_heap.next_visit_address] move i:kernel.mark_heap.array_index, 0 move i:kernel.mark_heap.current_address, i:kernel.mark_heap.next_visit_address jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_6_if_05: compare b:kernel.mark_heap.current_kind, $05 if not zero jump @kernel.mark_heap.l_7_else # Navigated down to a static pointer array # Save currentaddress as the predecessor for this object load unsigned i:kernel.mark_heap.array_length, trio [i:kernel.mark_heap.next_visit_address - 3] # alloc size add i:kernel.mark_heap.array_length, (-4 + -6) # subtract header and trailer... move i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.array_length add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.next_visit_address # Space optimization: discard the high byte, because it is always $00 store trio [i:kernel.mark_heap.current_predecessor_pointer_address], i:kernel.mark_heap.current_address shift right unsigned i:kernel.mark_heap.array_length, 2 # ...divide by element size move i:kernel.mark_heap.array_index, 0 move i:kernel.mark_heap.current_address, i:kernel.mark_heap.next_visit_address jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_7_else: # Navigated down into a pointer class load unsigned i:kernel.mark_heap.descriptor_address, trio [i:kernel.mark_heap.next_visit_address - 3] # Save currentaddress as the predecessor for this object load unsigned i:kernel.mark_heap.current_predecessor_pointer_address, trio [i:kernel.mark_heap.descriptor_address] # alloc size add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.next_visit_address add i:kernel.mark_heap.current_predecessor_pointer_address, (-4 + -5) # Space optimization: discard the high byte, because it is always $00 store trio [i:kernel.mark_heap.current_predecessor_pointer_address], i:kernel.mark_heap.current_address load p:kernel.mark_heap.temp, [i:kernel.mark_heap.descriptor_address + 3] convert unsigned i:kernel.mark_heap.array_length, p:kernel.mark_heap.temp move i:kernel.mark_heap.array_index, 0 move i:kernel.mark_heap.current_address, i:kernel.mark_heap.next_visit_address jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_8_goto_visit_up: load unsigned i:kernel.mark_heap.current_address, trio [i:kernel.mark_heap.current_predecessor_pointer_address] compare i:kernel.mark_heap.current_address, 0 if not zero jump @kernel.mark_heap.l_9_else # Navigated up to gp move b:kernel.mark_heap.current_kind, $10 jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_9_else: load b:kernel.mark_heap.current_kind, [i:kernel.mark_heap.current_address - 4] and b:kernel.mark_heap.current_kind, $0f compare b:kernel.mark_heap.current_kind, $0d if not zero jump @kernel.mark_heap.l_10_if_05 # Navigated up to a dynamic pointer array load unsigned i:kernel.mark_heap.current_predecessor_pointer_address, trio [i:kernel.mark_heap.current_address - 3] # alloc size add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.current_address add i:kernel.mark_heap.current_predecessor_pointer_address, (-4 + -6) load unsigned i:kernel.mark_heap.array_length, trio [i:kernel.mark_heap.current_address] load unsigned i:kernel.mark_heap.array_index, trio [i:kernel.mark_heap.current_predecessor_pointer_address + 3] jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_10_if_05: compare b:kernel.mark_heap.current_kind, $05 if not zero jump @kernel.mark_heap.l_11_else # Navigated up to a static pointer array load unsigned i:kernel.mark_heap.array_length, trio [i:kernel.mark_heap.current_address - 3] # alloc size add i:kernel.mark_heap.array_length, (-4 + -6) # subtract header and trailer... move i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.array_length add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.current_address shift right unsigned i:kernel.mark_heap.array_length, 2 # ...divide by element size load unsigned i:kernel.mark_heap.array_index, trio [i:kernel.mark_heap.current_predecessor_pointer_address + 3] jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_11_else: compare b:kernel.mark_heap.current_kind, $0c if not zero jump @kernel.mark_heap.l_1_loop # Navigated up to a pointer class load unsigned i:kernel.mark_heap.descriptor_address, trio [i:kernel.mark_heap.current_address - 3] load unsigned i:kernel.mark_heap.current_predecessor_pointer_address, trio [i:kernel.mark_heap.descriptor_address] # alloc size add i:kernel.mark_heap.current_predecessor_pointer_address, i:kernel.mark_heap.current_address add i:kernel.mark_heap.current_predecessor_pointer_address, (-4 + -5) load p:kernel.mark_heap.temp, [i:kernel.mark_heap.descriptor_address + 3] convert unsigned i:kernel.mark_heap.array_length, p:kernel.mark_heap.temp load p:kernel.mark_heap.temp, [i:kernel.mark_heap.current_predecessor_pointer_address + 3] convert unsigned i:kernel.mark_heap.array_index, p:kernel.mark_heap.temp jump @kernel.mark_heap.l_1_loop @kernel.mark_heap.l_12_return: add sp, -49 pop fp pop ip fill 4