Skip to content

Commit

Permalink
Full Abseil + RE2 support (#173)
Browse files Browse the repository at this point in the history
Add RE2 support + full Abseil

Related cleanups:
- Simpler Docker container
- Single Makefile
- Support for -flto
- SDK proto upgrade to v26.1
- Updated build docs
- More/independent examples
- Remove protobuf CI check

Signed-off-by: Martijn Stevenson <[email protected]>
  • Loading branch information
martijneken authored Oct 25, 2024
1 parent 6b3dc93 commit 9dc95ae
Show file tree
Hide file tree
Showing 20 changed files with 17,981 additions and 17,848 deletions.
23 changes: 0 additions & 23 deletions .github/workflows/cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,29 +60,6 @@ jobs:
export PATH=$PATH:$(go env GOPATH)/bin
addlicense -ignore="**/BUILD" -check .
protobuf:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v2

- name: Install protobuf v3.9.1
run: |
git clone https://github.com/protocolbuffers/protobuf
cd protobuf
git checkout v3.9.1
./autogen.sh
./configure
make
sudo make install
sudo ldconfig
- name: Re-generate and verify protobuf artifacts
run: |
rm *.pb.{cc,h}
make protobuf
git diff --exit-code -G "(^[^ /])|(^\s+[^\*])" *.pb.{cc,h}
build:
runs-on: ubuntu-22.04

Expand Down
3 changes: 1 addition & 2 deletions Dockerfile-sdk
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
FROM ubuntu:noble

COPY ./sdk_container.sh /
COPY ./build_wasm.sh /
COPY *.sh /
COPY *.cc *.h *.js *.proto Makefile* /sdk/

RUN ./sdk_container.sh
70 changes: 50 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,27 +1,57 @@
CPP_API ?= .
ifdef NO_CONTEXT
CPP_CONTEXT_LIB =
else
CPP_CONTEXT_LIB = ${PROXY_WASM_CPP_SDK}/proxy_wasm_intrinsics.cc
endif

all: proxy_wasm_intrinsics.pb.h proxy_wasm_intrinsics_lite.pb.h struct_lite.pb.h ${CPP_API}/libprotobuf.a ${CPP_API}/libprotobuf-lite.a
PROTOBUF ?= none
ifeq ($(PROTOBUF), full)
PROTO_DEPS := protobuf
PROTO_OPTS := -DPROXY_WASM_PROTOBUF_FULL=1 \
${PROXY_WASM_CPP_SDK}/proxy_wasm_intrinsics.pb.cc
else ifeq ($(PROTOBUF), lite)
PROTO_DEPS := protobuf-lite
PROTO_OPTS := -DPROXY_WASM_PROTOBUF_LITE=1 \
${PROXY_WASM_CPP_SDK}/proxy_wasm_intrinsics_lite.pb.cc \
${PROXY_WASM_CPP_SDK}/struct_lite.pb.cc
else
PROTO_DEPS :=
PROTO_OPTS :=
endif

protobuf: proxy_wasm_intrinsics.pb.h proxy_wasm_intrinsics_lite.pb.h struct_lite.pb.h
# Provide a list of libraries that the wasm depends on (absl_*, re2, etc).
WASM_DEPS ?=

proxy_wasm_intrinsics.pb.h: proxy_wasm_intrinsics.proto
protoc --cpp_out=. proxy_wasm_intrinsics.proto
# Determine dependency link options.
# NOTE: Strip out -pthread which RE2 claims to need...
PKG_CONFIG ?= pkg-config
PKG_CONFIG_PATH = ${EMSDK}/upstream/emscripten/cache/sysroot/lib/pkgconfig
WASM_LIBS = $(shell $(PKG_CONFIG) $(WASM_DEPS) $(PROTO_DEPS) \
--with-path=$(PKG_CONFIG_PATH) --libs | sed -e 's/-pthread //g')

proxy_wasm_intrinsics_lite.pb.h struct_lite.pb.h: proxy_wasm_intrinsics_lite.proto
protoc --cpp_out=. -I. proxy_wasm_intrinsics_lite.proto
protoc --cpp_out=. struct_lite.proto
debug-deps:
# WASM_DEPS : ${WASM_DEPS}
# WASM_LIBS : ${WASM_LIBS}
# PROTO_DEPS: ${PROTO_DEPS}
# PROTO_OPTS: ${PROTO_OPTS}

${CPP_API}/libprotobuf.a ${CPP_API}/libprotobuf-lite.a:
rm -rf protobuf-wasm \
&& git clone https://github.com/protocolbuffers/protobuf protobuf-wasm \
&& cd protobuf-wasm \
&& git checkout v3.9.1 \
&& ./autogen.sh \
&& emconfigure ./configure --disable-shared CXXFLAGS="-O3 -flto" \
&& emmake make \
&& cd .. \
&& cp protobuf-wasm/src/.libs/libprotobuf-lite.a ${CPP_API}/libprotobuf-lite.a \
&& cp protobuf-wasm/src/.libs/libprotobuf.a ${CPP_API}/libprotobuf.a
# TODO(mpwarres): Add Emscripten stack/heap size params in PR#174.
%.wasm %.wat: %.cc
em++ --no-entry -sSTANDALONE_WASM -sEXPORTED_FUNCTIONS=_malloc \
--std=c++17 -O3 -flto \
--js-library ${PROXY_WASM_CPP_SDK}/proxy_wasm_intrinsics.js \
-I${PROXY_WASM_CPP_SDK} \
${CPP_CONTEXT_LIB} \
${PROTO_OPTS} \
${WASM_LIBS} \
$*.cc -o $*.wasm

clean:
rm -f proxy_wasm_intrinsics.pb.h proxy_wasm_intrinsics_lite.pb.h struct_lite.pb.h ${CPP_API}/libprotobuf.a ${CPP_API}/libprotobuf-lite.a
rm *.wasm

# NOTE: How to regenerate .pb.h and .pb.cc files for a protobuf update:
# - download + extract protobuf release (currently v26.1)
# - regenerate:
# ./bin/protoc --cpp_out=../ -I../ -Iinclude/google/protobuf/ ../struct_lite.proto
# ./bin/protoc --cpp_out=../ -I../ -Iinclude/google/protobuf/ ../proxy_wasm_intrinsics_lite.proto
# ./bin/protoc --cpp_out=../ -I../ -Iinclude/google/protobuf/ ../proxy_wasm_intrinsics.proto
11 changes: 0 additions & 11 deletions Makefile.base

This file was deleted.

11 changes: 0 additions & 11 deletions Makefile.base_lite

This file was deleted.

9 changes: 6 additions & 3 deletions bazel/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ load("@emsdk//emscripten_toolchain:wasm_rules.bzl", "wasm_cc_binary")
load("@rules_cc//cc:defs.bzl", "cc_binary")

def _optimized_wasm_cc_binary_transition_impl(settings, attr):
# TODO(PiotrSikora): Add -flto to copts/linkopts when fixed in emsdk.
# See: https://github.com/emscripten-core/emsdk/issues/971
# Define STANDALONE_WASM at compile time as well as link time (below).
# This fixes Abseil by not including Emscripten JS stacktraces + symbolization.
# TODO(martijneken): Remove after Abseil stops using this define.
return {
"//command_line_option:copt": ["-O3"],
"//command_line_option:copt": ["-O3", "-flto", "-DSTANDALONE_WASM"],
"//command_line_option:cxxopt": [],
"//command_line_option:linkopt": [],
"//command_line_option:collect_code_coverage": False,
Expand Down Expand Up @@ -102,6 +103,8 @@ def proxy_wasm_cc_binary(
)

wasm_cc_binary(
standalone = True,
threads = "off",
name = "wasm_" + name,
cc_target = ":proxy_wasm_" + name.rstrip(".wasm"),
tags = tags + [
Expand Down
16 changes: 16 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,19 @@ def proxy_wasm_cpp_sdk_repositories():
strip_prefix = "protobuf-3.17.3",
url = "https://github.com/protocolbuffers/protobuf/releases/download/v3.17.3/protobuf-all-3.17.3.tar.gz",
)

maybe(
http_archive,
name = "com_google_absl",
sha256 = "95e90be7c3643e658670e0dd3c1b27092349c34b632c6e795686355f67eca89f",
strip_prefix = "abseil-cpp-20240722.0",
urls = ["https://github.com/abseil/abseil-cpp/archive/20240722.0.zip"],
)

maybe(
http_archive,
name = "com_google_re2",
sha256 = "18cf85922e27fad3ed9c96a27733037da445f35eb1a2744c306a37c6d11e95c4",
strip_prefix = "re2-2023-07-01",
url = "https://github.com/google/re2/archive/2023-07-01.tar.gz",
)
2 changes: 1 addition & 1 deletion build_wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

source /root/emsdk/emsdk_env.sh
export PATH=/usr/local/bin:$PATH
make
make "$@"
123 changes: 30 additions & 93 deletions docs/building.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Proxy-Wasm C++ SDK Build Instructions

The C++ SDK has dependencies on specific versions of the C++ WebAssembly
toolchain [Emscripten](https://emscripten.org) and the protobuf library,
therefore use of a Docker image is recommended.
toolchain [Emscripten](https://emscripten.org). Use of a Docker image is
recommended to achieve repeatable builds and save work.

## Docker

Expand All @@ -11,7 +11,7 @@ A Dockerfile for the C++ SDK is provided in [Dockerfile-sdk](../Dockerfile-sdk).
It can built in this repository's root directory by:

```bash
docker build -t wasmsdk:v2 -f Dockerfile-sdk .
docker build -t wasmsdk:v3 -f Dockerfile-sdk .
```

The docker image can be used for compiling C++ plugin code into Wasm modules.
Expand All @@ -23,12 +23,10 @@ Create a directory with your source files and a Makefile:
```makefile
PROXY_WASM_CPP_SDK=/sdk

all: myproject.wasm

include ${PROXY_WASM_CPP_SDK}/Makefile.base_lite
include ${PROXY_WASM_CPP_SDK}/Makefile
```

Source file (myproject.cc):
Create a C++ source file (myproject.cc):

```c++
#include <string>
Expand Down Expand Up @@ -57,62 +55,40 @@ void ExampleContext::onDone() { logInfo("onDone " + std::to_string(id())); }

### Compiling with the Docker build image

Run docker:

```bash
docker run -v $PWD:/work -w /work wasmsdk:v2 /build_wasm.sh
```

### Caching the standard libraries

The first time that emscripten runs it will generate the standard libraries. To
cache these in the docker image, after the first successful compilation (e.g
myproject.cc above), commit the image with the standard libraries:
Run docker to build wasm, using a target with a .wasm suffix:

```bash
docker commit `docker ps -l | grep wasmsdk:v2 | awk '{print $1}'` wasmsdk:v2
docker run -v $PWD:/work -w /work wasmsdk:v3 /build_wasm.sh myproject.wasm
```

This will save time on subsequent compiles.
You can specify wasm dependencies via these Makefile variables:

### Using Abseil from the Docker image
- PROTOBUF = {full, lite, none}
- WASM_DEPS = list of libraries

Abseil (optionally) is built in /root/abseil and can be used. Note that the
Abseil containers (e.g. `absl::flat_hash_set`) exercise many syscalls which are
not supported. Consequentially individual files should be pulled in which are
relatively self contained (e.g. `strings`). Example customized Makefile:
For example:

```makefile
PROXY_WASM_CPP_SDK=/sdk
CPP_API:=${PROXY_WASM_CPP_SDK}
CPP_CONTEXT_LIB = ${CPP_API}/proxy_wasm_intrinsics.cc
ABSL = /root/abseil-cpp
ABSL_CPP = ${ABSL}/absl/strings/str_cat.cc ${ABSL}/absl/strings/str_split.cc ${ABSL}/absl/strings/numbers.cc ${ABSL}/absl/strings/ascii.cc

all: plugin.wasm
PROTOBUF=lite
WASM_DEPS=re2 absl_strings

%.wasm %.wat: %.cc ${CPP_API}/proxy_wasm_intrinsics.h ${CPP_API}/proxy_wasm_enums.h ${CPP_API}/proxy_wasm_externs.h ${CPP_API}/proxy_wasm_api.h ${CPP_API}/proxy_wasm_intrinsics.js ${CPP_CONTEXT_LIB}
ls /root
em++ --no-entry -s EXPORTED_FUNCTIONS=['_malloc'] --std=c++17 -O3 -flto -I${CPP_API} -I${CPP_API}/google/protobuf -I/usr/local/include -I${ABSL} --js-library ${CPP_API}/proxy_wasm_intrinsics.js ${ABSL_CPP} $*.cc ${CPP_API}/proxy_wasm_intrinsics.pb.cc ${CPP_CONTEXT_LIB} ${CPP_API}/libprotobuf.a -o $*.wasm
include ${PROXY_WASM_CPP_SDK}/Makefile
```

Precompiled Abseil libraries are also available, so the above can also be done
as:

```makefile
PROXY_WASM_CPP_SDK=/sdk
CPP_API:=${PROXY_WASM_CPP_SDK}
CPP_CONTEXT_LIB = ${CPP_API}/proxy_wasm_intrinsics.cc
ABSL = /root/abseil-cpp
ABSL_LIBS = ${ABSL}/absl/strings/libabsl_strings.a ${ABSL}/absl/strings/libabsl_strings_internal.a ${ABSL}/absl/strings/libabsl_str_format_internal.a
### Caching the standard libraries

all: plugin.wasm
The first time that emscripten runs it will generate the standard libraries. To
cache these in the docker image, after the first successful compilation (e.g
myproject.cc above), commit the image with the standard libraries:

%.wasm %.wat: %.cc ${CPP_API}/proxy_wasm_intrinsics.h ${CPP_API}/proxy_wasm_enums.h ${CPP_API}/proxy_wasm_externs.h ${CPP_API}/proxy_wasm_api.h ${CPP_API}/proxy_wasm_intrinsics.js ${CPP_CONTEXT_LIB}
ls /root
em++ --no-entry -s EXPORTED_FUNCTIONS=['_malloc'] --std=c++17 -O3 -flto -I${CPP_API} -I${CPP_API}/google/protobuf -I/usr/local/include -I${ABSL} --js-library ${CPP_API}/proxy_wasm_intrinsics.js $*.cc ${CPP_API}/proxy_wasm_intrinsics.pb.cc ${CPP_CONTEXT_LIB} ${CPP_API}/libprotobuf.a ${ABSL_LIBS} -o $*.wasm
```bash
docker commit `docker ps -l | grep wasmsdk:v3 | awk '{print $1}'` wasmsdk:v3
```

This will save time on subsequent compiles.

### Ownership of the resulting .wasm files

The compiled files may be owned by root. To chown them, add the follow lines to
Expand All @@ -124,48 +100,32 @@ PROXY_WASM_CPP_SDK=/sdk
all: myproject.wasm
chown ${uid}.${gid} $^

include ${PROXY_WASM_CPP_SDK}/Makefile.base_lite
include ${PROXY_WASM_CPP_SDK}/Makefile
```

Invocation file (e.g. build.sh):

```bash
#!/bin/bash
docker run -e uid="$(id -u)" -e gid="$(id -g)" -v $PWD:/work -w /work wasmsdk:v2 /build_wasm.sh
docker run -e uid="$(id -u)" -e gid="$(id -g)" -v $PWD:/work -w /work wasmsdk:v3 /build_wasm.sh
```

## Dependencies for building Wasm modules:

If you do not wish to use the Docker image, the dependencies can be installed by
script (sdk\_container.sh), or by hand.

### protobuf v3.9.1

You must install the version of protobuf on your build system that matches the
libprotobuf.a files (without any patches) so that the generated code matches the
.a library. Currently this is based on tag v3.9.1 of
https://github.com/protocolbuffers/protobuf.

```bash
git clone https://github.com/protocolbuffers/protobuf
cd protobuf
git checkout v3.9.1
git submodule update --init --recursive
./autogen.sh
./configure
make
make check
sudo make install
```
script (sdk\_container.sh), or by hand. First you need Emscripten to build
everything else. Then you can build other wasm-compatible libraries, such as
protobuf, abseil, and RE2.

### Emscripten

Version 3.1.67 is known to work:

```bash
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
git checkout 3.1.67

./emsdk install 3.1.67
./emsdk install --shallow 3.1.67
./emsdk activate 3.1.67

source ./emsdk_env.sh
Expand All @@ -178,26 +138,3 @@ It is possible later versions will work, e.g.
./emsdk install latest
./emsdk activate latest
```

However 3.1.67 is known to work.

### Rebuilding the libprotobuf.a files

To build the protobuf static libraries for use in your proxy-wasm wasm module,
if you need them, you may use Emscripten in the same way sdk\_container.sh does
it:

```bash
git clone https://github.com/protocolbuffers/protobuf protobuf-wasm
cd protobuf-wasm
git checkout v3.9.1
git submodule update --init --recursive
./autogen.sh
emconfigure ./configure --disable-shared CXXFLAGS="-O3 -flto"
emmake make
cd ..
cp protobuf-wasm/src/.libs/libprotobuf-lite.a ${CPP_API}/libprotobuf-lite.a
cp protobuf-wasm/src/.libs/libprotobuf.a ${CPP_API}/libprotobuf.a
```

Note: ensure /usr/local/bin is in your path.
Loading

0 comments on commit 9dc95ae

Please sign in to comment.