all: add WebAssembly backend
Этот коммит содержится в:
родитель
a51e04c550
коммит
e5e09747f0
15 изменённых файлов: 199 добавлений и 17 удалений
|
@ -8,7 +8,7 @@ before_install:
|
||||||
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
||||||
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install llvm-7-dev clang-7 gcc-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
- sudo apt-get install llvm-7-dev clang-7 lld-7 gcc-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||||
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
@ -27,3 +27,4 @@ script:
|
||||||
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||||
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||||
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
|
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
|
||||||
|
- tinygo build -o test.wasm.wasm -target=wasm examples/test
|
||||||
|
|
|
@ -22,6 +22,6 @@ COPY --from=0 /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y libllvm7
|
apt-get install -y libllvm7 lld-7
|
||||||
|
|
||||||
ENTRYPOINT ["/go/bin/tinygo"]
|
ENTRYPOINT ["/go/bin/tinygo"]
|
||||||
|
|
|
@ -44,6 +44,13 @@ needs the following tools:
|
||||||
``avr-gcc``.
|
``avr-gcc``.
|
||||||
* ``avrdude`` for flashing to an Arduino.
|
* ``avrdude`` for flashing to an Arduino.
|
||||||
|
|
||||||
|
WebAssembly
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
The WebAssembly backend only needs a special linker from the LLVM project:
|
||||||
|
|
||||||
|
* LLVM linker (``ld.lld-7``) for linking WebAssembly files together.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
27
main.go
27
main.go
|
@ -91,13 +91,15 @@ func Compile(pkgName, outpath, opt string, spec *TargetSpec, printIR, dumpSSA, d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate output.
|
// Generate output.
|
||||||
if strings.HasSuffix(outpath, ".o") {
|
outext := filepath.Ext(outpath)
|
||||||
|
switch outext {
|
||||||
|
case ".o":
|
||||||
return c.EmitObject(outpath)
|
return c.EmitObject(outpath)
|
||||||
} else if strings.HasSuffix(outpath, ".bc") {
|
case ".bc":
|
||||||
return c.EmitBitcode(outpath)
|
return c.EmitBitcode(outpath)
|
||||||
} else if strings.HasSuffix(outpath, ".ll") {
|
case ".ll":
|
||||||
return c.EmitText(outpath)
|
return c.EmitText(outpath)
|
||||||
} else {
|
default:
|
||||||
// Act as a compiler driver.
|
// Act as a compiler driver.
|
||||||
|
|
||||||
// Create a temporary directory for intermediary files.
|
// Create a temporary directory for intermediary files.
|
||||||
|
@ -160,14 +162,13 @@ func Compile(pkgName, outpath, opt string, spec *TargetSpec, printIR, dumpSSA, d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := filepath.Ext(outpath)
|
if outext == ".hex" || outext == ".bin" {
|
||||||
if ext == ".hex" || ext == ".bin" {
|
|
||||||
// Get an Intel .hex file or .bin file from the .elf file.
|
// Get an Intel .hex file or .bin file from the .elf file.
|
||||||
tmppath = filepath.Join(dir, "main"+ext)
|
tmppath = filepath.Join(dir, "main"+outext)
|
||||||
format := map[string]string{
|
format := map[string]string{
|
||||||
".hex": "ihex",
|
".hex": "ihex",
|
||||||
".bin": "binary",
|
".bin": "binary",
|
||||||
}[ext]
|
}[outext]
|
||||||
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
|
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
|
@ -386,7 +387,7 @@ func main() {
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||||
target := flag.String("target", llvm.DefaultTargetTriple(), "LLVM target")
|
target := flag.String("target", "", "LLVM target")
|
||||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||||
|
@ -415,7 +416,11 @@ func main() {
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err := Build(flag.Arg(0), *outpath, *target, *opt, *printIR, *dumpSSA, !*nodebug, *printSize)
|
target := *target
|
||||||
|
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
||||||
|
target = "wasm"
|
||||||
|
}
|
||||||
|
err := Build(flag.Arg(0), *outpath, target, *opt, *printIR, *dumpSSA, !*nodebug, *printSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "error:", err)
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -443,7 +448,7 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
if *target == llvm.DefaultTargetTriple() {
|
if *target == "" {
|
||||||
err = Run(flag.Arg(0))
|
err = Run(flag.Arg(0))
|
||||||
} else {
|
} else {
|
||||||
err = Emulate(flag.Arg(0), *target, *opt)
|
err = Emulate(flag.Arg(0), *target, *opt)
|
||||||
|
|
9
src/examples/wasm/wasm.go
Обычный файл
9
src/examples/wasm/wasm.go
Обычный файл
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export add
|
||||||
|
func add(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
15
src/examples/wasm/wasm.html
Обычный файл
15
src/examples/wasm/wasm.html
Обычный файл
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<title>Go WebAssembly</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<script src="wasm.js" async></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>WebAssembly</h1>
|
||||||
|
<p>Add two numbers, using WebAssembly:</p>
|
||||||
|
<input type="number" id="a" value="2"/> + <input type="number" id="b" value="2"/> = <input type="number" id="result" readonly/>
|
||||||
|
</body>
|
||||||
|
</html>
|
55
src/examples/wasm/wasm.js
Обычный файл
55
src/examples/wasm/wasm.js
Обычный файл
|
@ -0,0 +1,55 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const WASM_URL = '../../../wasm.wasm';
|
||||||
|
|
||||||
|
var wasm;
|
||||||
|
var logLine = [];
|
||||||
|
var memory8;
|
||||||
|
|
||||||
|
var importObject = {
|
||||||
|
env: {
|
||||||
|
io_get_stdout: function() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
resource_write: function(fd, ptr, len) {
|
||||||
|
if (fd == 1) {
|
||||||
|
for (let i=0; i<len; i++) {
|
||||||
|
let c = memory8[ptr+i];
|
||||||
|
if (c == 13) { // CR
|
||||||
|
// ignore
|
||||||
|
} else if (c == 10) { // LF
|
||||||
|
// write line
|
||||||
|
let line = new TextDecoder("utf-8").decode(new Uint8Array(logLine));
|
||||||
|
logLine = [];
|
||||||
|
console.log(line);
|
||||||
|
} else {
|
||||||
|
logLine.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('invalid file descriptor:', fd);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateResult() {
|
||||||
|
let a = parseInt(document.querySelector('#a').value);
|
||||||
|
let b = parseInt(document.querySelector('#b').value);
|
||||||
|
let result = wasm.exports.add(a, b);
|
||||||
|
document.querySelector('#result').value = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
document.querySelector('#a').oninput = updateResult;
|
||||||
|
document.querySelector('#b').oninput = updateResult;
|
||||||
|
|
||||||
|
WebAssembly.instantiateStreaming(fetch(WASM_URL), importObject).then(function(obj) {
|
||||||
|
wasm = obj.instance;
|
||||||
|
memory8 = new Uint8Array(wasm.exports.memory.buffer);
|
||||||
|
wasm.exports.cwa_main();
|
||||||
|
updateResult();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
const GOARCH = "arm"
|
const GOARCH = "arm"
|
||||||
|
|
||||||
// The length type used inside strings and slices.
|
// The length type used inside strings and slices.
|
||||||
|
@ -9,3 +13,6 @@ type lenType uint32
|
||||||
|
|
||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
|
//go:extern _heap_start
|
||||||
|
var heapStart unsafe.Pointer
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
const GOARCH = "avr"
|
const GOARCH = "avr"
|
||||||
|
|
||||||
// The length type used inside strings and slices.
|
// The length type used inside strings and slices.
|
||||||
|
@ -9,3 +13,6 @@ type lenType uint16
|
||||||
|
|
||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 8
|
const TargetBits = 8
|
||||||
|
|
||||||
|
//go:extern _heap_start
|
||||||
|
var heapStart unsafe.Pointer
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
const GOARCH = "wasm"
|
const GOARCH = "wasm"
|
||||||
|
|
||||||
// The length type used inside strings and slices.
|
// The length type used inside strings and slices.
|
||||||
|
@ -9,3 +13,6 @@ type lenType uint32
|
||||||
|
|
||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
|
//go:extern __heap_base
|
||||||
|
var heapStart unsafe.Pointer
|
||||||
|
|
|
@ -6,9 +6,6 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:extern _heap_start
|
|
||||||
var heapStart unsafe.Pointer
|
|
||||||
|
|
||||||
var heapptr = uintptr(unsafe.Pointer(&heapStart))
|
var heapptr = uintptr(unsafe.Pointer(&heapStart))
|
||||||
|
|
||||||
func alloc(size uintptr) unsafe.Pointer {
|
func alloc(size uintptr) unsafe.Pointer {
|
||||||
|
|
54
src/runtime/runtime_wasm.go
Обычный файл
54
src/runtime/runtime_wasm.go
Обычный файл
|
@ -0,0 +1,54 @@
|
||||||
|
// +build wasm,!arm,!avr
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
type timeUnit int64
|
||||||
|
|
||||||
|
const tickMicros = 1
|
||||||
|
|
||||||
|
var timestamp timeUnit
|
||||||
|
|
||||||
|
// CommonWA: io_get_stdout
|
||||||
|
func _Cfunc_io_get_stdout() int32
|
||||||
|
|
||||||
|
// CommonWA: resource_write
|
||||||
|
func _Cfunc_resource_write(id int32, ptr *uint8, len int32) int32
|
||||||
|
|
||||||
|
var stdout int32
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
stdout = _Cfunc_io_get_stdout()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export _start
|
||||||
|
func _start() {
|
||||||
|
initAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:export cwa_main
|
||||||
|
func cwa_main() {
|
||||||
|
initAll() // _start is not called by olin/cwa so has to be called here
|
||||||
|
mainWrapper()
|
||||||
|
}
|
||||||
|
|
||||||
|
func putchar(c byte) {
|
||||||
|
_Cfunc_resource_write(stdout, &c, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sleepTicks(d timeUnit) {
|
||||||
|
// TODO: actually sleep here for the given time.
|
||||||
|
timestamp += d
|
||||||
|
}
|
||||||
|
|
||||||
|
func ticks() timeUnit {
|
||||||
|
return timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Align on word boundary.
|
||||||
|
func align(ptr uintptr) uintptr {
|
||||||
|
return (ptr + 3) &^ 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func abort() {
|
||||||
|
// TODO
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aykevl/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Target specification for a given target. Used for bare metal targets.
|
// Target specification for a given target. Used for bare metal targets.
|
||||||
|
@ -30,6 +32,10 @@ type TargetSpec struct {
|
||||||
|
|
||||||
// Load a target specification
|
// Load a target specification
|
||||||
func LoadTarget(target string) (*TargetSpec, error) {
|
func LoadTarget(target string) (*TargetSpec, error) {
|
||||||
|
if target == "" {
|
||||||
|
target = llvm.DefaultTargetTriple()
|
||||||
|
}
|
||||||
|
|
||||||
spec := &TargetSpec{
|
spec := &TargetSpec{
|
||||||
Triple: target,
|
Triple: target,
|
||||||
BuildTags: []string{runtime.GOOS, runtime.GOARCH},
|
BuildTags: []string{runtime.GOOS, runtime.GOARCH},
|
||||||
|
@ -54,7 +60,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||||
// Expected a 'file not found' error, got something else.
|
// Expected a 'file not found' error, got something else.
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
// No target spec available. This is fine.
|
// No target spec available. Use the default one.
|
||||||
}
|
}
|
||||||
|
|
||||||
return spec, nil
|
return spec, nil
|
||||||
|
|
10
targets/wasm.json
Обычный файл
10
targets/wasm.json
Обычный файл
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"llvm-target": "wasm32-unknown-unknown-wasm",
|
||||||
|
"build-tags": ["js", "wasm"],
|
||||||
|
"linker": "ld.lld-7",
|
||||||
|
"pre-link-args": [
|
||||||
|
"-flavor", "wasm",
|
||||||
|
"-allow-undefined-file", "targets/wasm.syms"
|
||||||
|
],
|
||||||
|
"emulator": ["cwa"]
|
||||||
|
}
|
2
targets/wasm.syms
Обычный файл
2
targets/wasm.syms
Обычный файл
|
@ -0,0 +1,2 @@
|
||||||
|
io_get_stdout
|
||||||
|
resource_write
|
Загрузка…
Создание таблицы
Сослаться в новой задаче