tinygo Documentation

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.

17,632 stars Go Go 1 file ยท ~4,847 tokens #adafruit#arduino#arm#avr#esp32#gpio#i2c#llvm
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy tinygo on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name tinygo --restart always tinygo-org/tinygo:latest

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:

go
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:

shell
tinygo flash -target arduino-uno examples/blinky1

WebAssembly

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:

go
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:

shell
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go

You can also use the same syntax as Go 1.24+:

shell
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go

Installation

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:

go
var foo = []byte{1, 2, 3, 4}

It would generate something like this:

go
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:

  1. 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 of env) can be specified using
    //go:wasm-module <module>.
  2. 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:

bash
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go

This 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:

bash
$ make main

Running

Start the local web server:

bash
$ go run server.go
Serving ./html on http://localhost:8080

Use 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, or
WebAssembly.instantiate in some browsers:

js
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 the
wasm.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 the
Content-Type
header set to application/wasm. Without it, most browsers won't run it.

go
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 port
8080, 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 the
Cache-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:

http://gohugo.io/

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.

text
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:

text
git clone [email protected]:tinygo-org/tinygo-site.git

Change directories into the tinygo-site directory:

text
cd tinygo-site

You are now able to run the site locally, "Docsy" theme will be automatically downloaded as module:

text
hugo serve

Once 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:

  • ?