diff options
author | Ayke van Laethem <[email protected]> | 2018-10-18 15:15:29 +0200 |
---|---|---|
committer | Ayke van Laethem <[email protected]> | 2018-10-21 19:47:47 +0200 |
commit | e5e09747f0bb390713e26ebbeaab6dfa885ded1c (patch) | |
tree | 9a24fcdf930d4c30e551b6d2f71d181c22ac4c5b | |
parent | a51e04c550d82b239c8c64c57efcad455647297f (diff) | |
download | tinygo-e5e09747f0bb390713e26ebbeaab6dfa885ded1c.tar.gz tinygo-e5e09747f0bb390713e26ebbeaab6dfa885ded1c.zip |
all: add WebAssembly backend
-rw-r--r-- | .travis.yml | 3 | ||||
-rw-r--r-- | Dockerfile | 2 | ||||
-rw-r--r-- | docs/installation.rst | 7 | ||||
-rw-r--r-- | main.go | 27 | ||||
-rw-r--r-- | src/examples/wasm/wasm.go | 9 | ||||
-rw-r--r-- | src/examples/wasm/wasm.html | 15 | ||||
-rw-r--r-- | src/examples/wasm/wasm.js | 55 | ||||
-rw-r--r-- | src/runtime/arch_arm.go | 7 | ||||
-rw-r--r-- | src/runtime/arch_avr.go | 7 | ||||
-rw-r--r-- | src/runtime/arch_wasm.go | 7 | ||||
-rw-r--r-- | src/runtime/gc.go | 3 | ||||
-rw-r--r-- | src/runtime/runtime_wasm.go | 54 | ||||
-rw-r--r-- | target.go | 8 | ||||
-rw-r--r-- | targets/wasm.json | 10 | ||||
-rw-r--r-- | targets/wasm.syms | 2 |
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 ------------ @@ -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 +} @@ -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 |