From d60f7fe33f54961780faf0d78ebfe5e36bb864c2 Mon Sep 17 00:00:00 2001 From: Mihai Parparita Date: Mon, 24 Oct 2022 16:05:01 -0700 Subject: [PATCH] cmd/tsconnect: run wasm-opt on the generated wasm file Saves about 1.4MB from the generated wasm file. The Brotli size is basically unchanged (it's actually slightly larger, by 40K), suggesting that most of the size delta is due to not inlining and other changes that were easily compressible. However, it still seems worthwhile to have a smaller final binary, to reduce parse time and increase likelihood that we fit in the browser's disk cache. Actual performance appears to be unchanged. Updates #5142 Signed-off-by: Mihai Parparita --- cmd/tsconnect/common.go | 35 +++++++++++++++++-- tool/binaryen.rev | 1 + tool/wasm-opt | 74 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tool/binaryen.rev create mode 100755 tool/wasm-opt diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go index 20ee449f0..9c112f8ac 100644 --- a/cmd/tsconnect/common.go +++ b/cmd/tsconnect/common.go @@ -216,10 +216,41 @@ func buildWasm(dev bool) ([]byte, error) { if err != nil { return nil, fmt.Errorf("Cannot build main.wasm: %w", err) } - log.Printf("Built wasm in %v\n", time.Since(start)) + log.Printf("Built wasm in %v\n", time.Since(start).Round(time.Millisecond)) + + if !dev { + err := runWasmOpt(outputPath) + if err != nil { + return nil, fmt.Errorf("Cannot run wasm-opt: %w", err) + } + } + return os.ReadFile(outputPath) } +func runWasmOpt(path string) error { + start := time.Now() + stat, err := os.Stat(path) + if err != nil { + return fmt.Errorf("Cannot stat %v: %w", path, err) + } + startSize := stat.Size() + cmd := exec.Command("../../tool/wasm-opt", "-Oz", path, "-o", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + if err != nil { + return fmt.Errorf("Cannot run wasm-opt: %w", err) + } + stat, err = os.Stat(path) + if err != nil { + return fmt.Errorf("Cannot stat %v: %w", path, err) + } + endSize := stat.Size() + log.Printf("Ran wasm-opt in %v, size dropped by %dK\n", time.Since(start).Round(time.Millisecond), (startSize-endSize)/1024) + return nil +} + // installJSDeps installs the JavaScript dependencies specified by package.json func installJSDeps() error { log.Printf("Installing JS deps...\n") @@ -256,7 +287,7 @@ func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) { } cmd := exec.Command(*yarnPath, yarnArgs...) tailwindOutput, err := cmd.Output() - log.Printf("Ran tailwind in %v\n", time.Since(start)) + log.Printf("Ran tailwind in %v\n", time.Since(start).Round(time.Millisecond)) if err != nil { if exitErr, ok := err.(*exec.ExitError); ok { log.Printf("Tailwind stderr: %s", exitErr.Stderr) diff --git a/tool/binaryen.rev b/tool/binaryen.rev new file mode 100644 index 000000000..bc6298e80 --- /dev/null +++ b/tool/binaryen.rev @@ -0,0 +1 @@ +110 diff --git a/tool/wasm-opt b/tool/wasm-opt new file mode 100755 index 000000000..08f3e5bfb --- /dev/null +++ b/tool/wasm-opt @@ -0,0 +1,74 @@ +#!/bin/sh +# +# This script acts like the "wasm-opt" command from the Binaryen toolchain, but +# uses Tailscale's currently-desired version, downloading it first if necessary. + +set -eu + +BINARYEN_DIR="$HOME/.cache/tailscale-binaryen" +read -r BINARYEN_REV < "$(dirname "$0")/binaryen.rev" +# This works for Linux and Darwin, which is sufficient +# (we do not build for other targets). +OS=$(uname -s | tr A-Z a-z) +if [ "$OS" = "darwin" ]; then + # Binaryen uses the name "macos". + OS="macos" +fi +ARCH="$(uname -m)" +if [ "$ARCH" = "aarch64" ]; then + # Binaryen uses the name "arm64". + ARCH="arm64" +fi + +install_binaryen() { + BINARYEN_URL="https://github.com/WebAssembly/binaryen/releases/download/version_${BINARYEN_REV}/binaryen-version_${BINARYEN_REV}-${ARCH}-${OS}.tar.gz" + install_tool "wasm-opt" $BINARYEN_REV $BINARYEN_DIR $BINARYEN_URL +} + +install_tool() { + TOOL=$1 + REV=$2 + TOOLCHAIN=$3 + URL=$4 + + archive="$TOOLCHAIN-$REV.tar.gz" + mark="$TOOLCHAIN.extracted" + extracted= + [ ! -e "$mark" ] || read -r extracted junk <$mark + + if [ "$extracted" = "$REV" ] && [ -e "$TOOLCHAIN/bin/$TOOL" ]; then + # Already extracted, continue silently + return 0 + fi + echo "" + + rm -f "$archive.new" "$TOOLCHAIN.extracted" + if [ ! -e "$archive" ]; then + log "Need to download $TOOL '$REV' from $URL." + curl -f -L -o "$archive.new" $URL + rm -f "$archive" + mv "$archive.new" "$archive" + fi + + log "Extracting $TOOL '$REV' into '$TOOLCHAIN'." >&2 + rm -rf "$TOOLCHAIN" + mkdir -p "$TOOLCHAIN" + (cd "$TOOLCHAIN" && tar --strip-components=1 -xf "$archive") + echo "$REV" >$mark +} + +log() { + echo "$@" >&2 +} + +if [ "${BINARYEN_DIR}" = "SKIP" ] || + [ "${OS}" != "macos" -a "${OS}" != "linux" ] || + [ "${ARCH}" != "x86_64" -a "${ARCH}" != "arm64" ]; then + log "Unsupported OS (${OS}) and architecture (${ARCH}) combination." + log "Using existing wasm-opt (`which wasm-opt`)." + exec wasm-opt "$@" +fi + +install_binaryen + +"$BINARYEN_DIR/bin/wasm-opt" "$@"