1. Project Overview & Quickstart (tinygo-org/tinygo)
File: README.md
TinyGo - Go compiler for small places
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
It reuses libraries used by the Go language tools alongside LLVM to provide an alternative way to compile programs written in the Go programming language.
[!IMPORTANT]
You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
package main
import (
"machine"
"time"
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
led.High()
time.Sleep(time.Millisecond * 1000)
}
}The above program can be compiled and run without modification on an Arduino Uno, an Adafruit Circuit Playground Express, a Seeed Studio XIAO-ESP32S3 or any of the many supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
tinygo flash -target arduino-uno examples/blinky1WebAssembly
TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI).
TinyGo programs can run in Fastly Compute, Fermyon Spin, wazero and many other WebAssembly runtimes.
Here is a small TinyGo program for use by a WASI host application:
package main
//go:wasmexport add
func add(x, y uint32) uint32 {
return x + y
}This compiles the above TinyGo program for use on any WASI Preview 1 runtime:
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.goYou can also use the same syntax as Go 1.24+:
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.goInstallation
See the getting started instructions for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
Supported targets
Embedded
You can compile TinyGo programs for over 150 different microcontroller boards.
For more information, please see https://tinygo.org/docs/reference/microcontrollers/
WebAssembly
TinyGo programs can be compiled for both WASM and WASI targets.
For more information, see https://tinygo.org/docs/guides/webassembly/
Operating Systems
You can also compile programs for Linux, macOS, and Windows targets.
For more information:
Currently supported features:
For a description of currently supported Go language features, please see https://tinygo.org/lang-support/.
Documentation
Documentation is located on our web site at https://tinygo.org/.
You can find the web site code at https://github.com/tinygo-org/tinygo-site.
Getting help
If you're looking for a more interactive way to discuss TinyGo usage or
development, we have a #TinyGo channel
on the Gophers Slack.
If you need an invitation for the Gophers Slack, you can generate one here which
should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.org
Contributing
Your contributions are welcome!
Please take a look at our Contributing page on our web site for details.
Project Scope
Goals:
- Have very small binary sizes. Don't pay for what you don't use.
- Support for most common microcontroller boards.
- Be usable on the web using WebAssembly.
- Good CGo support, with no more overhead than a regular function call.
- Support most standard library packages and compile most Go code without modification.
Non-goals:
- Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
- Be as fast as
gc. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching. - Be able to compile every Go program out there.
Why this project exists
We never expected Go to be an embedded language, and so itโs got serious problems...
-- Rob Pike, GopherCon 2014 Opening Keynote
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to emgo but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
The original reasoning was: if Python can run on microcontrollers, then certainly Go should be able to run on even lower level micros.
License
This project is licensed under the BSD 3-clause license, just like the Go project itself.
Some code has been copied from the LLVM project and is therefore licensed under a variant of the Apache 2.0 license. This has been clearly indicated in the header of these files.
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
File: docs/index.rst
.. TinyGo documentation master file, created by
sphinx-quickstart on Sat Sep 22 15:05:19 2018.
You can adapt this file completely to your liking, but it should at least
contain the root toctree directive.
The TinyGo site has moved!
Contents:
.. toctree::
:maxdepth: 2
moved
File: docs/moved.rst
.. _moved:
.. highlight:: none
The TinyGo Docs Site Has Moved
The documentation web site for TinyGo has moved.
You can find the new web site at tinygo.org <https://tinygo.org/>_
Thank you!
File: hooks/README.md
Hooks for Docker Hub
Files in this directory are custom commands to be run during the different Docker Hub build phases.
See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks
File: interp/README.md
Partial evaluation of initialization code in Go
For several reasons related to code size and memory consumption (see below), it
is best to try to evaluate as much initialization code at compile time as
possible and only run unknown expressions (e.g. external calls) at runtime. This
is in practice a partial evaluator of the runtime.initAll function, which
calls each package initializer.
This package is a rewrite of a previous partial evaluator that worked
directly on LLVM IR and used the module and LLVM constants as intermediate
values. This newer version instead uses a mostly Go intermediate form. It
compiles functions and extracts relevant data first (compiler.go), then
executes those functions (interpreter.go) in a memory space that can be
rolled back per function (memory.go). This means that it is not necessary to
scan functions to see whether they can be run at compile time, which was very
error prone. Instead it just tries to execute everything and if it hits
something it cannot interpret (such as a store to memory-mapped I/O) it rolls
back the execution of that function and runs the function at runtime instead.
All in all, this design provides several benefits:
- Much better error handling. By being able to revert to runtime execution
without the need for scanning functions, this version is able to
automatically work around many bugs in the previous implementation. - More correct memory model. This is not inherent to the new design, but the
new design also made the memory model easier to reason about. - Faster execution of initialization code. While it is not much faster for
normal interpretation (maybe 25% or so) due to the compilation overhead,
it should be a whole lot faster for loops as it doesn't have to call into
LLVM (via CGo) for every operation.
As mentioned, this partial evaluator comes in three parts: a compiler, an
interpreter, and a memory manager.
Compiler
The main task of the compiler is that it extracts all necessary data from
every instruction in a function so that when this instruction is interpreted,
no additional CGo calls are necessary. This is not currently done for all
instructions (runtime.alloc is a notable exception), but at least it does
so for the vast majority of instructions.
Interpreter
The interpreter runs an instruction just like it would if it were executed
'for real'. The vast majority of instructions can be executed at compile
time. As indicated above, some instructions need to be executed at runtime
instead.
Memory
Memory is represented as objects (the object type) that contains data that
will eventually be stored in a global and values (the value interface) that
can be worked with while running the interpreter. Values therefore are only
used locally and are always passed by value (just like most LLVM constants)
while objects represent the backing storage (like LLVM globals). Some values
are pointer values, and point to an object.
Importantly, this partial evaluator can roll back the execution of a
function. This is implemented by creating a new memory view per function
activation, which makes sure that any change to a global (such as a store
instruction) is stored in the memory view. It creates a copy of the object
and stores that in the memory view to be modified. Once the function has
executed successfully, all these modified objects are then copied into the
parent function, up to the root function invocation which (on successful
execution) writes the values back into the LLVM module. This way, function
invocations can be rolled back without leaving a trace.
Pointer values point to memory objects, but not to a particular memory
object. Every memory object is given an index, and pointers use that index to
look up the current active object for the pointer to load from or to copy
when storing to it.
Rolling back a function should roll back everything, including the few
instructions emitted at runtime. This is done by treating instructions much
like memory objects and removing the created instructions when necessary.
Why is this necessary?
A partial evaluator is hard to get right, so why go through all the trouble of
writing one?
The answer is that globals with initializers are much easier to optimize by
LLVM than initialization code. Also, there are a few other benefits:
- Dead globals are trivial to optimize away.
- Constant globals are easier to detect. Remember that Go does not have global
constants in the same sense as that C has them. Constants are useful because
they can be propagated and provide some opportunities for other
optimizations (like dead code elimination when branching on the contents of
a global). - Constants are much more efficient on microcontrollers, as they can be
allocated in flash instead of RAM.
The Go SSA package does not create constant initializers for globals.
Instead, it emits initialization functions, so if you write the following:
var foo = []byte{1, 2, 3, 4}It would generate something like this:
var foo []byte
func init() {
foo = make([]byte, 4)
foo[0] = 1
foo[1] = 2
foo[2] = 3
foo[3] = 4
}This is of course hugely wasteful, it's much better to create foo as a
global array instead of initializing it at runtime.
For more details, see this section of the
documentation.
File: src/examples/wasm/main/README.md
WebAssembly main execution example
A simple hello world that prints to the browser console.
License
Note that index.html is copied almost verbatim from the Go 1.12 source at$GOROOT/misc/wasm/wasm_exec.html. Its license applies to this file.
File: src/examples/wasm/README.md
TinyGo WebAssembly examples
The examples here show two different ways of using WebAssembly with TinyGo:
- Defining and exporting functions via the
//export <name>directive. See
the export folder for an example of this. Additionally, the Wasm
module (which has a default value ofenv) can be specified using//go:wasm-module <module>. - Defining and executing a
func main(). This is similar to how the Go
standard library implementation works. See the main folder for an
example of this.
Building
Build using the tinygo compiler:
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.goThis creates a wasm.wasm file, which we can load in JavaScript and execute in
a browser.
Next, choose which example you want to use:
- callback: Defines and configures callbacks in Wasm.
- export: Defines callbacks in Wasm, but configures them in JavaScript.
- invoke: Invokes a function defined in JavaScript from Wasm.
- main: Prints a message to the JavaScript console from Wasm.
- slices: Splits an Array defined in JavaScript from Wasm.
Let's say you chose main, you'd build it like so:
$ make mainRunning
Start the local web server:
$ go run server.go
Serving ./html on http://localhost:8080Use your web browser to visit http://localhost:8080.
- Tip: Open the browser development tools (e.g. Right-click, Inspect in
FireFox) to see console output.
How it works
Execution of the contents require a few JavaScript helper functions which are
called from WebAssembly.
We have defined these in wasm_exec.js. It is
based on $GOROOT/misc/wasm/wasm_exec.js from the standard library, but is
slightly different. Ensure you are using the same version of wasm_exec.js as
the version of tinygo you are using to compile.
The general steps required to run the WebAssembly file in the browser includes
loading it into JavaScript with WebAssembly.instantiateStreaming, orWebAssembly.instantiate in some browsers:
const go = new Go(); // Defined in wasm_exec.js
const WASM_URL = 'wasm.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}If you have used explicit exports, you can call them by invoking them under thewasm.exports namespace. See the export directory for an
example of this.
In addition to the JavaScript, it is important the wasm file is served with theContent-Type
header set to application/wasm. Without it, most browsers won't run it.
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./html"
func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Add("Cache-Control", "no-cache")
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))}This simple server serves anything inside the ./html directory on port8080, setting any *.wasm files Content-Type header appropriately.
For development purposes (only!), it also sets the Cache-Control header
so your browser doesn't cache the files. This is useful while developing, to
ensure your browser displays the newest wasm when you recompile.
In a production environment you probably wouldn't want to set theCache-Control header like this. Caching is generally beneficial for end
users.
Further information on the Cache-Control header can be found here:
File: src/examples/systick/README.md
TinyGo ARM SysTick example
This example uses the ARM System Timer to blink an LED. The timer fires
an interrupt 10 times per second. The interrupt handler toggles the LED on
and off.
Many ARM-based chips have this timer feature. If you run the example and the
LED blinks, then you have one.
The System Timer runs from a cycle counter. The more cycles, the slower the
LED will blink. This counter is 24 bits wide, which places an upper bound on
the number of cycles, and the slowness of the blinking.
2. Official Technical Reference & Guides (tinygo-org/tinygo-site)
TinyGo Web Site
This is the web site for TinyGo.
Built using Hugo:
and the Hugo "Docsy" theme:
https://github.com/google/docsy
Prerequisites
Install Hugo command line tool for your
operating system with the "extended". Note that the most recent compatible
version of Hugo is v0.139.5.
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/[email protected]Hugo will automatically download the docsy
NodeJS modules needed for processing the site CSS.
Installation
Clone this repo using:
git clone [email protected]:tinygo-org/tinygo-site.gitChange directories into the tinygo-site directory:
cd tinygo-siteYou are now able to run the site locally, "Docsy" theme will be automatically downloaded as module:
hugo serveOnce the site code is running locally, you can navigate to it by going to http://localhost:1313
Deploy to Netlify
Pushing to the release branch automatically deploys the latest site to Netlify.
That's it.
TODO:
- ?