Memory Introspection
Goblin exposes its memory model through a set of introspection tools that let you observe stashes, tethers, and addresses in real time.
:memaddr is the only one currently available. This page documents the full planned set.:memaddr(x)
Available now.
Returns the memory address of the stash that x is tethered to.
x | [:a :b :c] :memaddr(x) /// -> "0x5555555a1040"
x | [:a :b :c] :memaddr(x) /// -> "0x5555555a1040"
Use this to confirm whether two names share the same stash or point to different ones:
x | 5 y | x :memaddr(x) /// -> "0x55555555f2c0" :memaddr(y) /// -> "0x55555555f2c0" (same stash) x |= 6 :memaddr(x) /// -> "0x55555555f2e0" (moved) :memaddr(y) /// -> "0x55555555f2c0" (unchanged)
x | 5 y | x :memaddr(x) /// -> "0x55555555f2c0" :memaddr(y) /// -> "0x55555555f2c0" (same stash) x |= 6 :memaddr(x) /// -> "0x55555555f2e0" (moved) :memaddr(y) /// -> "0x55555555f2c0" (unchanged)
:same_place?(x, y)
Planned.
Returns true if two tethers point to the same stash.
y | x :same_place?(x, y) /// -> true x |= 6 :same_place?(x, y) /// -> false
y | x :same_place?(x, y) /// -> true x |= 6 :same_place?(x, y) /// -> false
:tether_count(x)
Planned.
Returns how many tethers point to the same stash as x.
x | [:a :b :c] :tether_count(x) /// -> 1 y | x :tether_count(x) /// -> 2
x | [:a :b :c] :tether_count(x) /// -> 1 y | x :tether_count(x) /// -> 2
Also accepts an address string directly:
addr | :memaddr(x) :tether_count(addr) /// -> 2
addr | :memaddr(x) :tether_count(addr) /// -> 2
:memsize(x)
Planned.
Returns how much memory the stash uses in bytes.
items | [:a :b :c] :memsize(items) /// -> 48
items | [:a :b :c] :memsize(items) /// -> 48
:meminfo(x)
Planned.
Full diagnostic dump of a stash.
items | [:a :b :c] :meminfo(items) /// { /// address: "0x5555555a1040" /// type: "array" /// size_bytes: 48 /// tether_count: 1 /// burned: false /// }
items | [:a :b :c] :meminfo(items) /// { /// address: "0x5555555a1040" /// type: "array" /// size_bytes: 48 /// tether_count: 1 /// burned: false /// }
:list_untethered()
Planned.
Returns both abandoned stashes and untethered variables — the complete picture of what is disconnected in the current session.
:list_untethered() /// { /// stashes: [ /// { addr: "0x1000", type: "int", size_bytes: 8, preview: "5" } /// { addr: "0x2000", type: "array", size_bytes: 48, preview: "[:data]" } /// ] /// vars: ["y"] /// }
:list_untethered() /// { /// stashes: [ /// { addr: "0x1000", type: "int", size_bytes: 8, preview: "5" } /// { addr: "0x2000", type: "array", size_bytes: 48, preview: "[:data]" } /// ] /// vars: ["y"] /// }
Abandoned stashes have no tethers — the garbage collector will reclaim them. Untethered variables have no stash — they cannot be used until retethered.
:memory_snapshot()
Planned.
Returns overall memory usage for the current session.
:memory_snapshot() /// { /// total_allocated: 1048576 /// total_tethered: 524288 /// total_untethered: 524288 /// stash_count_tethered: 120 /// stash_count_abandoned: 80 /// gc_last_run: 1500 /// stashes_by_type: { /// array: { count: 45, bytes: 102400 } /// map: { count: 30, bytes: 204800 } /// string: { count: 60, bytes: 61440 } /// int: { count: 40, bytes: 960 } /// } /// }
:memory_snapshot() /// { /// total_allocated: 1048576 /// total_tethered: 524288 /// total_untethered: 524288 /// stash_count_tethered: 120 /// stash_count_abandoned: 80 /// gc_last_run: 1500 /// stashes_by_type: { /// array: { count: 45, bytes: 102400 } /// map: { count: 30, bytes: 204800 } /// string: { count: 60, bytes: 61440 } /// int: { count: 40, bytes: 960 } /// } /// }
Use before and after a section of code to observe memory behavior:
before | :memory_snapshot() /// ... your code ... after | :memory_snapshot() say "Abandoned stashes created: " ++ after{"stash_count_abandoned"}
before | :memory_snapshot() /// ... your code ... after | :memory_snapshot() say "Abandoned stashes created: " ++ after{"stash_count_abandoned"}