TinyGo uses a different representation for some data types than standard Go.


A string is encoded as a {ptr, len} tuple. The type is actually defined in the runtime as runtime._string, in src/runtime/string.go. That file also contains some compiler intrinsics for dealing with strings and UTF-8.


A slice is encoded as a {ptr, len, cap} tuple. There is no runtime definition of it as slices are a generic type and the pointer type is different for each slice. That said, the bit layout is exactly the same for every slice and generic copy and append functions are implemented in src/runtime/slice.go.


Arrays are simple: they are simply lowered to a LLVM array type.


Complex numbers are implemented the same way as Clang implements them: as a struct with two float32 or float64 elements.


The map type is a very complex type and is implemented as an (incomplete) hashmap. It is defined as runtime.hashmap in src/runtime/hashmap.go. As maps are reference types, they are lowered to a pointer to the aforementioned struct. See for example runtime.hashmapMake that is the compiler intrinsic to create a new hashmap.


An interface is a {typecode, value} tuple and is defined as runtime._interface in src/runtime/interface.go. The typecode is a small integer unique to the type of the value. See interface.go for a detailed description of how typeasserts and interface calls are implemented.

function value

A function value is a fat function pointer in the form of {context, function pointer} where context is a pointer which may have any value. See calling convention for details. The function pointer may be a real pointer or an arbitrary number, depending on the target platform.


Goroutines are implemented differently depending on the platform.

  • For most platforms, it is implemented as a linked list of LLVM coroutines. Every blocking call will create a new coroutine and pass itself to the coroutine as a parameter (see calling convention). The callee then re-activates the caller once it would otherwise return to the parent. Non-blocking calls are normal calls, unaware of the fact that they’re running on a particular goroutine. For details, see src/runtime/scheduler.go. This is rather expensive but has the advantage of being portable and requiring only a single stack.
  • For Cortex-M (which includes most supported microcontrollers as of this time), a real stack is allocated and scheduling happens much like the main Go implementation by saving and restoring registers in assembly.