Options
All
  • Public
  • Public/Protected
  • All
Menu

Module lib/rt.wat

Index

Variables

default: "(module\n ;; Imported functions from the host\n {{USER_IMPORTS}}\n\n ;; Function reference table\n {{USER_TABLE}}\n\n ;; Memory export\n (memory (export \"__memory\") {{PAGES_NEEDED}})\n\n ;; Reference stack pointer\n (global $__ref_sp (mut i32) (i32.const {{STACK_END}}))\n (global $__rv_sp (mut i32) (i32.const {{RV_STACK_END}}))\n\n ;; Push a pointer onto the reference stack\n (func $__ref_stack_push (param $ptr i32)\n ;; Note we decrement for better cache efficiency\n ;; __ref_sp--\n global.get $__ref_sp\n i32.const 4\n i32.sub\n global.set $__ref_sp\n\n ;; *__ref_sp = ptr\n global.get $__ref_sp\n local.get $ptr\n i32.store\n )\n\n ;; Pop a pointer from the top of the reference stack\n (func $__ref_stack_pop (result i32)\n ;; ret = *__ref_sp\n global.get $__ref_sp\n i32.load\n\n ;; __ref_sp++\n global.get $__ref_sp\n i32.const 4\n i32.add\n global.set $__ref_sp\n\n ;; return ret\n )\n\n ;; Instead functions allocate chunks of known sizes based on needs\n ;; ;; Store a reference on the rv stack\n ;; (func $__rv_stack_push (result i32)\n ;; ;; return *(--__rv_sp) = ref_stack_pop()\n\n ;; global.get $__rv_sp\n ;; i32.const 4\n ;; i32.sub\n ;; global.set $__rv_sp\n\n ;; global.get $__rv_sp\n ;; call $__ref_stack_pop\n ;; i32.store\n\n ;; global.get $__ref_sp\n ;; )\n\n ;; ;; Pop N references from the rv stack\n ;; (func $__rv_stack_pop (param $n i32)\n ;; ;; __rv_sp += 4*n\n ;; global.get $__rv_sp\n ;; local.get $n\n ;; i32.const 2\n ;; i32.shl\n ;; i32.add\n ;; global.set $__rv_sp\n ;; )\n\n ;; Initialize static data\n (data (i32.const {{STACK_SIZE}}) \"{{STATIC_DATA_STR}}\")\n\n ;; Initialize nursery head\n ;; (data (i32.const {{NURSERY_SP_INIT}}) \"\\00\\00\\00\\00\" \"\\00\\00\\00\\00\" \"\\00\\00\\00\\00\")\n\n ;; Nursery stack pointer\n (global $__nursery_sp (mut i32) (i32.const {{NURSERY_SP_INIT}}))\n\n ;; Does the given pointer fall within the region of the nursery?\n (func $__in_nursery (param $ptr i32) (result i32)\n ;; OPTIMIZATION single compare, don't need to worry about lhs check\n local.get $ptr\n i32.const {{NURSERY_START}}\n i32.ge_u\n local.get $ptr\n i32.const {{NURSERY_END}}\n i32.lt_u\n i32.and\n )\n\n ;; Heap start\n (global $__heap_start i32\n (i32.const {{HEAP_START}}))\n\n ;; Initialize heap head\n ;; (data\n ;; (i32.const {{HEAP_START}})\n ;; \"\\00\\00\\00\\00\" \"\\00\\00\\00\\00\" \"\\00\\00\\00\\00\")\n\n ;; Initialize last item stored on the heap\n (global $__heap_tail (mut i32) (i32.const {{HEAP_START}}))\n\n ;; Initialize last free space\n (global $__free_head (mut i32) (i32.const {{FREE_START}}))\n (data (i32.const {{FREE_START}}) \"{{INIT_FREE_SIZE_STR}}\")\n\n ;; Allocate an object (w/ preference to nursery)\n ;; Note that size is measured in multiples of 32 bits\n (func $__alloc (param $size i32) (param $ref_bitfield_addr i32) (result i32)\n (local $new_nsp i32)\n\n ;; Check if we can fit it into the nursery\n ;; nursery_sp - (size + sizeof(obj_header)) < START_OF_NURSERY\n global.get $__nursery_sp ;; last allocated object start\n i32.const 12 ;; size of heap object header (3 x i32)\n local.get $size\n i32.const 2\n i32.shl\n i32.add\n i32.sub\n local.tee $new_nsp\n i32.const {{NURSERY_START}} ;; start of nursery\n i32.lt_u\n if\n ;; Item too big to fit into an empty nursery\n local.get $size\n i32.const {{NURSERY_SIZE}}\n i32.const 1\n i32.shr_u ;; 50% of nursery => put it in the heap\n i32.gt_u\n if\n ;; Allocate it onto the heap, not the nursery\n (return (call $__alloc_heap\n (local.get $size)\n (local.get $ref_bitfield_addr)))\n else\n ;; Empty the nursery to make room\n call $__do_gc\n\n local.get $size\n local.get $ref_bitfield_addr\n call $__alloc\n return\n end\n end\n\n ;; Else: Perform allocation\n\n ;; nursery_sp->next = updated_nursery_sp\n global.get $__nursery_sp\n local.get $new_nsp\n i32.store offset=8\n\n ;; Update nsp\n local.get $new_nsp\n global.set $__nursery_sp\n\n ;; Initialize new header\n\n ;; nsp->bitfield = ref_bitfield_addr\n local.get $new_nsp\n local.get $ref_bitfield_addr\n i32.store\n\n ;; nsp->size = size\n local.get $new_nsp\n local.get $size\n i32.store offset=4\n\n ;; nsp->next = NULL\n local.get $new_nsp\n i32.const 0\n i32.store offset=8\n\n ;; Return the start of the payload\n ;; return (nsp + sizeof(header))\n local.get $new_nsp\n i32.const 12\n i32.add\n )\n\n ;; Marks user-provided memory address\n ;; Note this means user cannot have objects stored in static memory\n (func $__mark (param $user_ptr i32)\n ;; Pointer to the start of header object for $user_ptr\n (local $m_ptr i32)\n\n ;; Value of the mark member of header object\n (local $m_mark_size i32) ;; recycled: localized index of bit\n\n ;; Address of the references bitfield\n (local $m_bf_addr i32)\n\n ;; Locals for iterating over the references bitfield\n ;; Note: Initialized to 0\n (local $bit_ind i32) ;; Current Bit in the references bitfield\n (local $bf_cursor i64) ;; Scanned 64bit section of ref bitfield\n (local $local_ind i32) ;; Index within the Scanned 64bit section\n\n ;; check nullptr\n local.get $user_ptr\n i32.eqz\n if\n return\n end\n\n ;; Get mark\n local.get $user_ptr\n i32.const 12\n i32.sub\n local.tee $m_ptr\n i32.load offset=4\n local.set $m_mark_size\n\n ;; If we've already visited, return\n local.get $m_mark_size\n i32.const 0xc0000000\n i32.and\n if\n return\n end\n\n ;; Write mark\n local.get $m_ptr\n local.get $m_mark_size\n i32.const 0xc0000000\n i32.or\n i32.store offset=4\n\n ;; Recursively iterate through bitfield references\n\n ;; Read bf addr\n local.get $m_ptr\n i32.load\n local.tee $m_bf_addr\n i32.eqz\n if ;; No references (optimization)\n return\n end\n\n loop $for_each_bit\n ;; If it's the first bit in an i64\n local.get $bit_ind\n i32.const 64\n i32.rem_u\n local.tee $local_ind\n i32.eqz\n if\n ;; Load the next 64 bits from the bitfield\n local.get $bit_ind\n i32.const 3\n i32.shr_u ;; 64 / 8 *\n local.get $m_bf_addr\n i32.add\n i64.load\n local.set $bf_cursor\n end\n\n ;; If bit indicates a reference\n i64.const 0x1\n local.get $local_ind\n i32.const 7\n i32.xor ;; wasm is little endian\n i64.extend_i32_u\n i64.shl\n local.get $bf_cursor\n i64.and\n i64.eqz\n i32.eqz ;; convert i64 to boolean -> !!\n if\n ;; Mark referenced pointer\n local.get $bit_ind\n i32.const 2\n i32.shl\n local.get $user_ptr\n i32.add\n i32.load\n call $__mark\n end\n\n ;; Do while ++bit_ind < size\n ;; Remember that size is denoted as multiples of 32 bits\n ;; Note that the mark part in the local is always 0b00 (see: return)\n local.get $bit_ind\n i32.const 1\n i32.add\n local.tee $bit_ind\n local.get $m_mark_size\n i32.lt_u\n br_if $for_each_bit\n end\n )\n\n ;; Identical to mark except it ignores pointers which aren't in the nursery\n (func $__minor_mark (param $user_ptr i32)\n ;; Pointer to the start of header object for $user_ptr\n (local $m_ptr i32)\n\n ;; Value of the mark member of header object\n (local $m_mark_size i32) ;; recycled: localized index of bit\n\n ;; Address of the references bitfield\n (local $m_bf_addr i32)\n\n ;; Locals for iterating over the references bitfield\n ;; Note: Initialized to 0\n (local $bit_ind i32) ;; Current Bit in the references bitfield\n (local $bf_cursor i64) ;; Scanned 64bit section of ref bitfield\n (local $local_ind i32) ;; Index within the Scanned 64bit sectio\n\n ;; check nullptr\n local.get $user_ptr\n i32.eqz\n if\n return\n end\n\n ;; Skip pointers not in nursery\n local.get $user_ptr\n call $__in_nursery\n i32.eqz\n if\n return\n end\n\n ;; Get mark\n local.get $user_ptr\n i32.const 12\n i32.sub\n local.tee $m_ptr\n i32.load offset=4\n local.set $m_mark_size\n\n ;; If we've already visited, return\n local.get $m_mark_size\n i32.const 0xc0000000\n i32.and\n if\n return\n end\n\n ;; Write mark\n local.get $m_ptr\n local.get $m_mark_size\n i32.const 0xc0000000\n i32.or\n i32.store offset=4\n\n ;; Recursively iterate through bitfield references\n\n ;; Read bf addr\n local.get $m_ptr\n i32.load\n local.tee $m_bf_addr\n i32.eqz\n if ;; No references (optimization)\n return\n end\n\n loop $for_each_bit\n ;; If it's the first bit in an i64\n local.get $bit_ind\n i32.const 64\n i32.rem_u\n local.tee $local_ind\n i32.eqz\n if\n ;; Load the next 64 bits from the bitfield\n local.get $bit_ind\n i32.const 3\n i32.shr_u ;; 64 / 8 *\n local.get $m_bf_addr\n i32.add\n i64.load\n local.set $bf_cursor\n end\n\n ;; If bit indicates a reference\n i64.const 0x1\n local.get $local_ind\n i32.const 7\n i32.xor ;; wasm is little endian\n i64.extend_i32_u\n i64.shl\n local.get $bf_cursor\n i64.and\n i64.eqz\n i32.eqz ;; convert i64 to boolean -> !!\n if\n ;; Mark referenced pointer (DFS)\n local.get $bit_ind\n i32.const 2\n i32.shl ;; * sizeof(i32)\n local.get $user_ptr\n i32.add\n i32.load\n call $__minor_mark\n end\n\n ;; Do while ++bit_ind < size\n ;; Remember that size is denoted as multiples of 32 bits\n ;; Note that the mark part in the local is always 0b00 (see: return)\n local.get $bit_ind\n i32.const 1\n i32.add\n local.tee $bit_ind\n local.get $m_mark_size\n i32.le_u\n br_if $for_each_bit\n end\n )\n\n ;; Allocate an object onto the heap\n ;; Note this also copies the given mark if included in size-bf\n ;; Note that size is measured in multiples of 32 bits\n (func $__alloc_heap (param $mark_size i32) (param $bf_ptr i32) (result i32)\n (local $free_p i32) ;; current gc_heap_empty_t*\n (local $f_size i32) ;; value of size field in free_p\n (local $f_next i32) ;; pointer to next freespace in free_p\n (local $last_free_p i32) ;; previous value of free_p or 0 if first\n (local $delta i32) ;; multipurpose, difference\n (local $next i32) ;; copy next pointer before overwriting it\n (local $just_size i32) ;; Size without the mark + size of object header\n\n ;; Align to nearest 64 bits (obj header is 96)\n local.get $mark_size\n i32.const 0x1\n i32.or\n i32.const 0x3fffffff\n i32.and\n local.tee $mark_size\n\n ;; Extract size + header\n ;; i32.const 2\n ;; i32.shl ;; * 4 (i32 -> bytes)\n i32.const 3\n i32.add\n local.set $just_size\n\n ;; Start at start of free-list\n global.get $__free_head\n ;; local.tee $last_free_p\n local.set $free_p\n\n loop $next_empty\n ;; (free_v = *free_ptr).size >= just_size\n ;; read freespace header\n local.get $free_p\n i32.load\n local.set $f_size\n local.get $free_p\n i32.load offset=4\n local.set $f_next\n\n ;; Check if it fits\n local.get $just_size\n local.get $f_size\n i32.ge_u\n if ;; Too big for this free space\n ;; if (free_p->next == NULL)\n local.get $f_next\n if ;; check to next node\n ;; last_free_p = free_p\n local.get $free_p\n local.set $last_free_p\n\n ;; free_p = free_p->next\n local.get $f_next\n local.set $free_p\n\n ;; maybe this one is big enough\n br $next_empty\n end\n\n ;; Else: this is the last node\n\n ;; Expand linear memory to fill the gap\n local.get $just_size\n local.get $f_size\n i32.sub\n i32.const 14\n i32.shr_u ;; / ((1024 B/KiB * 64 KiB/page) / 4 B/i32)\n i32.const 1\n i32.add ;; guarantee there's always some free space at the end\n local.tee $delta\n\n memory.grow\n i32.const -1\n i32.eq\n if\n unreachable ;; failed to get more memory -> panic\n end\n\n ;; delta now stores the size of the free space that will remain\n ;; after the object is allocated\n local.get $delta\n i32.const 14\n i32.shl ;; pages -> # i32's\n local.get $f_size\n i32.add\n local.get $just_size\n i32.sub\n local.set $delta\n\n ;; Overwrite free object with our object header\n local.get $free_p\n local.get $bf_ptr\n i32.store\n local.get $free_p\n local.get $mark_size\n i32.store offset=4\n local.get $free_p\n i32.const 0\n i32.store offset=8\n\n ;; Update heap tail\n global.get $__heap_tail\n local.get $free_p\n i32.store offset=8\n local.get $free_p\n global.set $__heap_tail\n\n ;; Make new excess space new free object in list\n ;; *(free_p = free_p + just_size) = gc_heap_empty_t { .size=delta, .next=NULL }\n local.get $free_p\n local.get $just_size\n i32.const 2\n i32.shl ;; * sizeof(i32)\n i32.add\n local.tee $free_p\n local.get $delta\n i32.store\n local.get $free_p\n i32.const 0\n i32.store offset=4\n\n ;; Update last free item entry\n local.get $last_free_p\n if\n ;; new end of list\n local.get $last_free_p\n local.get $free_p\n i32.store offset=4\n else\n ;; new start of list\n local.get $free_p\n global.set $__free_head\n end\n\n global.get $__heap_tail\n i32.const 12\n i32.add\n return\n\n else ;; Small enough for this free space\n ;; Store leftover space into delta\n ;; delta = (free_size - just_size)\n local.get $f_size\n local.get $just_size\n i32.sub\n local.set $delta\n\n ;; Copy next pointer\n ;; next = free_p->next\n local.get $free_p\n i32.load offset=4\n local.set $next\n\n ;; Overwrite free object with our object header\n ;; free_p now refers to heap_object_header_t*\n ;; free_p->refs_bitfield_addr = bf_addr\n ;; free_p->mark_size = mark_size\n ;; free_p->next = 0\n local.get $free_p\n local.get $bf_ptr\n i32.store\n local.get $free_p\n local.get $mark_size\n i32.store offset=4\n local.get $free_p\n i32.const 0\n i32.store offset=8\n\n ;; Add object to LL\n ;; heap_tail->next = free_p\n ;; heap_tail = free_p\n global.get $__heap_tail\n local.get $free_p\n i32.store offset=8\n local.get $free_p\n global.set $__heap_tail\n\n ;; Check leftover space\n local.get $delta\n if\n ;; leftover space\n\n ;; Write new free space head\n ;; (free_p = free_p + just_size * sizeof(i32))->size = delta\n local.get $free_p\n local.get $just_size\n i32.const 2\n i32.shl ;; * sizeof(i32)\n i32.add\n local.tee $free_p\n local.get $delta\n i32.store\n\n ;; free_p->next = next\n local.get $free_p\n local.get $next\n i32.store offset=4\n\n ;; Maintain LL\n local.get $last_free_p\n if\n local.get $last_free_p\n local.get $free_p\n i32.store offset=4\n else\n local.get $free_p\n global.set $__free_head\n end\n else\n ;; no leftover space\n local.get $last_free_p\n if ;; Make previous freespace new free list head\n local.get $last_free_p\n local.get $next\n i32.store offset=4\n else ;; We have exactly populated the heap\n ;; Grow memory to make a new freespace\n ;; Put the new free head at end of lm\n ;; TODO calculate this via free_p instead?\n memory.size\n i32.const 16\n i32.shl ;; * bytes/page\n global.set $__free_head\n\n ;; Extend lm by page (64 KiB)\n i32.const 1\n memory.grow\n i32.const -1\n i32.eq\n if\n unreachable ;; panic: failed\n end\n\n ;; Store the size of new free space\n global.get $__free_head\n i32.const 16384 ;; 1024 B/KiB * 64KiB/page / 4 B/I32\n i32.store\n end\n end\n\n global.get $__heap_tail\n i32.const 12\n i32.add\n return\n end\n end\n unreachable\n )\n\n ;; Similar to memcpy but works in multiples of 32 bits for improved performance\n ;; note that len is in multiples of 32 bits\n ;; could maybe optimize by using a 64 bit read head but eh\n (func $memcpy32 (param $dest i32) (param $src i32) (param $len i32)\n local.get $len\n if\n ;; len = len * 4 + dest\n local.get $dest\n local.get $len\n i32.const 2\n i32.shl\n i32.add\n local.set $len\n\n loop $cp_loop\n ;; *dest = *src\n local.get $dest\n local.get $src\n i32.load\n i32.store\n\n ;; src++; dest++\n local.get $src\n i32.const 4\n i32.add\n local.set $src\n local.get $dest\n i32.const 4\n i32.add\n local.tee $dest\n\n ;; Do while dest < len\n local.get $len\n i32.lt_u\n br_if $cp_loop\n end $cp_loop\n end\n )\n\n ;; increments each time we do gc\n (global $__gc_cycle (mut i32) (i32.const 0))\n\n ;; Collect Garbage\n (; TODO lots of room for optimization for minor gc/nursery\n - don't need next ptr\n ;)\n (func $__do_gc\n (local $p i32) ;; stack pointer\n (local $dest i32) ;; pointer to where object is being moved\n (local $is_major i32) ;; is this gc a major gc?\n (local $mark_size i32) ;; mark+size fields\n (local $bf i32) ;; bitfield addres\n\n ;; Mark\n\n ;; p = end of reference stack\n global.get $__ref_sp\n local.tee $p\n\n ;; If there's nothing to mark, delete everything\n ;; if p == reference stack end && rv_sp == ref vars stack end\n i32.const {{STACK_SIZE}}\n i32.eq\n global.get $__rv_sp\n i32.const {{RV_STACK_END}}\n i32.eq\n i32.and\n if ;; stack is empty -> everything is garbage\n ;; Reset globals\n i32.const {{NURSERY_SP_INIT}}\n global.set $__nursery_sp\n i32.const {{HEAP_START}}\n global.set $__heap_tail\n i32.const {{FREE_START}}\n global.set $__free_head\n\n ;; heap_start->next = null\n i32.const {{HEAP_START}}\n i32.const 0\n i32.store offset=8\n\n ;; free_start->size = (free_start - (memory.size * PAGE_SIZE)) / sizeof(i32)\n i32.const {{FREE_START}}\n memory.size\n i32.const 16\n i32.shl\n i32.const {{FREE_START}}\n i32.sub\n i32.const 2\n i32.shr_u\n i32.store\n\n ;; free_start->next = null\n i32.const {{FREE_START}}\n i32.const 0\n i32.store offset=4\n return\n end\n\n ;; is_major = (++gc_cycle) % GEN_RATIO == 0\n global.get $__gc_cycle\n i32.const 1\n i32.add\n global.set $__gc_cycle\n global.get $__gc_cycle\n i32.const 4 ;; TODO tune this\n i32.rem_u\n i32.eqz\n local.tee $is_major\n\n ;; for each pointer on the references stack\n if\n loop $mark_loop\n ;; mark(*p)\n local.get $p\n i32.load\n call $__mark\n\n ;; do while (++p < end_of_stack)\n local.get $p\n i32.const 4\n i32.add\n local.tee $p\n i32.const {{STACK_END}}\n i32.lt_u\n br_if $mark_loop\n end $mark_loop\n else\n loop $mark_loop\n ;; mark(*p)\n local.get $p\n i32.load\n call $__minor_mark\n\n ;; do while (++p < end_of_stack)\n local.get $p\n i32.const 4\n i32.add\n local.tee $p\n i32.const {{STACK_END}}\n i32.lt_u\n br_if $mark_loop\n end $mark_loop\n end\n\n ;; p = ref var stack pointer\n global.get $__rv_sp\n local.set $p\n\n ;; for each pointer on the ref var stack\n local.get $is_major\n if\n loop $mark_loop2\n ;; mark(*p)\n local.get $p\n i32.load\n call $__mark\n\n ;; do while (++p < end_of_stack)\n local.get $p\n i32.const 4\n i32.add\n local.tee $p\n i32.const {{RV_STACK_END}}\n i32.lt_u\n br_if $mark_loop2\n end $mark_loop2\n else\n loop $mark_loop2\n ;; mark(*p)\n local.get $p\n i32.load\n call $__minor_mark\n\n ;; do while (++p < end_of_stack)\n local.get $p\n i32.const 4\n i32.add\n local.tee $p\n i32.const {{RV_STACK_END}}\n i32.lt_u\n br_if $mark_loop2\n end $mark_loop2\n end\n\n ;; If major gc, sweep the heap before emptying the nursery\n local.get $is_major\n if\n global.get $__heap_start\n local.tee $p\n\n ;; Note that the head object does not get freed and always has size zero\n i32.const 0xc0000000\n i32.store offset=4\n\n ;; $dest is used as $prev in this branch\n ;; it's initialized to null\n\n ;; for each object in the heap\n loop $sweep\n ;; if unmarked\n local.get $p\n i32.load offset=4\n local.tee $mark_size\n i32.const 0xc0000000\n i32.and\n i32.eqz\n if\n ;; Free it\n local.get $p\n local.get $dest\n call $__heap_free\n\n ;; Go next\n local.get $dest\n i32.load offset=8\n local.tee $p\n br_if $sweep\n else\n ;; Remove mark\n local.get $p\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n i32.store offset=4\n end\n\n ;; Do while ((p = (dest = p)->next) != 0)\n local.get $p\n local.tee $dest\n i32.load offset=8\n local.tee $p\n br_if $sweep\n end $sweep\n\n ;; Coalese adjacent free spaces\n call $__coalesce\n end\n\n ;; Move marked stuff from nursery to the main heap\n\n ;; Iterate over items in the nursery\n global.get $__nursery_sp\n local.set $p\n loop $cp_loop\n ;; Load header\n local.get $p\n i32.load\n local.set $bf\n local.get $p\n i32.load offset=4\n local.tee $mark_size\n\n ;; If marked\n i32.const 0xc0000000\n i32.and\n if ;; Copy it into the main heap\n ;; Allocate space on heap\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n local.get $bf\n call $__alloc_heap\n local.set $dest\n\n ;; Store address into the next pointer field\n local.get $p\n local.get $dest\n i32.store offset=8\n\n ;; memcpy user data\n local.get $dest\n\n local.get $p\n i32.const 12\n i32.add\n\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n call $memcpy32\n end\n\n ;; if (p = p + sizeof(obj_header_t) + p->size) < END_OF_NURSERY - sizeof(obj_header_t)\n local.get $p\n i32.const 12\n i32.add\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n i32.const 2\n i32.shl\n i32.add\n local.tee $p\n i32.const {{NURSERY_SP_INIT}}\n i32.lt_u\n br_if $cp_loop\n end $cp_loop\n\n ;; Update references\n\n ;; Update stack refs\n ;; for each pointer on the references stack\n global.get $__ref_sp\n local.set $p\n loop $rsu_loop\n local.get $p\n i32.load\n local.tee $dest\n call $__in_nursery\n if\n ;; Update reference to new location\n local.get $p\n local.get $dest\n i32.const 4\n i32.sub\n i32.load\n i32.store\n end\n\n ;; do while (++p < end_of_stack)\n local.get $p\n i32.const 4\n i32.add\n local.tee $p\n i32.const {{STACK_SIZE}}\n i32.lt_u\n br_if $rsu_loop\n end $rsu_loop\n\n ;; Update references to objs previously stored in nursery\n call $__update_nursery_refs\n\n ;; Empty nursery: ie- allow overwrites\n i32.const {{NURSERY_SP_INIT}}\n global.set $__nursery_sp\n )\n\n ;; (func (export \"heapLen\") (result i32)\n ;; (local $ret i32)\n ;; (local $p i32)\n\n ;; global.get $__heap_start\n ;; local.set $p\n\n ;; loop $iter_ll\n ;; local.get $p\n ;; i32.eqz\n ;; if\n ;; local.get $ret\n ;; return\n ;; end\n ;; local.get $ret\n ;; i32.const 1\n ;; i32.add\n ;; local.set $ret\n ;; local.get $p\n ;; i32.load offset=8\n ;; local.set $p\n\n ;; br $iter_ll\n ;; end $iter_ll\n ;; unreachable\n ;; )\n\n ;; After minor gc, update references to values stored in the nursery\n ;; TODO ideally would be inlined within do_gc\n ;; TODO this should instead operate on the nursery values before memcpy to heap\n (func $__update_nursery_refs\n (local $p i32) ;; pointer to head of current nursery object\n (local $mark_size i32) ;; mark-size field\n (local $size i32) ;; size extracted\n (local $dest i32) ;; where object p has been relocated to\n (local $bf i32) ;; pointer to the ref bitfield of p\n (local $i i32) ;; bit index within ref bitfield of p\n (local $bf_cursor i64) ;; read head for bitfield\n (local $local_ind i32) ;; index within current bf_cursor\n (local $check_ptr i32) ;; tmp: pointer we're checking and updating\n\n ;; Algorithm:\n ;; for each object in nursery:\n ;; if marked && has refs bf:\n ;; for each child reference (according to refs bf):\n ;; if in nursery:\n ;; read translation address from nursery\n ;; update value\n\n ;; maybe replace this with `block`s?\n\n ;; For each pointer p in nursery\n global.get $__nursery_sp\n local.set $p\n loop $cp_loop\n ;; Load header\n local.get $p\n i32.load\n local.set $bf\n local.get $p\n i32.load offset=4\n local.tee $mark_size\n\n ;; If marked\n i32.const 0xc0000000\n i32.and\n if\n ;; and if has a refs bf\n local.get $bf\n if\n ;; Get size\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n local.set $size\n\n ;; Get updated address\n local.get $p\n i32.load offset=8\n local.set $dest\n\n ;; Initialize i\n i32.const 0\n local.set $i\n\n ;; Iterate over bitfield\n loop $rbf_loop\n ;; First bit in an i64\n local.get $i\n i32.const 64\n i32.rem_u\n local.tee $local_ind\n i32.eqz\n if\n ;; Load next 64 bits from bitfield\n local.get $i\n i32.const 3\n i32.shr_u ;; 64 / 8 *\n local.get $bf\n i32.add\n i64.load\n local.set $bf_cursor\n end\n\n ;; If bit indicates reference\n i64.const 0x1\n local.get $local_ind\n i32.const 7\n i32.xor ;; wasm is little endian\n i64.extend_i32_u\n i64.shl\n local.get $bf_cursor\n i64.and\n i64.eqz\n i32.eqz ;; convert i64 to boolean -> !!\n if\n ;; Load reference in object\n ;; *(dest + i * 4) as i32\n local.get $i\n i32.const 2\n i32.shl\n local.get $dest\n i32.add\n i32.load ;;offset=12 ;; skip past header\n local.tee $check_ptr\n call $__in_nursery\n if\n ;; Update pointer in the object\n local.get $dest\n local.get $i\n i32.const 2\n i32.shl\n i32.add\n\n local.get $check_ptr\n i32.const 4\n i32.sub\n i32.load ;; relocation addr stored in next ptr (see __do_gc)\n i32.store ;;offset=12\n end\n end\n\n ;; Do while ++i < size\n ;; Remember that size is denoted as multiples of 32 bits\n local.get $i\n i32.const 1\n i32.add\n local.tee $i\n local.get $size\n i32.lt_u\n br_if $rbf_loop\n end\n end\n end\n\n ;; next p\n ;; if (p = p + sizeof(obj_header_t) + p->size * sizeof(i32))\n ;; < END_OF_NURSERY - sizeof(obj_header_t)\n local.get $p\n i32.const 12\n i32.add\n local.get $mark_size\n i32.const 0x3fffffff\n i32.and\n i32.const 2\n i32.shl\n i32.add\n local.tee $p\n i32.const {{NURSERY_SP_INIT}}\n i32.lt_u\n br_if $cp_loop\n end $cp_loop\n )\n\n ;; Free an object from the heap\n ;; ptr = the object to free\n ;; prev = object that came before it in the objects LL\n (func $__heap_free (param $ptr i32) (param $prev i32)\n ;; Convert object into a freespace\n local.get $ptr\n local.get $ptr\n i32.load offset=4\n i32.const 3 ;; add size of object header\n i32.add\n i32.const 0x3fffffff\n i32.and ;; remove mark\n i32.store\n local.get $ptr\n i32.const 0\n i32.store offset=4\n\n ;; Remove object from objects LL\n local.get $prev\n local.get $ptr\n i32.load offset=8\n i32.store offset=8\n\n ;; Add freespace to freespace ll\n global.get $__free_head\n local.get $ptr\n i32.store offset=4 ;; make current free head .next = new freespace\n local.get $ptr\n global.set $__free_head ;; make current free head = new freespace\n )\n\n ;; Coalesce free spaces\n (func $__coalesce\n (local $p i32) ;; current pointer\n (local $next i32) ;; p -> next\n (local $size i32) ;; p -> size\n\n ;; Algorithm: O(N * log(N) + N)\n ;; 1. Merge sort free-list by memory address\n ;; Note this leaves __free_tail intact\n ;; 2. Iterate through free-list, mergeing adjacent blocks\n\n ;; Sort freelist by memory address\n call $___sort_freelist\n\n ;; Iterate through free list, Combine adjacent free regions\n global.get $__free_head\n local.set $p\n loop $walk\n ;; if (p + p->size === p->next)\n local.get $p\n local.get $p\n i32.load\n local.tee $size\n i32.add\n local.get $p\n i32.load offset=4\n local.tee $next\n i32.eq\n if\n ;; p->size = p->size + p->next->size\n local.get $p\n local.get $next\n i32.load\n local.get $size\n i32.add\n i32.store\n\n ;; p->next = p->next->next\n local.get $p\n local.get $next\n i32.load offset=4\n local.tee $next\n i32.store offset=4\n end\n\n ;; Repeat while ((p = p->next))\n local.get $next\n local.tee $p\n br_if $walk\n end $walk\n )\n\n ;; Sort freelist such that elements are in order by memory address\n (func $___sort_freelist\n (local $list i32) ;; list head (always = $global.__free_head)\n (local $p i32) ;; merge list p\n (local $q i32) ;; merge list q\n (local $e i32) ;; element\n (local $tail i32) ;; end of the merged list\n (local $insize i32)\n (local $nmerges i32)\n (local $psize i32) ;; items left in p\n (local $qsize i32) ;; items left in q\n (local $i i32) ;; iterator\n\n global.get $__free_head\n local.set $list\n\n i32.const 1\n local.set $insize\n\n loop $pass\n local.get $list\n local.set $p ;; p = list\n i32.const 0\n local.tee $list ;; list = null\n local.tee $tail ;; tail = null\n local.set $nmerges ;; nmerges = 0\n\n loop $merge\n ;; There exists a merge to be done\n ;; nmerges++\n local.get $nmerges\n i32.const 1\n i32.add\n local.set $nmerges\n\n ;; step insize places along from p\n local.get $p\n local.set $q ;; q = p\n i32.const 0\n local.set $psize ;; psize = 0\n ;; for (i = 0; i < insize; i++)\n loop $step\n ;; psize++\n local.get $psize\n i32.const 1\n i32.add\n local.set $psize\n ;; if ((q = q->next))\n local.get $q\n i32.load offset=4\n local.tee $q\n if\n ;; if (++i < insize) continue\n local.get $i\n i32.const 1\n i32.add\n local.tee $i\n local.get $insize\n i32.lt_u\n br_if $step\n end\n end\n\n ;; size of the 2 lists\n ;; qsize = insize\n local.get $insize\n local.set $qsize\n\n ;; merge the 2 lists\n ;; while (psize > 0 || (qsize > 0 && q))\n ;; => if (q ? qsize : psize) { do {...} while (...) }\n local.get $qsize\n local.get $psize\n local.get $q\n select\n if\n loop $merge_loop\n block $cond\n block $take_p\n block $take_q\n ;; Emptiness checks\n local.get $psize\n i32.eqz\n br_if $take_q\n local.get $qsize\n i32.eqz\n br_if $take_p\n local.get $q\n i32.eqz\n br_if $take_p\n\n ;; p <= q\n local.get $p\n local.get $q\n i32.le_u\n br_if $take_p\n\n ;; else: q < p\n ;; fall through to take from q\n end\n ;; take from q\n ;; q = (e = q)->next; qsize--;\n local.get $q\n local.tee $e\n i32.load offset=4\n local.set $q\n local.get $qsize\n i32.const 1\n i32.sub\n local.set $qsize\n br $cond\n end\n ;; take from p\n ;; p = (e = p)->next; psize--;\n local.get $p\n local.tee $e\n i32.load offset=4\n local.set $p\n local.get $psize\n i32.const 1\n i32.sub\n local.set $psize\n end\n\n ;; add next element to merged list\n local.get $tail\n if\n ;; tail->next = e\n local.get $tail\n local.get $e\n i32.store offset=4\n else\n local.get $e\n local.set $list\n end\n local.get $e\n local.set $tail\n\n ;; continue if ...\n ;; psize > 0 || (qsize > 0 && q)\n ;; => q ? qsize : psize\n local.get $qsize\n local.get $psize\n local.get $q\n select\n br_if $merge_loop\n end\n end\n\n ;; Now p&q have stepped `insize` places\n local.get $q\n local.set $p\n end\n\n ;; tail->next = NULL\n local.get $tail\n i32.const 0\n i32.store offset=4\n\n ;; finished when only one merge needed\n local.get $nmerges\n i32.const 1\n i32.le_u\n if\n ;; By design, neither of these should be modified\n ;; local.get $list\n ;; global.set $__free_head\n ;; local.get $tail\n ;; global.set $__free_tail\n return\n end\n\n ;; not done repeat merging 2x size lists\n ;; insize *= 2\n local.get $insize\n i32.const 1\n i32.shl\n local.set $insize\n\n ;; infinite loop\n br $pass\n end $pass\n )\n\n ;; Debugging\n ;; (export \"push_ref\" (func $__ref_stack_push))\n ;; (export \"pop_ref\" (func $__ref_stack_pop))\n ;; (export \"alloc\" (func $__alloc))\n ;; (export \"alloch\" (func $__alloc_heap))\n ;; (export \"mark\" (func $__mark))\n ;; (export \"mmark\" (func $__minor_mark))\n ;; (export \"do_gc\" (func $__do_gc))\n ;; (export \"free\" (func $__heap_free))\n ;; (export \"coalesce\" (func $__coalesce))\n\n {{USER_CODE_STR}}\n)"
noRuntime: "(module\n ;; User defined host imports\n {{USER_IMPORTS}}\n\n ;; User defined function table\n {{USER_TABLE}}\n\n ;; Memory export\n (memory (export \"__memory\") {{PAGES_NEEDED}})\n\n ;; Initialize static data\n (data (i32.const {{STATIC_DATA_START}}) \"{{STATIC_DATA_STR}}\")\n\n ;; User defined functions\n {{USER_CODE_STR}}\n)" = "(module\n ;; User defined host imports\n {{USER_IMPORTS}}\n\n ;; User defined function table\n {{USER_TABLE}}\n\n ;; Memory export\n (memory (export \"__memory\") {{PAGES_NEEDED}})\n\n ;; Initialize static data\n (data (i32.const {{STATIC_DATA_START}}) \"{{STATIC_DATA_STR}}\")\n\n ;; User defined functions\n {{USER_CODE_STR}}\n)"

Generated using TypeDoc