编程 TinyGo 深度拆解:用 Go 写嵌入式和 WebAssembly,LLVM 后端是怎么做到的

2026-07-21 08:08:11 +0800 CST views 15

TinyGo 深度拆解:用 Go 写嵌入式和 WebAssembly,LLVM 后端是怎么做到的

来源:微信公众号

Go 的标准编译器(gc)是为服务器和云计算设计的,生成的二进制最小也有几 MB,跑在内存几百 KB 的微控制器上不现实。

TinyGo 解决的就是这个问题——它用 LLVM 做后端,能把 Go 代码编译到 AVR(Arduino)、ARM Cortex-M(BBC micro:bit、Adafruit)、RISC-V,还有 WebAssembly。编译出来的二进制可以小到几 KB。GitHub ⭐ 15K+,作者 TinyGo 社区。

为什么写 TinyGo

Go 的嵌入式能力一直被忽视。大家觉得 Go 就是写后端服务的,嵌入式是 C 和 Rust 的地盘。但 TinyGo 改变了这件事:有人用 Go 写了一个温湿度传感器的驱动,编译出来只有 8KB,直接刷到 Arduino Nano 上跑起来了。

TinyGo 不是简单地把 Go 裁小——它重新设计了一套编译流程:

  • 用 LLVM 做优化
  • 实现了 Go 的子集
  • 支持 goroutine 和 channel(在嵌入式上)
  • 还能编译成 WebAssembly 跑在浏览器里

项目信息

项目信息
GitHubgithub.com/tinygo-org/tinygo
Star15K+
语言Go + LLVM(C++)
协议BSD-3-Clause
维护方TinyGo 社区(Hybrid Group 赞助)
编译器后端LLVM
支持平台Arduino、BBC micro:bit、Adafruit、ESP32、ESP8266、STM32、RISC-V、WebAssembly
Go 版本兼容Go 1.22+
最小二进制几 KB(裸机 AVR)

Go 编译器 vs TinyGo:两套思路

Go 官方的编译器(gc)靠的是"快速编译 + 够用的性能",不做复杂优化,生成的二进制通常比较大(几 MB 起步)。

TinyGo 的思路完全不同:用 LLVM 做后端

LLVM 是编译器的"瑞士军刀"——Clang、Rust 编译器、Swift 编译器都用 LLVM 做后端。TinyGo 把 Go 代码先转成 LLVM IR(中间表示),然后让 LLVM 做优化和代码生成。

Go 源码 → TinyGo 前端(类型检查、AST→IR)→ LLVM IR → LLVM 优化 → 机器码/WebAssembly

这种方式的代价是编译速度比 gc 慢很多,但生成的二进制小得多。

嵌入式上的 Go:goroutine 在微控制器上

TinyGo 在微控制器上支持 goroutine 和 channel。

Arduino 的 ATmega328P 只有 2KB RAM、32KB Flash,在这种环境下跑 goroutine?听起来不可能。

TinyGo 的实现方式:每个 goroutine 的栈不是从堆上分配的,而是编译时静态分配的。TinyGo 在编译时分析每个 goroutine 的最大栈深度,预留固定大小的栈空间。

goroutine 的数量在编译时就确定了,不能像标准 Go 那样动态创建几万个。但对于嵌入式场景来说,十几个 goroutine 就够用了——一个读传感器、一个处理数据、一个控制 LED,很够了。

Channel 也是类似的处理:TinyGo 的 channel 是同步的(无缓冲或者固定缓冲),实现比标准 Go 简单得多。

// 这段代码可以编译到 Arduino 上
package main

import (
    "machine"
    "time"
)

func main() {
    led := machine.LED
    led.Configure(machine.PinConfig{Mode: machine.PinOutput})

    go blink(led, 200*time.Millisecond)
    go blink(led, 500*time.Millisecond)

    select {} // 阻塞,让 goroutine 跑
}

func blink(led machine.Pin, interval time.Duration) {
    for {
        led.High()
        time.Sleep(interval)
        led.Low()
        time.Sleep(interval)
    }
}

两个 goroutine 同时闪 LED,一个快一个慢。这在 Arduino 上真的能跑。

WebAssembly:Go 跑在浏览器里

TinyGo 的另一个重要场景是 WebAssembly(Wasm)。

Go 官方编译器也支持 Wasm 编译(GOOS=js GOARCH=wasm),但生成的二进制有 8MB+,浏览器加载要好几秒,基本没法用。

TinyGo 编译的 Wasm 二进制可以压到 几十 KB 到几百 KB。原因是 LLVM 做了激进的死代码消除——没用到的标准库函数不会被编译进去。

# 编译成 WebAssembly
tinygo build -o main.wasm -target=wasm ./cmd/main
维度Go 官方编译器TinyGo
Wasm 二进制大小~8MB~几十 KB
编译速度
Go 特性完整度完整子集
浏览器加载时间几秒毫秒级

硬件抽象层:machine 包

TinyGo 提供了一套硬件抽象层(machine 包),让你用统一的 API 操作不同硬件的 GPIO、SPI、I2C、UART、ADC 等:

import "machine"

// 不管底层是 Arduino 还是 ESP32,API 一样
pin := machine.D2
pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
pin.High()

这套抽象层让 Go 代码可以在不同硬件之间移植。TinyGo 社区还维护了很多外设驱动(tinygo.org/x/drivers):温湿度传感器、OLED 屏幕、步进电机、GPS 模块……都是纯 Go 写的。

编译流程:LLVM 的魔力

  1. Go 源码解析:TinyGo 用 Go 的标准 go/ast 包解析源码,做类型检查
  2. IR 生成:把 Go 的 AST 转成 LLVM IR。这步最复杂——需要处理 interface、goroutine、defer、recover 等特性
  3. LLVM 优化:死代码消除是这里做的——没用到的函数和全局变量会被删掉
  4. 代码生成:LLVM 生成目标平台的机器码(AVR、ARM、RISC-V)或者 WebAssembly
  5. 链接:跟 TinyGo 的 runtime(精简版 GC、调度器)链接,生成最终二进制

TinyGo 的 runtime 也是专门为嵌入式设计的:官方的 Go runtime 有几 MB,TinyGo 的 runtime 只有几 KB。GC 用的是标记-清除的简化版本,内存开销很小。

跟其他方案对比

维度TinyGoGo 官方编译器Rust + EmbassyC/Arduino
目标平台微控制器 + Wasm + Linux服务器/桌面微控制器 + 通用微控制器嵌入式
二进制大小几 KB ~ 几百 KB几 MB 起几 KB ~ 几十 KB几 KB
并发模型goroutine/channelgoroutine/channelasync/await无(中断驱动)
编译速度慢(LLVM)慢(LLVM)
内存安全有(GC)有(GC)有(所有权)
生态硬件驱动库服务器生态Embassy 生态巨大

TinyGo 是唯一一个让你在微控制器上用 goroutine 的编译器。

不足

  • 编译速度慢:LLVM 的优化是慢的,开发体验不太好
  • Go 特性不完整:reflection 支持有限,sync 包不完整,很多标准库依赖不可用
  • 调试困难:GDB 支持有限,fmt.Println 在微控制器上不一定能用
  • LLVM 依赖:需要安装 LLVM,不同平台版本不一,安装过程不如 Go 官方编译器丝滑
  • 社区规模:15K Star,但活跃贡献者不多

适用场景

  • 嵌入式开发:在 Arduino、ESP32、STM32 上写程序,又不想学 C 或 Rust
  • WebAssembly:需要在浏览器或边缘计算平台跑 Go 代码,官方编译器的 Wasm 太大
  • IoT 原型开发:快速搭一个 IoT 原型
  • 教育:用 TinyGo 教嵌入式编程,Go 的语法比 C 简单得多

总结

TinyGo 把 Go 带到了微控制器和浏览器里。它不是 Go 的替代品——不会用 TinyGo 来写后端服务。它是一个补充:在 Go 官方编译器覆盖不到的地方,TinyGo 补上了。

LLVM 后端是 TinyGo 的核心选择。代价是编译速度慢,收益是二进制小、优化好。对于嵌入式和 Wasm 场景,这个 trade-off 是值得的。

GitHub:https://github.com/tinygo-org/tinygo

推荐文章

淘宝npm镜像使用方法
2024-11-18 23:50:48 +0800 CST
Python 微软邮箱 OAuth2 认证 Demo
2024-11-20 15:42:09 +0800 CST
Rust 高性能 XML 读写库
2024-11-19 07:50:32 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
Vue3中的JSX有什么不同?
2024-11-18 16:18:49 +0800 CST
thinkphp swoole websocket 结合的demo
2024-11-18 10:18:17 +0800 CST
程序员茄子在线接单