Skip to content

Commit 5a1ed9c

Browse files
authored
wasm: replace unused functions by stub (open-policy-agent#3206)
We're in this situation: performing dead code analysis on wasm isn't too hard, but it requires a representation of all wasm instructions: we'd need to be able to parse the "runtime" wasm bits, i.e., what's built using llvm from C code. When building upon that wasm module, we process the function bodies uninterpreted -- they are all just `[]byte` to us. This restriction lets us get by without implementing all the wasm instructions -- we only write what we use, and read a bare minimum to work as outlined above. To still be able to remove dead code, this change employs a trick: at build time, when the aforementioned runtime wasm module is compiled, we're calling wasm-opt on it to extract its call graph. We'll use that, together with the functions actually planned in our wasm compiler (using the subset of instructions that we understand), to remove all unused functions from the name section, and replace their function bodies with `unreachable`. We cannot really remove them, since that would require reindexing all functions; and we cannot do that without replacing the function indices at their call sites in the "runtime" wasm module. Another restriction to the impact of this approach is call_indirect: We need to keep every function that's referenced in the table -- we don't know which function might be calling them indirectly. In a follow-up, we could record that information and use it to further reduce the code size: we know that if none of the regex-related builtins are used, we could also stub out the re2-related functions. Signed-off-by: Stephan Renatus <[email protected]>
1 parent 79a5a55 commit 5a1ed9c

File tree

9 files changed

+2526
-14
lines changed

9 files changed

+2526
-14
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ wasm-lib-build:
180180
ifeq ($(DOCKER_RUNNING), 1)
181181
@$(MAKE) -C wasm ensure-builder build
182182
cp wasm/_obj/opa.wasm internal/compiler/wasm/opa/opa.wasm
183+
cp wasm/_obj/callgraph.csv internal/compiler/wasm/opa/callgraph.csv
183184
else
184185
@echo "Docker not installed or not running. Skipping OPA-WASM library build."
185186
endif

build/check-working-copy.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
EXCEPTIONS=(
44
"internal/compiler/wasm/opa/opa.go"
55
"internal/compiler/wasm/opa/opa.wasm"
6+
"internal/compiler/wasm/opa/callgraph.csv"
67
)
78

89
STATUS=$(git status --porcelain)

internal/cmd/genopawasm/main.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func main() {
2828
Use: executable,
2929
Short: executable + " <opa.wasm path>",
3030
RunE: func(_ *cobra.Command, args []string) error {
31-
if len(args) != 1 {
32-
return fmt.Errorf("provide path of opa.wasm file")
31+
if len(args) != 2 {
32+
return fmt.Errorf("provide path of opa.wasm and callgraph.csv files")
3333
}
3434
return run(params, args)
3535
},
@@ -81,6 +81,16 @@ func Bytes() ([]byte, error) {
8181
return ioutil.ReadAll(gr)
8282
}
8383
84+
// CallGraphCSV returns a CSV representation of the
85+
// OPA-WASM bytecode's call graph: 'caller,callee'
86+
func CallGraphCSV() ([]byte, error) {
87+
cg, err := gzip.NewReader(bytes.NewBuffer(gzippedCallGraphCSV))
88+
if err != nil {
89+
return nil, err
90+
}
91+
return ioutil.ReadAll(cg)
92+
}
93+
8494
`))
8595

8696
if err != nil {
@@ -92,7 +102,34 @@ func Bytes() ([]byte, error) {
92102
return err
93103
}
94104

95-
in, err := os.Open(args[0])
105+
if err := output(out, args[0]); err != nil {
106+
return err
107+
}
108+
109+
if _, err := out.Write([]byte(`")
110+
`)); err != nil {
111+
return err
112+
}
113+
114+
_, err = out.Write([]byte(`var gzippedCallGraphCSV = []byte("`))
115+
if err != nil {
116+
return err
117+
}
118+
119+
if err := output(out, args[1]); err != nil {
120+
return err
121+
}
122+
123+
if _, err := out.Write([]byte(`")
124+
`)); err != nil {
125+
return err
126+
}
127+
128+
return nil
129+
}
130+
131+
func output(out io.Writer, filename string) error {
132+
in, err := os.Open(filename)
96133
if err != nil {
97134
return err
98135
}
@@ -116,14 +153,7 @@ func Bytes() ([]byte, error) {
116153
return err
117154
}
118155
}
119-
120-
_, err = out.Write([]byte(`")`))
121-
if err != nil {
122-
return err
123-
}
124-
125-
_, err = out.Write([]byte("\n"))
126-
return err
156+
return nil
127157
}
128158

129159
var digits = "0123456789ABCDEF"

0 commit comments

Comments
 (0)