Skip to content

runtime (gc.blocks): move objHeader to the end#5455

Open
niaow wants to merge 1 commit into
tinygo-org:devfrom
niaow:end-header
Open

runtime (gc.blocks): move objHeader to the end#5455
niaow wants to merge 1 commit into
tinygo-org:devfrom
niaow:end-header

Conversation

@niaow

@niaow niaow commented Jun 9, 2026

Copy link
Copy Markdown
Member

This moves the objHeader from before an object body to after it. On 32-bit systems with 16-byte alignment requirements (x86, ARM, RISC-V), we previously padded the header to a whole block. This wastes up to 12 bytes, as on -gc=conservative the header is a single pointer. With this change, no padding is required (beyond that from rounding the size up).

The "head" block in the metadata was moved to the end of the range to match the header location. This changed the block loop directions throughout the GC logic. The bit hacks used by sweep no longer work because there is no equivalent of addition that carries downwards. However it is now possible to merge the sweep and free range list rebuild passes because their loop directions match.

There are two other places where we rebuilt the free ranges list: when initializing or growing the heap. The former can be easily replaced with a single hardcoded range containing the entire heap. In the latter case, I opted to only add the new space to the existing list. These replacements allowed me to fully remove the buildFreeRanges function.

@niaow niaow force-pushed the end-header branch 2 times, most recently from f2b97aa to 7408184 Compare June 9, 2026 17:18
Comment thread src/runtime/gc_blocks.go
// findHead returns the head (last block) of an object, assuming the block
// points to an allocated object. It returns the same block if this block
// already points to the head.
func (b gcBlock) findHead() gcBlock {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should rename this findHeader

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finds the head block. It returns the index of the block with the header, not the header itself.

Comment thread src/runtime/gc_blocks.go
Comment thread src/runtime/gc_blocks.go

// Find the first block in the allocation.
firstBlock := lastBlock
for firstBlock > 0 && (firstBlock-1).state() == blockStateTail {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question as above: can we scan multiple blocks at once by checking the entire meta byte?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think that was in the scope of this PR since we didn't do that before. It is also less important because this only happens once per allocation, as opposed to findHead which runs for every interior pointer to an allocation.

This moves the objHeader from before an object body to after it.
On 32-bit systems with 16-byte alignment requirements (x86, ARM, RISC-V), we previously padded the header to a whole block.
This wastes up to 12 bytes, as on -gc=conservative the header is a single pointer.
With this change, no padding is required (beyond that from rounding the size up).

The "head" block in the metadata was moved to the end of the range to match the header location.
This changed the block loop directions throughout the GC logic.
The bit hacks used by sweep no longer work because there is no equivalent of addition that carries downwards.
However it is now possible to merge the sweep and free range list rebuild passes because their loop directions match.

There are two other places where we rebuilt the free ranges list: when initializing or growing the heap.
The former can be easily replaced with a single hardcoded range containing the entire heap.
In the latter case, I opted to only add the new space to the existing list.
These replacements allowed me to fully remove the buildFreeRanges function.
@dgryski

dgryski commented Jun 10, 2026

Copy link
Copy Markdown
Member

I'm going to run this across the test corpus and the it's probably fine to merge.

@dgryski

dgryski commented Jun 11, 2026

Copy link
Copy Markdown
Member

Test corpus fails on https://github.com/dgryski/go-interp/.
CPU never increases

~/go/src/github.com/tinygo-org/tinygo $ while :; do sleep 1; ps axuw|grep [/]var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main; done
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v
dgryski          19376   0.0  0.1 35171256  36536 s000  S+    9:50PM   0:00.03 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo2214306572/main -test.v

vs dev

~/go/src/github.com/tinygo-org/tinygo $ while :; do sleep 1; ps axuw|grep [/]var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main; done
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:24.90 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541  99.9  2.4 35171256 789212 s000  R+    9:51PM   0:26.08 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:27.25 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:28.42 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541  99.7  2.4 35171256 789212 s000  R+    9:51PM   0:29.59 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:30.83 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541  99.4  2.4 35171256 789212 s000  R+    9:51PM   0:32.05 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:33.28 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:34.50 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:35.73 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:36.97 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541  98.8  2.4 35171256 789212 s000  R+    9:51PM   0:38.19 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541  99.7  2.4 35171256 789212 s000  R+    9:51PM   0:39.41 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:40.64 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v
dgryski          19541 100.0  2.4 35171256 789212 s000  R+    9:51PM   0:41.87 /var/folders/5b/_hr1d1fd3qn4t9vtfx5p61wm0000gp/T/tinygo3622897092/main -test.v

where it exits cleanly after ~150s on my laptop.

~/go/src/github.com/dgryski/tinygo-test-corpus/_corpus/dgryski/go-interp $ tinygo test -gc=precise -v
=== RUN   TestSearchSmall
--- PASS: TestSearchSmall (0.01s)
=== RUN   TestSearchBig
--- PASS: TestSearchBig (151.60s)
PASS
ok  	github.com/dgryski/go-interp	152.027s

@dgryski

dgryski commented Jun 11, 2026

Copy link
Copy Markdown
Member

Passes with -target=wasip1. So, something about building for Darwin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants