-
Notifications
You must be signed in to change notification settings - Fork 951
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
Changes from all commits
3069b5d
91c8ecf
cc85097
0ca39b5
7102585
c3e47d2
eeb9df4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
clean: | ||
@rm -rf build | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be a dependency of the |
||
|
||
# Build wasi-libc sysroot | ||
.PHONY: wasi-libc | ||
|
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() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -621,6 +621,30 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil | |
} | ||
} | ||
|
||
// Run wasm-opt if necessary. | ||
if config.GOARCH() == "wasm" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be |
||
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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
There was a problem hiding this comment.
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.