Globals live a short hop from r13
Here's the problem: a GameCube address is 32 bits, and a single PowerPC instruction has nowhere to put all of them, so you can't lwz a global from its absolute address. The Small Data Area is MWCC's workaround. Boot code points register r13 at a fixed base, and the globals a game uses most are packed into a window that a signed 16-bit offset off r13 can cover. It's not a big window — 64 KB, ±32 KB. Spill past it and a global has to take the long way with @ha/@l, which shows up later. While it fits, one load does the job, and the linker bakes the offset in:
lwz r3, g@sda21(r13) # load global g, r13-relative
blr
R_PPC_EMB_SDA21 g
@sda21 is a relocation, not a real address. An unlinked object disassembles to lwz r3, 0(0) plus a dangling R_PPC_EMB_SDA21 g line, because the offset and base register stay unresolved until link time. That R_PPC_EMB_SDA21 line is your tell for a plain, non-array global, whether it's extern or defined right here.
Your task
Declare nothing yourself — extern int gFrameCount; is already provided. Write func_80021258 to match the target assembly above.