💾 Archived View for sdf.org › rg19 › core › 2023.02.12.gmi captured on 2023-03-20 at 18:59:00. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2023-12-28)

-=-=-=-=-=-=-

TinyGo Terror

Sometime around 2019 or 2020, I was in the shack working on some Go code for a fairly ambitious home automation project that would soon be terminated (don't even ask). I had the Adafruit Feather M4 Express with CircuitPython pre-loaded, some weather sensors and way too many ideas. Soon I began to wonder if Go could run on a micro-controller, surely somebody must have thought of it by now. As it turns out, someone had indeed thought of it and even wrote a compiler to build Go code for more than 90 different micro controllers. The project is called TinyGo.

How exciting! The possibilities were endless, more ideas began to flood my mind as I imagined a masterpiece of buzzers, bells and blinkenlights that would be the ultimate command center, written entirely in Go and running on all sorts of cool micro-controllers... Or not. I downloaded the source and built the binary, but reality came knocking when I tried to build a simple LED blink example for the M4 Express. The compiler failed instantly, spitting out some message I didn't understand. Searching the web revealed nothing relevant since the project was still quite new.

Recently I decided to try again, but the excitement would end fairly quickly due to a number of unforeseen issues. To build TinyGo from source, one needs to have LLVM installed, along with Cmake, Clang and something called ninja, apparently another build system? Reading the instructions, it seemed building LLVM and Clang from source would be the way to go since it would ensure all architectures are supported. Just run 'make llvm-source' to get the LLVM and Clang source, followed by 'make llvm-build', then 'make' to build TinyGo. Piece of cake, right?

WRONG!

Since my only working computer is a Udoo Bolt with Void Linux installed on the 32GB eMMC drive, the first thing I noticed was how long it takes to build LLVM. Seemed a good time to get some reading done while I wait, but noooo. When it came time to build Clang, the system became almost entirely unresponsive. I could switch to a different window in tmux but it took over a minute to register the keystrokes. Trying to log into another virtual console was a failure as well. I shut down the machine manually in the hopes that it wouldn't corrupt the still-mounted NVMe drive I just added less than a week ago. Upon rebooting I tried to finish the build with only one tmux window open, split into two panes with 'top' running so I could monitor the memory usage. Turns out the 8GB of RAM was JUST BARELY enough to build one object file for Clang, which took over half an hour. There were 439 files remaining, which at this rate would probably have taken all night if it didn't run out of memory. Just that first file left ~100MB RAM free during the build. What the hell!?

Some quick reading on the web revealed some startling numbers. The default build options for Clang results in over 60GB of stuff, which was terrible news for me since the build directory was on the 32GB eMMC, which only had <5GB free. Fearing the worst and not knowing anything about these huge complex build tools, I moved everything to the (thankfully intact) NVMe drive, hoping the TinyGo source was using sane options that would produce <2GB. I restarted the build once more but found the machine was STILL not quite usable. Switching between panes in tmux was slow but doable, though it couldn't do anything else while this was running. Clearly this was a bad idea.

At this point I thought I'd just try to install Clang and LLVM from the Void repos, having forgotten that this may be why the first experience was such a failure (lack of architectures in the Void package build). I ran 'make' once more but that didn't work either. Something about some missing files or whatever, I don't even remember. Then I realize 'make' is only supposed to be used when building LLVM from source. If the dependencies are already installed, just run 'go install --tags=llvm14'. Oops. Well I did that but substituted 'llvm12' since that is the only version available on Void. Once more it failed to build. It seems /bin/ld was complaining about not finding some things which I had no idea what to do about. All of these missing 'things' started with the prefix 'llld', followed by all-caps for whatever the specific thing was. Some time in the web revealed (again) that I failed to read the instructions properly. TinyGo requires LLVM 14 or 15, but the only way I could get it is to build from source...

Shit.

It doesn't say anything about which version of Clang is needed, so I'm assuming at this point that it doesn't matter as long as it's relatively recent. I start to download the LLVM 15 source from git, but the download counter keeps going up because it's downloading every single point release!. I decided to terminate the whole idea and canceled the download when it exceeded 500MB, thinking this whole adventure was just one big waste of time. Good thing I did, because I'd somehow overlooked the fact that Clang is going to get built alongide LLVM no matter which version I use, because IT IS PART OF the LLVM project. There is no way in hell I'm going to upgrade the Bolt with 16GB of RAM just so I can build some tools that will be used once and promptly thrown away after they've done their job.

Except...

The next day it occured to me that I _really_ didn't follow the instructions at all. While I did install Clang from the repository, I was actually using GCC to build everything. The instructions specifically recommended using Clang because it compiles faster and uses "much less memory". So of course I reinstalled the build tools that were previously deleted and ran 'export CC=clang' followed by 'export CXX=g++'. As before, there was only one tmux window with three panes; the build shell, the instructions file viewed with 'less' and a third pane for 'top'.

Sure enough, free memory hovered around 1GB for much of the build, often jumping down to 100MB but only briefly. I sat and watched as the file names came and went, looking for something called 'SemaCast.cpp.o'. When GCC was used, it was that particular file that marked the start of the Bolt becoming unresponsive, taking more than half an hour to process it. Using Clang took less than a minute for the very same file... In fact it seemed to just breeze right through the entire build, ending in just an hour and 20 minutes. Not once did the Bolt slow down to any noticible degree. Yet another reminder for me to FOLLOW THE INSTRUCTIONS.

The final step was to build a release tarball and unpack it somewhere in $PATH. I ran 'make release' and waited around ten minutes before it was complete, then copied the resulting tarball to the eMMC drive. Once unpacked, the total size came out to 718MB which is even better than I was expecting. The fact that it's stacically linked against LLVM and Clang means I don't need to keep the build tools that were previously installed. That freed up over 400MB. The overall size of the tinygo build directory came out to 6.5GB after all this, so its a good thing it was moved over to the SSD.

But does it work?

Naturally the first logical test was a simple 'hello world'. I first built the test program with regular 'go build', then again with '--ldflags="-s -w"' to see how the sizes compared. The latter only took off 0.6MB from the otherwise 1.8MB binary. Running 'tinygo build hello.go' was surprisingly slow, taking several seconds to build this simple program. On the other hand, the binary was just 180KB, but when running 'strip' on the binary, the size was further reduced to only 24KB. And it worked, printing "Hello World!" to the terminal. Of course this was only a simple test for AMD64 Linux. Trying to build for the M4 Express was not so exciting since it failed with 'segmentation fault'.

$ tinygo build -target=feather-m4
$ error: failed to link /tmp/tinygo913068522/main: signal: segmentation fault

This was tremendously disappointing. All that time and effort wasted away as I downloaded and built all this stuff just to have it blow up in my face. Oddly, it did build without errors when 'target=arduino', but this is quite annoying since I don't even have the Arduino Uno, or any Arduino for that matter except for the useless Leonardo chip inside the Bolt. It also doesn't make sense that the binary is 64KB, but the Uno only has 32KB of Flash storage. Upon reading 'tinygo help', I found the option 'no-debug' which reduces the size to 16KB. That makes more sense... Running 'tinygo targets' revealed there is in fact a target for the Leonardo, for which the build was successful. So why isn't it working for the Feather targets? The internet is useless once again because the only relevent issue that turns up is someone trying to flash an ESP32 but getting an error about 'xtensa-esp32-elf-ld' not found in $PATH. Not even close to what's wrong in my own attempts.

Out of curiosity, I tried flashing the Leonardo with this blink example, but apparently I needed something called 'avrdude'. I was horrified to find that installing this would drag in over 600MB of packages. SIX HUNDRED MEGABYTES! Just to upload a program to the Leonardo... Except it DIDN'T upload, instead it complained about "programmer not responding" and something about buffered memory access not supported. As it turns out there are several known issues with using the Leonardo on the Bolt, but they've mostly been solved for use with the Arduino IDE. Not sure how that applies to this particular example since I'm already part of the 'dialout' group and do not have this so-called 'ModemManager' installed. In any case, I'm not even interested in the Arduino ecosystem, but it's interesting that TinyGo can build without errors for THOSE targets but not the Adafruit Feather targets. It won't even build for the CircuitPlayground or the RP2040. Even trying to build for the Raspberry Pi Pico fails.

So that's the story of how I threw away several hours messing around with TinyGo. I'm not ready to give up on it just yet, but it's not looking so good at this point.