aboutsummaryrefslogtreecommitdiffhomepage
path: root/docs/faq.rst
blob: 1ecbc7453d8e746eac91ca2782f225e58f5c91d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
.. faq:

Frequently Asked Questions
==========================


What is TinyGo exactly?
-----------------------

A new compiler and a new runtime implementation.

Specifically:

  * A new compiler using (mostly) the standard library to parse Go programs and
    using LLVM to optimize the code and generate machine code for the target
    architecture.

  * A new runtime library that implements some compiler intrinsics, like a
    memory allocator, a scheduler, and operations on strings. Also, some
    packages that are strongly connected to the runtime like the ``sync``
    package and the ``reflect`` package have been or will be re-implemented for
    use with this new compiler.


Why a new compiler?
-------------------

Why not modify the existing compiler to produce binaries for microcontrollers?

There are several reasons for this:

  * The standard Go compiler (``gc``) does not support instruction sets as used
    on microcontrollers:

      * The Thumb instruction set is unsupported, but it should be possible to
        add support for it as it already has an ARM backend.
      * The AVR instruction set (as used in the Arduino Uno) is unsupported and
        unlikely to be ever supported.

    Of course, it is possible to use ``gccgo``, but that has different problems
    (see below).

  * The runtime is really big. A standard 'hello world' on a desktop PC produces
    a binary of about 1MB, even when using the builtin ``println`` function and
    nothing else. All this overhead is due to the runtime. Of course, it may be
    possible to use a different runtime with the same compiler but that will be
    kind of painful as the exact ABI as used by the compiler has to be matched,
    limiting optimization opportunities (see below).

  * The compiler is optimized for speed, not for code size or memory
    consumption (which are usually far more important on MCUs). This results in
    design choices like allocating memory on every value → interface conversion
    while TinyGo sacrifices some performance for reduced GC pressure.

  * With the existing Go libraries for parsing Go code and the pretty awesome
    LLVM optimizer/backend it is relatively easy to get simple Go programs
    working with a very small binary size. Extra features can be added where
    needed in a pay-as-you-go manner similar to C++ avoiding their cost when
    unused. Most programs on microcontrollers are relatively small so a
    not-complete compiler is still useful.

  * The standard Go compilers do not allocate global variables as static data,
    but as zero-initialized data that is initialized during program startup.
    This is not a big deal on desktop computers but prevents allocating these
    values in flash on microcontrollers. Part of this is due to how the
    `language specification defines package initialization
    <https://golang.org/ref/spec#Package_initialization>`_, but this can be
    worked around to a large extent.

  * The standard Go compilers do a few special things for CGo calls. This is
    necessary because only Go code can use the (small) Go stack while C code
    will need a much bigger stack. A new compiler can avoid this limitation if
    it ensures stacks are big enough for C, greatly reducing the C ↔ Go calling
    overhead.

`At one point <https://github.com/aykevl/tinygo-gccgo>`_, a real Go compiler
had been used to produce binaries for various platforms, and the result was
painful enough to start writing a new compiler:

  * The ABI was fixed, so could not be optimized for speed. Also, the ABI
    didn't seem to be documented anywhere.

  * Working arount limitations in the ``go`` toolchain was rather burdensome
    and quite a big hack.

  * The binaries produced were quite bloated, for various reasons:

      * The Go calling convention places all arguments on the stack. Due to
        this, stack usage was really bad and code size was bigger than it
        needed to be.

      * Global initialization was very inefficient, see above.

      * There seemed to be no way to optimize across packages.


Why Go instead of Rust?
-----------------------

Rust is another "new" and safer language that is now made ready for embedded
processors. There is `a fairly active community around it
<https://rust-embedded.github.io/blog/>`_.

However, apart from personal language preference, Go has a few advantages:

  * Subjective, but in general Go is `easier to learn
    <https://matthias-endler.de/2017/go-vs-rust/>`_. Rust is in general far more
    complicated than Go, with difficult-to-grasp ownership rules, traits,
    generics, etc. Go prides itself on being a simple and slightly dumb
    language, sacrificing some expressiveness for readability.

  * Built-in support for concurrency with goroutines and channels that do not
    rely on a particular implementation threads. This avoids the need for a
    custom `RTOS-like framework <https://blog.japaric.io/rtfm-v2/>`_ or a
    `full-blown RTOS <https://github.com/rust-embedded/wg/issues/45>`_ with the
    associated API one has to learn. In Go, everything is handled by goroutines
    which are built into the language itself.

  * A batteries-included standard library that consists of loosely-coupled
    packages. Rust uses a monolithic standard library that is currently unusable
    on bare-metal, while the Go standard library is much more loosely coupled so
    is more likely to be (partially) supported. Also, non-standard packages in
    Go do not have to be marked with something like ``#![no_std]`` to be usable
    on bare metal. Note: most standard library packages cannot yet be compiled,
    but this situation will hopefully improve in the future.

At the same time, Rust has other advantages:

  * Unlike Go, Rust does not have a garbage collector by default and carefully
    written Rust code can avoid most or all uses of the heap. Go relies heavily
    on garbage collection and often implicitly allocates memory on the heap.

  * Rust has stronger memory-safety guarantees.

  * In general, Rust is more low-level and easier to support on a
    microcontroller. Of course, this doesn't mean one shouldn't try to run Go on
    a microcontroller, just that it is more difficult. When even dynamic
    languages like `Python <https://micropython.org/>`_, `Lua
    <https://nodemcu.readthedocs.io/en/master/>`_ and `JavaScript
    <https://www.espruino.com/>`_ can run on a microcontroller, then certainly
    Go can.


.. _faq-esp:

What about the ESP8266/ESP32?
-----------------------------

These chips use the rather obscure Xtensa instruction set. While a port of GCC
exists and Espressif provides precompiled GNU toolchains, there is no support
yet in LLVM (although there have been `multiple attempts
<http://lists.llvm.org/pipermail/llvm-dev/2018-July/124789.html>`_).

There are two ways these chips might be supported in the future, and both will
take a considerable amount of work:

  * The compiled LLVM IR can be converted into (ugly) C and then be compiled
    with a supported C compiler (like GCC for Xtensa). This has been `done
    before <https://github.com/JuliaComputing/llvm-cbe>`_ so should be doable.

  * One of the work-in-progress LLVM backends can be worked on to get it in a
    usable state. If this is finished, a true TinyGo port is possible.