Skip to content

Conversation

@aykevl
Copy link
Member

@aykevl aykevl commented Jun 20, 2025

Writing the pointer of a buffer to memory-mapped I/O will normally cause it to escape, which forces the compiler to heap-allocate the buffer. But we do know how long the value stays alive, so we can tell the compiler to keep it alive exactly until it is not needed anymore - and tell it to not treat the pointer-to-uintptr cast as escaping.

This PR tries to solve the same problem as #4889 but in a different way. In fact, see #4889 (comment) where I first came up with this idea. Basically, with this PR, the allocation inside legacy.ReadRegister (but probably not legacy.WriteRegister!) should be fixed. See: tinygo-org/drivers#766.

It's perhaps not the cleanest solution, but it seems to work in my tests.

@ysoldak can you take a look?

@dgryski @eliasnaur you may also be interested in this PR.

@aykevl aykevl requested a review from ysoldak June 20, 2025 08:10
@github-actions
Copy link

github-actions bot commented Jun 20, 2025

Size difference with the dev branch:

Binary size difference
not the same command!
    tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/adafruit4650
    go: downloading tinygo.org/x/tinyfont v0.3.0
not the same command!
    tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
    go: downloading golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d
not the same command!
    tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/websocket/dial/
    go: downloading golang.org/x/net v0.33.0
not the same command!
    tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/mqttclient/natiu/
    go: downloading github.com/soypat/natiu-mqtt v0.5.1
not the same command!
    tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/mqttclient/paho/
    go: downloading github.com/eclipse/paho.mqtt.golang v1.2.0
 flash                          ram
 before   after   diff          before   after   diff
  14776   14716    -60  -0.41%    6580    6580      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/hts221/main.go
  11884   11844    -40  -0.34%    6580    6580      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/apds9960/proximity/main.go
  14128   14100    -28  -0.20%    6580    6580      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/lps22hb/main.go
  18472   18472      0   0.00%    6236    6236      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/adafruit4650
  61580   61580      0   0.00%    6180    6180      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
   8752    8752      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
  13568   13568      0   0.00%    6796    6796      0   0.00% tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
   8916    8916      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
   9176    9176      0   0.00%    4752    4752      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
   7484    7484      0   0.00%    2320    2320      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/at24cx/main.go
   8000    8000      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1750/main.go
   7360    7360      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
  71564   71564      0   0.00%    3656    3656      0   0.00% tinygo build -size short -o ./build/test.hex -target=pinetime     ./examples/bma42x/main.go
  64300   64300      0   0.00%    6196    6196      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
  27652   27652      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
  64128   64128      0   0.00%    6220    6220      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
  11872   11872      0   0.00%    4812    4812      0   0.00% tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
   7844    7844      0   0.00%    3352    3352      0   0.00% tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
  21752   21752      0   0.00%    3556    3556      0   0.00% tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
  69464   69464      0   0.00%    6368    6368      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go
   4532    4532      0   0.00%    2280    2280      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
  70136   70136      0   0.00%    6980    6980      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
  67024   67024      0   0.00%    9020    9020      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
   7200    7200      0   0.00%    2284    2284      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/gc9a01/main.go
  67792   67792      0   0.00%    6360    6360      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
  68240   68240      0   0.00%    6504    6504      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go
   8336    8336      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/hcsr04/main.go
   5660    5660      0   0.00%    2280    2280      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/customchar/main.go
   5700    5700      0   0.00%    2280    2280      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/text/main.go
  10336   10336      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go
  16112   16112      0   0.00%    2364    2364      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
  10384   10384      0   0.00%    6916    6916      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic
  11100   11100      0   0.00%    4876    4876      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/basic
  29764   29764      0   0.00%   38076   38076      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/pyportal_boing
  10416   10416      0   0.00%    6916    6916      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/scroll
  11184   11184      0   0.00%    4876    4876      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
 263892  263892      0   0.00%   46772   46772      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/slideshow
  10980   10980      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
  26356   26356      0   0.00%    2328    2328      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
  28120   28120      0   0.00%    6828    6828      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/lsm303dlhc/main.go
  12304   12304      0   0.00%    4788    4788      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
  10008   10008      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
   9140    9140      0   0.00%    4772    4772      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017/main.go
   9544    9544      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017-multiple/main.go
   9400    9400      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
  70340   70340      0   0.00%    6196    6196      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp2515/main.go
  27452   27452      0   0.00%    3864    3864      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
  27372   27372      0   0.00%    5844    5844      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit-v2 ./examples/microbitmatrix/main.go
   7452    7452      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mma8653/main.go
   7356    7356      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mpu6050/main.go
  76136   76136      0   0.00%    7452    7452      0   0.00% tinygo build -size short -o ./build/test.hex -target=p1am-100 ./examples/p1am/main.go
  14068   14068      0   0.00%    5416    5416      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/main.go
   6172    6172      0   0.00%    3292    3292      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
   4552    4552      0   0.00%    2284    2284      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
  12452   12452      0   0.00%    5392    5392      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/seesaw/soil-sensor
  13712   13712      0   0.00%    5400    5400      0   0.00% tinygo build -size short -o ./build/test.hex -target=qtpy-rp2040 ./examples/seesaw/rotary-encoder
   2841    2841      0   0.00%     558     558      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino ./examples/servo
  15772   15772      0   0.00%    5464    5464      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico     ./examples/sgp30
   8212    8212      0   0.00%    6788    6788      0   0.00% tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
  57696   57696      0   0.00%    3688    3688      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
  57696   57696      0   0.00%    3696    3696      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht4x/main.go
  57696   57696      0   0.00%    3688    3688      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/shtc3/main.go
   5804    5804      0   0.00%    2284    2284      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
   6716    6716      0   0.00%    2284    2284      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
   6608    6608      0   0.00%    2284    2284      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
  16772   16772      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
  10104   10104      0   0.00%    4532    4532      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-bluefruit ./examples/tone
  10096   10096      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/main.go
  12616   12616      0   0.00%    5404    5404      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/touch/capacitive
   9136    9136      0   0.00%    6780    6780      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
  12664   12664      0   0.00%    6976    6976      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
  15684   15684      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
  14316   14316      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl6180x/main.go
  24776   24776      0   0.00%   13720   13720      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-nrf52840-sense ./examples/waveshare-epd/epd1in54/main.go
   6440    6440      0   0.00%    2324    2324      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
   6072    6072      0   0.00%    2316    2316      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
   6360    6360      0   0.00%    2324    2324      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
  28060   28060      0   0.00%   18476   18476      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/waveshare-epd/epd2in66b/main.go
   6880    6880      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
   5622    5622      0   0.00%    9538    9538      0   0.00% '-xesppie' is not a recognized feature for this target (ignoring feature)
  63072   63072      0   0.00%    5948    5948      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
   1581    1581      0   0.00%     598     598      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino   ./examples/ws2812
   1056    1056      0   0.00%     180     180      0   0.00% tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
  32112   32112      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
  16336   16336      0   0.00%    4724    4724      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
  11540   11540      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
  12252   12252      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/veml6070/main.go
   6788    6788      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/simple/main.go
   8708    8708      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/speed/main.go
   6764    6764      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/simple/main.go
   9112    9112      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/speed/main.go
   7400    7400      0   0.00%    3324    3324      0   0.00% tinygo build -size short -o ./build/test.hex -target=nucleo-f103rb ./examples/shiftregister/main.go
   6796    6796      0   0.00%    2272    2272      0   0.00% '-xesppie' is not a recognized feature for this target (ignoring feature)
  13268   13268      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
   9800    9800      0   0.00%    4764    4764      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
  76704   76704      0   0.00%    6336    6336      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
  37680   37680      0   0.00%    6048    6048      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8523/
  71092   71092      0   0.00%    6344    6344      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
   7168    7168      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
  70524   70524      0   0.00%    6340    6340      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/
  70916   70916      0   0.00%    6344    6344      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/
  14092   14092      0   0.00%    5368    5368      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/qmi8658c/main.go
  12624   12624      0   0.00%    5352    5352      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/pcf8591/
   8688    8688      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go
  12280   12280      0   0.00%    4780    4780      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina219/main.go
   9392    9392      0   0.00%    5248    5248      0   0.00% tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
  74072   74072      0   0.00%   10756   10756      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
  62128   62128      0   0.00%    8228    8228      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
  10384   10384      0   0.00%    6788    6788      0   0.00% tinygo build -size short -o ./build/test.elf -target=wioterminal ./examples/axp192/m5stack-core2-blinky/
  10792   10792      0   0.00%    5340    5340      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/xpt2046/main.go
  13228   13228      0   0.00%    4936    4936      0   0.00% tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
  33392   33392      0   0.00%    6828    6828      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
  13012   13012      0   0.00%    6308    6308      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
  12304   12304      0   0.00%    5412    5412      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
   7940    7940      0   0.00%    4748    4748      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
   9488    9488      0   0.00%    4764    4764      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ds18b20/main.go
 122192  122192      0   0.00%    8100    8100      0   0.00% tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/lora/lorawan/atcmd/
  17852   17852      0   0.00%    7012    7012      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/as560x/main.go
  11672   11672      0   0.00%    5360    5360      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu6886/main.go
   7756    7756      0   0.00%    4740    4740      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ttp229/main.go
  69064   69064      0   0.00%    6892    6892      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/ndir/main_ndir.go
  62284   62284      0   0.00%    3784    3784      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ndir/main_ndir.go
  65328   65328      0   0.00%    6252    6252      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/ndir/main_ndir.go
  11120   11120      0   0.00%    5352    5352      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mpu9150/main.go
  10148   10148      0   0.00%    5824    5824      0   0.00% tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/encoders/quadrature-interrupt
  68568   68568      0   0.00%    6860    6860      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/mcp9808/main.go
  44212   44212      0   0.00%    7176    7176      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/tmc2209/main.go
  15196   15196      0   0.00%    5348    5348      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/main.go
  12452   12452      0   0.00%    4544    4544      0   0.00% tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
  14740   14740      0   0.00%    5408    5408      0   0.00% tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
  88200   88200      0   0.00%    7260    7260      0   0.00% tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
 347208  347208      0   0.00%   16796   16796      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal -stack-size 8kb ./examples/net/http-get/
 119880  119880      0   0.00%    8052    8052      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-nano33 -stack-size 8kb ./examples/net/tcpclient/
 308376  308376      0   0.00%   15956   15956      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/websocket/dial/
 103568  103568      0   0.00%   10032   10032      0   0.00% tinygo build -size short -o ./build/test.hex -target=metro-m4-airlift -stack-size 8kb ./examples/net/socket/
 388648  388648      0   0.00%   18804   18804      0   0.00% tinygo build -size short -o ./build/test.hex -target=matrixportal-m4 -stack-size 8kb ./examples/net/webstatic/
 166300  166300      0   0.00%   10024   10024      0   0.00% tinygo build -size short -o ./build/test.hex -target=arduino-mkrwifi1010 -stack-size 8kb ./examples/net/tlsclient/
 155772  155772      0   0.00%    8844    8844      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-rp2040 -stack-size 8kb ./examples/net/mqttclient/natiu/
 116740  116740      0   0.00%   13328   13328      0   0.00% tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webclient/
 338172  338172      0   0.00%   21848   21848      0   0.00% tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/webserver/
 338336  338336      0   0.00%   21772   21772      0   0.00% tinygo build -size short -o ./build/test.hex -target=wioterminal -stack-size 8kb ./examples/net/mqttclient/paho/
 174612  174612      0   0.00%   13872   13872      0   0.00% tinygo build -size short -o ./build/test.hex -target=elecrow-rp2040 -stack-size 8kb ./examples/net/tlsclient/
 120272  120272      0   0.00%   11896   11896      0   0.00% tinygo build -size short -o ./build/test.hex -target=elecrow-rp2350 -stack-size 8kb ./examples/net/ntpclient/
  12768   12772      4   0.03%    8364    8364      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao-ble ./examples/ssd1306/
  13272   13276      4   0.03%    5344    5344      0   0.00% tinygo build -size short -o ./build/test.hex -target=xiao-rp2040 ./examples/ssd1306/
  13400   13404      4   0.03%    5344    5344      0   0.00% tinygo build -size short -o ./build/test.hex -target=thumby ./examples/ssd1306/
  13824   13828      4   0.03%    5384    5384      0   0.00% tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
  13516   13520      4   0.03%    5388    5388      0   0.00% tinygo build -size short -o ./build/test.hex -target=macropad-rp2040 ./examples/sh1106/macropad_spi
  60624   62400   1776   2.93%    5968    5976      8   0.13% tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
6248592 6250260   1668   0.00%  963978  963986      8   0.00%

Comment on lines +69 to +94
// Convert the pointer to a uintptr, to be used for memory I/O (DMA for
// example). It also means the pointer is "gone" as far as the compiler is
// concerned, and a GC cycle might deallocate the object. To prevent this from
// happening, also call keepAliveNoEscape at a point after the address isn't
// accessed anymore by the hardware.
// The only exception is if the pointer is accessed later in a volatile way
// (volatile read/write), which also forces the value to stay alive until that
// point.
//
// This function is treated specially by the compiler to mark the 'ptr'
// parameter as not escaping.
//
// TODO: this function should eventually be replaced with the proposed ptrtoaddr
// instruction in LLVM. See:
// https://discourse.llvm.org/t/clarifiying-the-semantics-of-ptrtoint/83987/10
// https://github.com/llvm/llvm-project/pull/139357
func unsafeNoEscape(ptr unsafe.Pointer) uintptr {
return uintptr(ptr)
}

// Make sure the given pointer stays alive until this point. This is similar to
// runtime.KeepAlive, with the difference that it won't let the pointer escape.
// This is typically used together with unsafeNoEscape.
//
// This is a compiler intrinsic.
func keepAliveNoEscape(ptr unsafe.Pointer)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is, as you say, hacky and won't help external drivers that don't have access to the unexported unsafeNoEscape and keepAliveNoEscape.

Can't runtime.KeepAlive be made to not move the allocation to the heap in TinyGo? If so, that leaves unsafeNoEscape. I suggest the more drastical solution of accepting #4889 that relaxes uintptr(unsafe.Pointer(&x)) to not escape. My reasoning is that for something like DMA, you need something like runtime.KeepAlive to keep the pointer alive anyway (it's not enough for the pointer to be kept alive at the point of conversion to uintptr).

That way, all code can use the same tricks to avoid allocations.

Copy link
Member Author

@aykevl aykevl Jun 20, 2025

Choose a reason for hiding this comment

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

This is, as you say, hacky and won't help external drivers that don't have access to the unexported unsafeNoEscape and keepAliveNoEscape.

I intentionally kept them unexported. Drivers (like the tinygo.org/x/drivers/* packages) won't need it as long as they don't access these hardware registers directly - which is the vast majority. If there are drivers which really need these functions, we can export these functions.

Can't runtime.KeepAlive be made to not move the allocation to the heap in TinyGo?

Yes, but that won't help the machine package due to a circular dependency (machine -> runtime -> machine).

I suggest the more drastical solution of accepting #4889 that relaxes uintptr(unsafe.Pointer(&x)) to not escape.

The only way to implement that at the moment is to let the compiler do something exactly like unsafeNoEscape: insert a call at the point of the cast. I much prefer to keep this explicit and keep the default (escaping) behavior for these casts. In the future there might be ptrtoaddr but not today.

My reasoning is that for something like DMA, you need something like runtime.KeepAlive to keep the pointer alive anyway (it's not enough for the pointer to be kept alive at the point of conversion to uintptr).

Yeah that's the goal of this PR.

Copy link
Contributor

@ysoldak ysoldak Nov 12, 2025

Choose a reason for hiding this comment

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

In the future there might be ptrtoaddr but not today.

We may expect this function added in LLVM 22, seems like.
https://llvm.org/docs/ReleaseNotes.html#changes-to-the-llvm-ir

@ysoldak
Copy link
Contributor

ysoldak commented Jun 20, 2025

Any idea why it fails on Windows?

@ysoldak
Copy link
Contributor

ysoldak commented Jun 20, 2025

Do we have a test that proves this helps?
Asking because with tricks like tinygo-org/drivers#766 I was able to eliminate all I2C escapes, at least in my project.

@eliasnaur
Copy link
Contributor

Do we have a test that proves this helps? Asking because with tricks like tinygo-org/drivers#766 I was able to eliminate all I2C escapes, at least in my project.

I agree with this sentiment. Why avoid escaping buffers that could surely be scratch space in some larger, already allocated, structure?

@eliasnaur
Copy link
Contributor

What about drivers written on top of an interface, say drivers.I2C? AFAIK, Unless TinyGo can see through the interface indirection, any buffers passed to I2C.Tx will escape.

@aykevl aykevl force-pushed the machine-noescape-dma branch from 6e803f9 to a411cf7 Compare October 3, 2025 10:02
@aykevl
Copy link
Member Author

aykevl commented Oct 3, 2025

Rebased, and added tests.

What about drivers written on top of an interface, say drivers.I2C? AFAIK, Unless TinyGo can see through the interface indirection, any buffers passed to I2C.Tx will escape.

Yeah, this is only a partial solution. Callers that use the drivers.I2C interface should probably still allocate a static buffer to be sure (in the driver object, for example). But I do think it fixes a few real world cases.

@aykevl aykevl force-pushed the machine-noescape-dma branch from a411cf7 to e0a3e4b Compare October 3, 2025 10:05
// Make sure the w and r buffers stay alive until this point, so they won't
// be garbage collected while the buffers are used by the hardware.
if len(w) > 0 {
keepAliveNoEscape(unsafe.Pointer(&w[0]))
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: why not use unsafe.SliceData and drop the len(w) > 0 check? Here and elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it's an old PR that I rebased.
I've updated the PR to use unsafe.SliceData instead where possible.

@aykevl aykevl force-pushed the machine-noescape-dma branch from e0a3e4b to 48185fc Compare October 3, 2025 11:00
@ysoldak
Copy link
Contributor

ysoldak commented Nov 12, 2025

I have a reproducer that confirms this solves unnecessary heap escapes, at least for nrf52 chip.

In the drivers repo, on 0.33.0 version, on release branch of tinygo, i.e. 0.39.0 (same results on dev branch).

$ tinygo build -target nano-33-ble -o build/test.hex -size=short -opt=z -print-allocs=drivers ./examples/lsm6dsox

~/tinygo-drivers/lsm6dsox/lsm6dsox.go:43:16: object allocated on the heap: escapes at line 43
~/tinygo-drivers/internal/legacy/i2clegacy.go:10:13: object allocated on the heap: escapes at line 13
~/tinygo-drivers/internal/legacy/i2clegacy.go:6:36: object allocated on the heap: escapes at line 6
   code    data     bss |   flash     ram
  62376    1580    6472 |   63956    8052

Interestingly, rc2040 targets do not have this problem:
$ tinygo build -target nano-rp2040 -o build/test.hex -size=short -opt=z -print-allocs=drivers ./examples/lsm6dsox

~/tinygo-drivers/lsm6dsox/lsm6dsox.go:43:16: object allocated on the heap: escapes at line 43
   code    data     bss |   flash     ram
  65840    1636    5240 |   67476    6876

After switching to to machine-noescape-dma branch and doing go install, I now have this:
$ tinygo build -target nano-33-ble -o build/test.hex -size=short -opt=z -print-allocs=drivers ./examples/lsm6dsox

~/tinygo-drivers/lsm6dsox/lsm6dsox.go:43:16: object allocated on the heap: escapes at line 43
   code    data     bss |   flash     ram
  62392    1580    6472 |   63972    8052

This change seems to be fixing escapes for short-living slices, I'm happy.
@deadprogram @soypat makes our live easier in "drivers".
We may, as @soypat says, want to implement some CI to check for such escapes even if this change to be merged, to monitor regressions.

@deadprogram
Copy link
Member

I personally am in favor of this PR, mainly due to being able to at least catch some of these unintentional escapes now.

Also agree that avoiding them in the first place is the ideal, but this is at least another defensive step.

@soypat any opinions on this?

@soypat
Copy link
Contributor

soypat commented Nov 12, 2025

Seems like a neat compiler intrinsic to help out in these cases. Don't think it helps out too much in drivers package, but nice to have

Writing the pointer of a buffer to memory-mapped I/O will normally cause
it to escape, which forces the compiler to heap-allocate the buffer. But
we do know how long the value stays alive, so we can tell the compiler
to keep it alive exactly until it is not needed anymore - and tell it to
not treat the pointer-to-uintptr cast as escaping.
@deadprogram
Copy link
Member

Rebased against latest dev to make sure tests all pass.

@deadprogram
Copy link
Member

All passing so now merging. Thank you everyone!

@deadprogram deadprogram merged commit b203314 into dev Nov 12, 2025
25 checks passed
@deadprogram deadprogram deleted the machine-noescape-dma branch November 12, 2025 17:07
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.

6 participants