A structure or pointer parameter spends a value-pool slot per call, and sustained calling exhausts the pool #36
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A structure or pointer parameter spends a value-pool slot on every call, and nothing ever gives it back. Sustained calling exhausts the pool — measured at 1,015 calls of a two-pointer-parameter
DEFonfeature/reduce_memory_usage@17af2d4, after which every further call fails withMaximum values per line reached's sibling,Array of 1 elements does not fit in the 0 remaining value slots.Where
src/runtime.cbind_structure_parameter()(~line 992): every call binds eachAS TYPE/AS PTR TO TYPEparameter withakbasic_variable_init(variable, &obj->valuepool, sizes, 1).src/variable.cakbasic_variable_init()(~line 95): the@suffix is the exclusion from the inline-value optimisation, so the init always draws from the pool. The comment states the rule: "A@name … keeps pool storage because a pointer may outlive the scope that DIMmed it — docs/16-structures.md says nothing is reclaimed and akbasic_runtime_prev_environment() relies on it."src/runtime.cakbasic_runtime_prev_environment()(~line 158): scope pop returns the variable slot and deliberately not its storage.Each rule is right on its own. Their intersection is the defect: a parameter is a local that dies with the call scope, but it pays the storage price of a
DIMthat must survive one.Functional consequence
DEFwith structure or pointer parameters per enemy per frame — the interface issue #34 explored for the GALAGA embed — is dead after ~25 frames at 40 enemies with two pointer args each (2,048-slot pool / 2 slots per call).akbasic_environment_zero()after each call does not help; it resets the per-line scratch, not the value pool.DEF F(S@ AS RECT)-shaped functions in a loop dies after ~2,000 calls. A game loop reaches that in about a minute.Measured on the spike that decided issue #34's interface (pointer args work semantically — writes through
P@->X%land in the host struct, the type check refuses a wrong type, by-value copies exactly as documented — so this pool behaviour is the only thing that rules them out):Blast radius
Medium. Nothing in tests/ or examples/ trips it today (the reference and language suites call struct-parameter functions a handful of times), so it costs nothing until someone writes the natural thing: a struct-taking function called from a loop. Then it is a hard stop with a message about arrays that names neither the function nor the parameter.
The reduction, for whoever fixes it
The two parameter kinds are not equally hard:
structbasepoints at the target's slots, not its own.RETURN P@clones the reference into the caller's scratch, so nothing can retain the parameter's own slot past the pop.PTR TO PTRdoes not exist, so nothing can point at the parameter either. The cheapest fix may be to let a pointer parameter use the variable'sinlinevalueexactly as a scalar does — the exclusion test inakbasic_variable_init()is the spelling of the name, and forispointerparameters withtotalsize == 1the reason behind the exclusion does not apply.POINT Q@ AT B@inside the body, whereB@is the parameter andQ@an outer pointer, legitimately retains the copy's slots after the pop. Reclaiming those means either proving noPOINT ... AT <param>happened or accepting the documented never-reclaim rule for this case and saying so in docs/16.Until then the guard is: do not call structure-parameter functions from a loop or from a per-frame host path; bind a host global and rebind per instance instead (
akbasic_host_rebind(), docs/16-structures.md), which spends nothing per call.Filed by Tachikoma (Claude Code, Fable 5, 1M context) while executing #34.