Skip to content

builder: run binaryen after the linker when generating wasm #2035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ commands:
command: |
curl https://wasmtime.dev/install.sh -sSf | bash
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
install-binaryen:
steps:
- run:
name: "Install binaryen"
command: |
wget https://github.com/WebAssembly/binaryen/releases/download/version_101/binaryen-version_101-x86_64-linux.tar.gz
sudo tar --strip-components=1 -C /usr/local -xf binaryen-version_101-x86_64-linux.tar.gz
install-xtensa-toolchain:
parameters:
variant:
Expand Down Expand Up @@ -103,6 +110,7 @@ commands:
- install-node
- install-chrome
- install-wasmtime
- install-binaryen
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
Expand Down Expand Up @@ -144,8 +152,14 @@ commands:
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
avr-libc \
cmake \
ninja-build \
python3
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
- install-node
- install-wasmtime
- install-xtensa-toolchain:
Expand All @@ -166,11 +180,6 @@ commands:
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
Expand All @@ -179,6 +188,9 @@ commands:
key: llvm-build-11-linux-v4-assert
paths:
llvm-build
- run:
name: "Build Binaryen"
command: make lib/binaryen/lib/libbinaryen.a
- run: make ASSERT=1
- build-wasi-libc
- run:
Expand Down Expand Up @@ -212,8 +224,14 @@ commands:
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
avr-libc \
cmake \
ninja-build \
python3
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
- install-node
- install-wasmtime
- install-xtensa-toolchain:
Expand All @@ -234,11 +252,6 @@ commands:
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
Expand All @@ -247,6 +260,9 @@ commands:
key: llvm-build-11-linux-v4-noassert
paths:
llvm-build
- run:
name: "Build Binaryen"
command: make lib/binaryen/lib/libbinaryen.a
- build-wasi-libc
- run:
name: "Test TinyGo"
Expand Down Expand Up @@ -289,7 +305,7 @@ commands:
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu cmake ninja
- install-xtensa-toolchain:
variant: "macos"
- restore_cache:
Expand Down Expand Up @@ -320,8 +336,6 @@ commands:
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
Expand All @@ -330,6 +344,9 @@ commands:
key: llvm-build-11-macos-v5
paths:
llvm-build
- run:
name: "Build Binaryen"
command: make lib/binaryen/lib/libbinaryen.a
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v4
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/binaryen"]
path = lib/binaryen
url = https://github.com/WebAssembly/binaryen
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif

ifneq ("$(wildcard lib/binaryen/lib/libbinaryen.a)","")
CGO_CPPFLAGS+=-I$(abspath lib/binaryen/src/)
CGO_LDFLAGS+=$(abspath lib/binaryen/lib/libbinaryen.a)
endif
Comment on lines +98 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these flags should always be added when linking statically against LLVM.


clean:
@rm -rf build
Expand Down Expand Up @@ -168,6 +171,13 @@ $(LLVM_BUILDDIR)/build.ninja: llvm-source
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)

# Configure binaryen.
lib/binaryen/build.ninja:
cd lib/binaryen; cmake -G Ninja -DBUILD_STATIC_LIB=ON -DBUILD_LLVM_DWARF=OFF .

# Build binaryen.
lib/binaryen/lib/libbinaryen.a: lib/binaryen/build.ninja
ninja -C lib/binaryen lib/libbinaryen.a
Comment on lines +179 to +180
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a dependency of the tinygo: make target.


# Build wasi-libc sysroot
.PHONY: wasi-libc
Expand Down
20 changes: 13 additions & 7 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ jobs:
version: '1.17'
- checkout: self
fetchDepth: 1
- task: Bash@3
displayName: Install Dependencies
inputs:
targetType: inline
script: |
# install ninja
choco install ninja
# hack ninja to use fewer jobs
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
# install qemu
choco install qemu --version=2020.06.12
- task: Cache@2
displayName: Cache LLVM source
inputs:
Expand All @@ -37,18 +48,13 @@ jobs:
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
choco install ninja
# hack ninja to use fewer jobs
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
# build!
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
displayName: Build Binaryen
inputs:
targetType: inline
script: choco install qemu --version=2020.06.12
script: make lib/binaryen/lib/libbinaryen.a
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
Expand Down
39 changes: 39 additions & 0 deletions builder/binaryen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package builder

import (
"os"
"os/exec"
"strconv"
)

// BinaryenConfig is a configuration for binaryen's wasm-opt tool.
type BinaryenConfig struct {
// OptLevel is the optimization level to use.
// This must be between 0 (no optimization) and 2 (maximum optimization).
OptLevel int

// ShrinkLevel is the size optimization level to use.
// This must be between 0 (no size optimization) and 2 (maximum size optimization).
ShrinkLevel int

// SpecialPasses are special transform/optimization passes to explicitly run before the main optimizer.
SpecialPasses []string
}

func wasmOptCmd(src, dst string, cfg BinaryenConfig) error {
args := []string{
src,
"--output", dst,
"--optimize-level", strconv.Itoa(cfg.OptLevel),
"--shrink-level", strconv.Itoa(cfg.ShrinkLevel),
}
for _, pass := range cfg.SpecialPasses {
args = append(args, "--"+pass)
}

cmd := exec.Command("wasm-opt", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return cmd.Run()
}
24 changes: 24 additions & 0 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,30 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
}

// Run wasm-opt if necessary.
if config.GOARCH() == "wasm" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be strings.HasPrefix(config.Triple(), "wasm") because WASI uses GOOS=linux GOARCH=arm because of reasons (see #1964 for details).

var cfg BinaryenConfig
switch config.Options.Opt {
case "none", "0":
case "1":
cfg.OptLevel = 1
case "2":
cfg.OptLevel = 2
case "s":
cfg.OptLevel = 2
cfg.ShrinkLevel = 1
case "z":
cfg.OptLevel = 2
cfg.ShrinkLevel = 2
default:
return fmt.Errorf("unknown opt level: %q", config.Options.Opt)
}
err := WasmOpt(executable, executable, cfg)
if err != nil {
return fmt.Errorf("wasm-opt failed: %w", err)
}
}

if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
Expand Down
52 changes: 52 additions & 0 deletions builder/tools-builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ package builder

import (
"errors"
"fmt"
"io/ioutil"
"sync"
"unsafe"
)

/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
#include <binaryen-c.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
Expand Down Expand Up @@ -52,3 +56,51 @@ func RunTool(tool string, args ...string) error {
}
return nil
}

// wasmOptLock exists because the binaryen C API uses global state.
// In the future, we can hopefully avoid this by re-execing.
var wasmOptLock sync.Mutex
Comment on lines +60 to +62
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already do this for Clang and LLD, so it wouldn't be too much trouble to add another for wasm-opt. Either way, that's easy to do at a later time (no need to do it from the beginning).


func WasmOpt(src, dst string, cfg BinaryenConfig) error {
data, err := ioutil.ReadFile(src)
if err != nil {
return fmt.Errorf("reading source file: %w", err)
}

data, err = wasmOptApply(data, cfg)
if err != nil {
return fmt.Errorf("running wasm-opt: %w", err)
}

err = ioutil.WriteFile(dst, data, 0666)
if err != nil {
return fmt.Errorf("writing destination file: %w", err)
}

return nil
}

func wasmOptApply(src []byte, cfg BinaryenConfig) ([]byte, error) {
wasmOptLock.Lock()
defer wasmOptLock.Unlock()

// This doesn't actually seem to do anything?
C.BinaryenSetDebugInfo(false)

mod := C.BinaryenModuleRead((*C.char)(unsafe.Pointer(&src[0])), C.size_t(len(src)))
defer C.BinaryenModuleDispose(mod)

C.BinaryenSetOptimizeLevel(C.int(cfg.OptLevel))
C.BinaryenSetShrinkLevel(C.int(cfg.ShrinkLevel))
C.BinaryenModuleOptimize(mod)
C.BinaryenModuleValidate(mod)

// TODO: include source map
res := C.BinaryenModuleAllocateAndWrite(mod, nil)
defer C.free(unsafe.Pointer(res.binary))
if res.sourceMap != nil {
defer C.free(unsafe.Pointer(res.sourceMap))
}

return C.GoBytes(res.binary, C.int(res.binaryBytes)), nil
}
4 changes: 4 additions & 0 deletions builder/tools-external.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ const hasBuiltinTools = false
func RunTool(tool string, args ...string) error {
return errors.New("cannot run tool: " + tool)
}

func WasmOpt(src, dst string, cfg BinaryenConfig) error {
return wasmOptCmd(src, dst, cfg)
}
1 change: 1 addition & 0 deletions lib/binaryen
Submodule binaryen added at b9b8d7