
fn increase_block(block: ptr) $inline {
    @ptrv(block, uint, 2)++
    gc.mem_marked += @ptrv(block, uint, 4)
}
fn decrease_block(block: ptr) $inline {
    @ptrv(block, uint, 2)--
    gc.mem_marked -= @ptrv(block, uint, 4)
}

fn increase_block_of_item(item: ptr) {
    let data = (@offset(item, u8, -8)).@cast(GcData)
    let offset = data.block_offset & offmask
    if offset > 0 {
        let block = item - offset
        increase_block(block)
    }
}

fn decrease_block_of_item(item: ptr) {
    let data = (@offset(item, u8, -8)).@cast(GcData)
    let offset = data.block_offset & offmask
    if offset > 0 {
        let block = item - offset
        decrease_block(block)
    }
}

fn mark_used(item: ?GcItem) $hot {

    let ml = gc.mark_list
    if isset(item) : ml.add(item)

    while true {
        let item = ml.pop() ! break
        let data = gcdata(item)
        // if data.is_shared() : panic("Setting shared item to owned state")

        if data.is_new() : increase_block_of_item(item)
        data.set_owned()

        // Props
        let has_vtable = data.has_vtable()
        let propc = data.propc
        if propc > 0 {
            let prop_base : ptr[?GcItem] = item + size_of(ptr) * has_vtable.@cast(uint)
            // Mark properties
            while propc-- > 0 {
                let sub = prop_base[propc]
                if isset(sub) && (sub & 3 == 0) {
                    let sub_data = gcdata(sub)
                    if sub_data.is_not_owned_not_shared() {
                        // Own
                        @ptrv(prop_base, ?ptr, propc) = sub | 1
                        if sub_data.is_new() : increase_block_of_item(sub)
                        sub_data.set_owned()
                        // Mark sub properties
                        ml.add(sub)
                    } else {
                        // Co-own
                        @ptrv(prop_base, ?ptr, propc) = sub | 2
                        sub_data.co_own()
                    }
                }
            }
        }

        // Call hook gc_action
        if has_vtable {
            let func = @ptrv(data.vtable, ?fnRef(ptr, fnRef(ptr, ptr)())(), 0)
            if isset(func) : func(item, property_mark)
        }
    }
}

fn free_blank(item: ptr) $hot {
    let data = (@offset(item, u8, -8)).@cast(GcData)
    if data.is_not_new() : return

    // Call gc_free
    let func = @ptrv(data.vtable, ?fnRef(ptr)(), 1)
    if isset(func) : func(item)
    data.remove_has_vtable()

    #if TEST
    let mark = data.block_offset & 3
    if mark != 0 : panic("Mark not 0 before free")
    if data.co_count != 0 : panic("co_count not 0 before free")
    if data.flags != 0 {
        print("Flags: ")
        data.flags.print(10)
        print("\n")
        panic("Flags not 0 before free")
    }
    #end
}

fn dis_own_rec(item: ptr) {

    let ml = gc.mark_list
    ml.add(item)

    while true {
        let item = (ml.pop() ! break).@cast(GcItem)
        let data = gcdata(item)

        decrease_block_of_item(item)

        gc.dis_own_count++
        data.set_new()
        if data.co_count > 0 : gc.re_own_list.add_ptr(item)

        // Props
        let has_vtable = data.has_vtable()
        let propc = data.propc
        if propc > 0 {
            // Mark properties
            let prop_base = item + size_of(ptr) * has_vtable.@cast(uint)
            while propc-- > 0 {
                let sub = @ptrv(prop_base, ?GcItem, propc)
                if isset(sub) {
                    // if sub & 3 == 3 : panic("IS3")
                    if sub & 1 == 1 {
                        sub = sub - 1
                        @ptrv(prop_base, ptr, propc) = sub
                        let data = gcdata(sub)
                        data.set_no_owner()
                        if data.is_shared() : continue
                        ml.add(sub)
                    } else if sub & 2 == 2 {
                        sub = sub - 2
                        @ptrv(prop_base, ptr, propc) = sub
                        gcdata(sub).dis_co_own()
                    }
                }
            }
        }

        // Call hook gc_action
        if has_vtable {
            let func = @ptrv(data.vtable, ?fnRef(ptr, fnRef(ptr, ptr)())(), 0)
            if isset(func) : func(item, property_dis_own)
            if !data.is_in_blanks() {
                data.set_in_blanks()
                gc.blanks.add_ptr(item)
            }
        }
    }
}


fn share_null_check(item: ?ptr) {
    if isset(item) {
        share(item)
    }
}

fn share(item: GcItem) {
    let ds = gc.dont_stop
    gc.dont_stop = true

    let ml = gc.mark_list
    let start = ml.index
    ml.add(item)

    while true {
        if ml.index == start : break
        let item = (ml.pop() ! break).@cast(GcItem)
        let data = gcdata(item)

        if data.is_marked() : continue
        data.set_mark()

        if !data.is_shared() {
            data.set_shared()
            let is_new = data.is_new()
            if is_new : data.set_no_owner()

            gc.shared_items.add_ptr(item)

            let offset = data.block_offset & offmask
            if offset > 0 {
                let block = item - offset
                let isize = @ptrv(block, uint, 4)
                if is_new {
                    @ptrv(block, uint, 2)++
                } else {
                    gc.mem_marked -= isize
                }
                // Increase mem shared
                atomic(mem_shared + isize)
            }
        }

        // Props
        let has_vtable = data.has_vtable()
        let propc = data.propc
        if propc > 0 {
            // Mark properties
            let prop_base = item + size_of(ptr) * has_vtable.@cast(uint)
            while propc-- > 0 {
                let sub = @ptrv(prop_base, ?GcItem, propc)
                if isset(sub) {
                    let sub_item : GcItem = sub & -4.to(uint)

                    let sub_data = gcdata(sub_item)
                    if sub & 1 == 1 {
                        // Remove ownership
                        @ptrv(prop_base, ?GcItem, propc) = sub_item
                        sub_data.set_no_owner()
                    } else if sub & 2 == 2 {
                        // Remove co ownership
                        @ptrv(prop_base, ?GcItem, propc) = sub_item
                        sub_data.dis_co_own()
                    }

                    ml.add(sub_item)
                }
            }
        }

        // Call hook gc_action
        if has_vtable {
            let vt = data.vtable
            let func = @ptrv(vt, ?fnRef(ptr, fnRef(ptr, ptr)())(), 0)
            if isset(func) {
                func(item, property_share)
            }
        }
    }

    gc.dont_stop = ds
}

fn update_mark(item: GcItem) {

    let ml = gc.mark_list
    ml.add(item)

    while true {
        let item = (ml.pop() ! break).@cast(GcItem)
        let data = gcdata(item)
                    // if !data.is_shared() : panic("Marking a non shared item")
        if data.is_marked() : continue 
        data.set_mark()

        // Props
        let has_vtable = data.has_vtable()
        let propc = data.propc
        if propc > 0 {
            // Mark properties
            let prop_base = item + size_of(ptr) * has_vtable.@cast(uint)
            while propc-- > 0 {
                let sub = @ptrv(prop_base, ?GcItem, propc)
                if isset(sub) {
                    let item : GcItem = sub & -4.to(uint)
                    // if !gcdata(item).is_shared() : panic("Shared item has non shared properties")
                    ml.add(item)
                }
            }
        }

        // Call hook gc_action
        if has_vtable {
            let vt = data.vtable
            let func = @ptrv(vt, ?fnRef(ptr, fnRef(ptr, ptr)())(), 0)
            if isset(func) {
                func(item, property_update_mark)
            }
        }
    }
}

fn mark_leak_rec(item: GcItem, on: bool) {

    let ml = gc.mark_list
    ml.add(item)

    while true {
        let item = (ml.pop() ! break).@cast(GcItem)
        let data = gcdata(item)
        if data.is_shared() && !running_shared : continue
        if on {
            if data.has_leak_mark() : continue 
            if data.is_new() && (!data.is_shared() || !data.is_marked()) && (data.block_offset & offmask) != 0 {
                let offset = data.block_offset & offmask
                println("---------------------")
                println("ITEM: " + item.@cast(ptr))
                println("RUNNING SHARED GC: " + running_shared)
                println("MARK_LIST_SIZE: " + (ml.index / size_of(ptr)))
                println("NEW: " + data.is_new())
                println("SHARED: " + data.is_shared())
                println("MARKED: " + data.is_marked())
                println("OFFSET: " + data.block_offset)
                println("PROPC: " + data.propc)
                println("FLAGS: " + data.flags)
                println("HAS_VTABLE: " + data.has_vtable())
                if offset > 0 {
                    let block = item - offset
                    println("GC_ISIZE: " + @ptrv(block, uint, 4))
                    println("ITEM_SIZE: " + (@ptrv(block, uint, 4) - 8))
                }
                println("---------------------")
                panic("GC ITEM WAS LEAKED")
            }
            data.set_leak_mark()
        } else {
            if !data.has_leak_mark() : continue 
            data.remove_leak_mark()
        }

        // Props
        let has_vtable = data.has_vtable()
        let propc = data.propc
        if propc > 0 {
            // Mark properties
            let prop_base = item + size_of(ptr) * has_vtable.@cast(uint)
            while propc-- > 0 {
                let sub = @ptrv(prop_base, ?GcItem, propc)
                if isset(sub) {
                    let item : GcItem = sub & -4.to(uint)
                    ml.add(item)
                }
            }
        }

        // Call hook gc_action
        if has_vtable {
            let func = @ptrv(data.vtable, ?fnRef(ptr, fnRef(ptr, ptr)())(), 0)
            if isset(func) : func(item, property_add_mark_list)
        }
    }
}
