Skip to content

transform: PtrToInt and IntToPtr do not escape #4889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from

Conversation

ysoldak
Copy link
Contributor

@ysoldak ysoldak commented May 8, 2025

This hints compiler that constructs like uintptr(unsafe.Pointer(&x)) do not escape. Handled by llvm.PtrToInt case.
(I've also added llvm.IntToPtr, for completeness, but I'm not sure what code matches it. Feel free to reject.)

Disclaimer

This change may very well be completely wrong; can such constructs escape?
Hence a draft, merely a conversation starter.

Context

I2C implementations on RP2040 and NRF52 behave differently regarding allocation behavior.
NRF52 implementation escapes to heap while RP2040 does not.
The issue was tracked down to lines 52 and 61 of this file: https://github.com/tinygo-org/tinygo/blob/release/src/machine/machine_nrf528xx.go

Reproducer

$ tinygo build -target=feather-nrf52840 -print-allocs=main -o test.uf2 ./src/examples/i2c-target
.../tinygo/src/examples/i2c-target/main.go:101:13: object allocated on the heap: escapes at line 105
.../tinygo/src/examples/i2c-target/main.go:77:43: object allocated on the heap: escapes at line 77
.../tinygo/src/examples/i2c-target/main.go:76:12: object allocated on the heap: escapes at line 77
.../tinygo/src/examples/i2c-target/main.go:66:43: object allocated on the heap: escapes at line 66
.../tinygo/src/examples/i2c-target/main.go:57:43: object allocated on the heap: escapes at line 57
.../tinygo/src/examples/i2c-target/main.go:56:13: object allocated on the heap: escapes at line 57

And this does not escape at all

$ tinygo build -target=nano-rp2040 -print-allocs=main -o test.uf2 ./src/examples/i2c-target

After this change both do not escape anymore.
I was over-optimistic and probably tired, it still escapes in NRF52 case, but I don't understand why.

Thanks @dgryski for pointing at allocs.go

@ysoldak
Copy link
Contributor Author

ysoldak commented May 8, 2025

In machine package we have many cases of uintptr(unsafe use.
See https://github.com/search?q=repo%3Atinygo-org%2Ftinygo%20uintptr(unsafe%20path%3Asrc%2Fmachine&type=code

Each of such cases potentially unnecessarily allocates.

@dgryski
Copy link
Member

dgryski commented May 8, 2025

From a conservation on Slack:

I think we need a bit more; for example if you have a pointer conversion between two pointer types, we want to make sure the first one "escapes" if the second pointer "escapes" too.
That's probably why the official unsafe rules require all pointer conversions to happen in a single expression, so there's no requirement to track "does this int eventually become a pointer".
This code pattern with IntToPtr will happen with code predating https://pkg.go.dev/unsafe#Add.

@eliasnaur
Copy link
Contributor

As far as I can read the NRF code i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) fundamentally does escape the pointer, no? I realize that Tx does wait for the DMA transfer to complete, but the TinyGo compiler doesn't know that.

In other words, I can't see how this optimization can be done even with perfect tracking of the uintptr value.

As a side note, I believe code such as the NRF i2c.Tx technically needs a runtime.Pinner to keep the w and r buffers pinned during the DMA transfer. Because of #4072 and because TinyGo doesn't move pointers, a runtime.KeepAlive is probably enough.

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.

3 participants