aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2018-10-18 15:15:29 +0200
committerAyke van Laethem <[email protected]>2018-10-21 19:47:47 +0200
commite5e09747f0bb390713e26ebbeaab6dfa885ded1c (patch)
tree9a24fcdf930d4c30e551b6d2f71d181c22ac4c5b
parenta51e04c550d82b239c8c64c57efcad455647297f (diff)
downloadtinygo-e5e09747f0bb390713e26ebbeaab6dfa885ded1c.tar.gz
tinygo-e5e09747f0bb390713e26ebbeaab6dfa885ded1c.zip
all: add WebAssembly backend
-rw-r--r--.travis.yml3
-rw-r--r--Dockerfile2
-rw-r--r--docs/installation.rst7
-rw-r--r--main.go27
-rw-r--r--src/examples/wasm/wasm.go9
-rw-r--r--src/examples/wasm/wasm.html15
-rw-r--r--src/examples/wasm/wasm.js55
-rw-r--r--src/runtime/arch_arm.go7
-rw-r--r--src/runtime/arch_avr.go7
-rw-r--r--src/runtime/arch_wasm.go7
-rw-r--r--src/runtime/gc.go3
-rw-r--r--src/runtime/runtime_wasm.go54
-rw-r--r--target.go8
-rw-r--r--targets/wasm.json10
-rw-r--r--targets/wasm.syms2
15 files changed, 199 insertions, 17 deletions
diff --git a/.travis.yml b/.travis.yml
index 4b940333c..d6ed9577e 100644
--- a/.travis.yml
+++ b/.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
diff --git a/Dockerfile b/Dockerfile
index 61904a3ae..09486d666 100644
--- a/Dockerfile
+++ b/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"]
diff --git a/docs/installation.rst b/docs/installation.rst
index 1cf8dc7ba..7a3670adf 100644
--- a/docs/installation.rst
+++ b/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
------------
diff --git a/main.go b/main.go
index 4aa4731d6..e53ec606f 100644
--- a/main.go
+++ b/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)
diff --git a/src/examples/wasm/wasm.go b/src/examples/wasm/wasm.go
new file mode 100644
index 000000000..846fa7499
--- /dev/null
+++ b/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
+}
diff --git a/src/examples/wasm/wasm.html b/src/examples/wasm/wasm.html
new file mode 100644
index 000000000..d2e1bc15e
--- /dev/null
+++ b/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>
diff --git a/src/examples/wasm/wasm.js b/src/examples/wasm/wasm.js
new file mode 100644
index 000000000..6b0aef7eb
--- /dev/null
+++ b/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();
diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go
index 1f15a5c58..46c2a09e2 100644
--- a/src/runtime/arch_arm.go
+++ b/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
diff --git a/src/runtime/arch_avr.go b/src/runtime/arch_avr.go
index 2c92967e7..e7d2b5ff8 100644
--- a/src/runtime/arch_avr.go
+++ b/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
diff --git a/src/runtime/arch_wasm.go b/src/runtime/arch_wasm.go
index e7eaaf293..bc48ce0b2 100644
--- a/src/runtime/arch_wasm.go
+++ b/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
diff --git a/src/runtime/gc.go b/src/runtime/gc.go
index 8fd1f068b..90df58595 100644
--- a/src/runtime/gc.go
+++ b/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 {
diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go
new file mode 100644
index 000000000..cf4eca693
--- /dev/null
+++ b/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
+}
diff --git a/target.go b/target.go
index 2916912ac..968457590 100644
--- a/target.go
+++ b/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
diff --git a/targets/wasm.json b/targets/wasm.json
new file mode 100644
index 000000000..2da5204f4
--- /dev/null
+++ b/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"]
+}
diff --git a/targets/wasm.syms b/targets/wasm.syms
new file mode 100644
index 000000000..e48be7e79
--- /dev/null
+++ b/targets/wasm.syms
@@ -0,0 +1,2 @@
+io_get_stdout
+resource_write