Browse Source

all: add WebAssembly backend

pull/45/head
Ayke van Laethem 6 years ago
parent
commit
e5e09747f0
No known key found for this signature in database GPG Key ID: E97FF5335DFDFDED
  1. 3
      .travis.yml
  2. 2
      Dockerfile
  3. 7
      docs/installation.rst
  4. 27
      main.go
  5. 9
      src/examples/wasm/wasm.go
  6. 15
      src/examples/wasm/wasm.html
  7. 55
      src/examples/wasm/wasm.js
  8. 7
      src/runtime/arch_arm.go
  9. 7
      src/runtime/arch_avr.go
  10. 7
      src/runtime/arch_wasm.go
  11. 3
      src/runtime/gc.go
  12. 54
      src/runtime/runtime_wasm.go
  13. 8
      target.go
  14. 10
      targets/wasm.json
  15. 2
      targets/wasm.syms

3
.travis.yml

@ -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://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 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
install:
@ -27,3 +27,4 @@ script:
- 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.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
- tinygo build -o test.wasm.wasm -target=wasm examples/test

2
Dockerfile

@ -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 - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm7
apt-get install -y libllvm7 lld-7
ENTRYPOINT ["/go/bin/tinygo"]

7
docs/installation.rst

@ -44,6 +44,13 @@ needs the following tools:
``avr-gcc``.
* ``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
------------

27
main.go

@ -91,13 +91,15 @@ func Compile(pkgName, outpath, opt string, spec *TargetSpec, printIR, dumpSSA, d
}
// Generate output.
if strings.HasSuffix(outpath, ".o") {
outext := filepath.Ext(outpath)
switch outext {
case ".o":
return c.EmitObject(outpath)
} else if strings.HasSuffix(outpath, ".bc") {
case ".bc":
return c.EmitBitcode(outpath)
} else if strings.HasSuffix(outpath, ".ll") {
case ".ll":
return c.EmitText(outpath)
} else {
default:
// Act as a compiler driver.
// 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 ext == ".hex" || ext == ".bin" {
if outext == ".hex" || outext == ".bin" {
// 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{
".hex": "ihex",
".bin": "binary",
}[ext]
}[outext]
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@ -386,7 +387,7 @@ func main() {
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
printIR := flag.Bool("printir", false, "print LLVM IR")
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)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
@ -415,7 +416,11 @@ func main() {
usage()
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 {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
@ -443,7 +448,7 @@ func main() {
os.Exit(1)
}
var err error
if *target == llvm.DefaultTargetTriple() {
if *target == "" {
err = Run(flag.Arg(0))
} else {
err = Emulate(flag.Arg(0), *target, *opt)

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

@ -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

@ -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();

7
src/runtime/arch_arm.go

@ -2,6 +2,10 @@
package runtime
import (
"unsafe"
)
const GOARCH = "arm"
// 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).
const TargetBits = 32
//go:extern _heap_start
var heapStart unsafe.Pointer

7
src/runtime/arch_avr.go

@ -2,6 +2,10 @@
package runtime
import (
"unsafe"
)
const GOARCH = "avr"
// 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).
const TargetBits = 8
//go:extern _heap_start
var heapStart unsafe.Pointer

7
src/runtime/arch_wasm.go

@ -2,6 +2,10 @@
package runtime
import (
"unsafe"
)
const GOARCH = "wasm"
// 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).
const TargetBits = 32
//go:extern __heap_base
var heapStart unsafe.Pointer

3
src/runtime/gc.go

@ -6,9 +6,6 @@ import (
"unsafe"
)
//go:extern _heap_start
var heapStart unsafe.Pointer
var heapptr = uintptr(unsafe.Pointer(&heapStart))
func alloc(size uintptr) unsafe.Pointer {

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
}

8
target.go

@ -7,6 +7,8 @@ import (
"path/filepath"
"runtime"
"strings"
"github.com/aykevl/go-llvm"
)
// Target specification for a given target. Used for bare metal targets.
@ -30,6 +32,10 @@ type TargetSpec struct {
// Load a target specification
func LoadTarget(target string) (*TargetSpec, error) {
if target == "" {
target = llvm.DefaultTargetTriple()
}
spec := &TargetSpec{
Triple: target,
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.
return nil, err
} else {
// No target spec available. This is fine.
// No target spec available. Use the default one.
}
return spec, nil

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

@ -0,0 +1,2 @@
io_get_stdout
resource_write
Loading…
Cancel
Save