//go:build (gc.conservative || gc.custom || gc.precise || gc.boehm) && tinygo.wasm

package runtime

import (
	"internal/task"
	"runtime/volatile"
	"unsafe"
)

func gcMarkReachable() {
	markStack()
	findGlobals(markRoots)
}

//go:extern runtime.stackChainStart
var stackChainStart *stackChainObject

type stackChainObject struct {
	parent   *stackChainObject
	numSlots uintptr
}

// markStack marks all root pointers found on the stack.
//
//   - Goroutine stacks are heap allocated and always reachable in some way
//     (for example through internal/task.currentTask) so they will always be
//     scanned.
//   - The system stack (aka startup stack) is not heap allocated, so even
//     though it may be referenced it will not be scanned by default.
//
// Therefore, we only need to scan the system stack.
// It is relatively easy to scan the system stack while we're on it: we can
// simply read __stack_pointer and __global_base and scan the area in between.
// Unfortunately, it's hard to get the system stack pointer while we're on a
// goroutine stack. But when we're on a goroutine stack, the system stack is in
// the scheduler which means there shouldn't be anything on the system stack
// anyway.
// ...I hope this assumption holds, otherwise we will need to store the system
// stack in a global or something.
//
// The compiler also inserts code to store all globals in a chain via
// stackChainStart. Luckily we don't need to scan these, as these globals are
// stored on the goroutine stack and are therefore already getting scanned.
func markStack() {
	// Hack to force LLVM to consider stackChainStart to be live.
	// Without this hack, loads and stores may be considered dead and objects on
	// the stack might not be correctly tracked. With this volatile load, LLVM
	// is forced to consider stackChainStart (and everything it points to) as
	// live.
	volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart)))

	if task.OnSystemStack() {
		markRoots(getCurrentStackPointer(), stackTop)
	}
}

// trackPointer is a stub function call inserted by the compiler during IR
// construction. Calls to it are later replaced with regular stack bookkeeping
// code.
func trackPointer(ptr, alloca unsafe.Pointer)

// swapStackChain swaps the stack chain.
// This is called from internal/task when switching goroutines.
func swapStackChain(dst **stackChainObject) {
	*dst, stackChainStart = stackChainStart, *dst
}

func gcResumeWorld() {
	// Nothing to do here (single threaded).
}
