This may be a stupid question, but if the function must tail, that's just a jump, no? Why not use goto?
(though it is fine if GC can only happen inside a function call and the call takes the shadow stack as an argument)
You could put each stack frame into a struct, and have the first field be a pointer to a const static stack-map data structure or function that enumerates the pointers within the frame.
BTW, the passed pointer to this struct could also be used to implement access to the calling function's variables, for when you have nested functions and closures.