Skip to content

Commit 332a49f

Browse files
authored
PrintBoundary pass, emitting a JSON summary of the API boundary of the module (#8703)
1 parent 2f1f55a commit 332a49f

8 files changed

Lines changed: 262 additions & 0 deletions

File tree

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ set(passes_SOURCES
8989
PostEmscripten.cpp
9090
Precompute.cpp
9191
Print.cpp
92+
PrintBoundary.cpp
9293
PrintCallGraph.cpp
9394
PrintFeatures.cpp
9495
PrintFunctionMap.cpp

src/passes/PrintBoundary.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Prints the boundary - the imports and exports - in a convenient JSON format.
19+
// Only enough information for JavaScript is provided (for the full info, parse
20+
// the wat or wasm).
21+
//
22+
// Usage:
23+
//
24+
// wasm-opt --print-boundary=OUTFILE
25+
//
26+
// If OUTFILE is not provided, prints to stdout.
27+
//
28+
// Example:
29+
//
30+
// {
31+
// 'imports': [
32+
// {
33+
// 'module': 'foo', // foo.bar
34+
// 'base': 'bar',
35+
// 'kind': 'func',
36+
// 'type': {
37+
// 'params': ['i32', '(ref func)'],
38+
// 'results': ['f64']
39+
// },
40+
// },
41+
// [..]
42+
// ],
43+
// 'exports': [
44+
// {
45+
// 'name': 'foo',
46+
// 'kind': 'global',
47+
// 'type': 'i32',
48+
// },
49+
// [..]
50+
// ]
51+
// }
52+
//
53+
54+
#include "ir/module-utils.h"
55+
#include "pass.h"
56+
#include "support/file.h"
57+
#include "support/json.h"
58+
#include "wasm.h"
59+
60+
namespace wasm {
61+
62+
struct PrintBoundary : public Pass {
63+
bool modifiesBinaryenIR() override { return false; }
64+
65+
void run(Module* module) override {
66+
std::string target = getArgumentOrDefault("print-boundary", "");
67+
68+
// Imports.
69+
auto imports = json::Value::makeArray();
70+
71+
ModuleUtils::iterImportable(
72+
*module, [&](ExternalKind kind, Importable* import) {
73+
auto item = json::Value::makeObject();
74+
item["module"] = json::Value::make(import->module.view());
75+
item["base"] = json::Value::make(import->base.view());
76+
item["kind"] = getKindName(kind);
77+
item["type"] = getExternalType(kind, import->name, *module);
78+
imports->push_back(item);
79+
});
80+
81+
// Exports.
82+
auto exports = json::Value::makeArray();
83+
84+
for (auto& exp : module->exports) {
85+
auto item = json::Value::makeObject();
86+
item["name"] = json::Value::make(exp->name.view());
87+
item["kind"] = getKindName(exp->kind);
88+
item["type"] =
89+
getExternalType(exp->kind, *exp->getInternalName(), *module);
90+
exports->push_back(item);
91+
}
92+
93+
// Emit the final structure
94+
json::Value root;
95+
root.setObject();
96+
root["imports"] = imports;
97+
root["exports"] = exports;
98+
99+
Output output(target, Flags::BinaryOption::Text);
100+
root.stringify(output.getStream(), true /* pretty */);
101+
}
102+
103+
// Emits an array of multivalue types. For a signature, emits params and
104+
// results.
105+
//
106+
// We emit an array only when needed, unless forceArray is set.
107+
json::Value::Ref getTypes(Type type, bool forceArray = false) {
108+
if (type.isRef()) {
109+
auto heapType = type.getHeapType();
110+
if (heapType.isSignature()) {
111+
auto sig = heapType.getSignature();
112+
auto ret = json::Value::makeObject();
113+
// Always emit arrays for params and results.
114+
ret["params"] = getTypes(sig.params, true);
115+
ret["results"] = getTypes(sig.results, true);
116+
return ret;
117+
}
118+
}
119+
120+
// Simplify the output, avoiding an array for a single value.
121+
if (!forceArray && type.size() == 1) {
122+
return json::Value::make(type.toString());
123+
}
124+
125+
auto ret = json::Value::makeArray();
126+
for (auto t : type) {
127+
ret->push_back(json::Value::make(t.toString()));
128+
}
129+
return ret;
130+
}
131+
132+
// For an imported or exported thing (something external), and its name,
133+
// return the type info we report for it.
134+
json::Value::Ref getExternalType(ExternalKind kind, Name name, Module& wasm) {
135+
switch (kind) {
136+
case ExternalKind::Function:
137+
return getTypes(wasm.getFunction(name)->type);
138+
break;
139+
case ExternalKind::Table:
140+
break;
141+
case ExternalKind::Memory:
142+
break;
143+
case ExternalKind::Global:
144+
return getTypes(wasm.getGlobal(name)->type);
145+
case ExternalKind::Tag:
146+
break;
147+
case ExternalKind::Invalid:
148+
WASM_UNREACHABLE("invalid ExternalKind");
149+
}
150+
return {};
151+
}
152+
153+
json::Value::Ref getKindName(ExternalKind kind) {
154+
const char* name = nullptr;
155+
switch (kind) {
156+
case ExternalKind::Function:
157+
name = "func";
158+
break;
159+
case ExternalKind::Table:
160+
name = "table";
161+
break;
162+
case ExternalKind::Memory:
163+
name = "memory";
164+
break;
165+
case ExternalKind::Global:
166+
name = "global";
167+
break;
168+
case ExternalKind::Tag:
169+
name = "tag";
170+
break;
171+
case ExternalKind::Invalid:
172+
WASM_UNREACHABLE("invalid ExternalKind");
173+
}
174+
return json::Value::make(name);
175+
}
176+
};
177+
178+
Pass* createPrintBoundaryPass() { return new PrintBoundary(); }
179+
180+
} // namespace wasm

src/passes/pass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,8 @@ void PassRegistry::registerPasses() {
390390
createPrintFeaturesPass);
391391
registerPass(
392392
"print-full", "print in full s-expression format", createFullPrinterPass);
393+
registerPass(
394+
"print-boundary", "print boundary in JSON format", createPrintBoundaryPass);
393395
registerPass(
394396
"print-call-graph", "print call graph", createPrintCallGraphPass);
395397

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ Pass* createPostEmscriptenPass();
128128
Pass* createPrecomputePass();
129129
Pass* createPrecomputePropagatePass();
130130
Pass* createPrinterPass();
131+
Pass* createPrintBoundaryPass();
131132
Pass* createPrintCallGraphPass();
132133
Pass* createPrintFeaturesPass();
133134
Pass* createPrintFunctionMapPass();

test/lit/help/wasm-metadce.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@
362362
;; CHECK-NEXT:
363363
;; CHECK-NEXT: --print print in s-expression format
364364
;; CHECK-NEXT:
365+
;; CHECK-NEXT: --print-boundary print boundary in JSON format
366+
;; CHECK-NEXT:
365367
;; CHECK-NEXT: --print-call-graph print call graph
366368
;; CHECK-NEXT:
367369
;; CHECK-NEXT: --print-features print options for enabled

test/lit/help/wasm-opt.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@
398398
;; CHECK-NEXT:
399399
;; CHECK-NEXT: --print print in s-expression format
400400
;; CHECK-NEXT:
401+
;; CHECK-NEXT: --print-boundary print boundary in JSON format
402+
;; CHECK-NEXT:
401403
;; CHECK-NEXT: --print-call-graph print call graph
402404
;; CHECK-NEXT:
403405
;; CHECK-NEXT: --print-features print options for enabled

test/lit/help/wasm2js.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@
326326
;; CHECK-NEXT:
327327
;; CHECK-NEXT: --print print in s-expression format
328328
;; CHECK-NEXT:
329+
;; CHECK-NEXT: --print-boundary print boundary in JSON format
330+
;; CHECK-NEXT:
329331
;; CHECK-NEXT: --print-call-graph print call graph
330332
;; CHECK-NEXT:
331333
;; CHECK-NEXT: --print-features print options for enabled
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
(module
2+
(type $struct (struct))
3+
4+
(import "module" "base" (func $foo (param i32) (param f64) (result anyref)))
5+
6+
(import "module2" "other" (func $bar (result i32 f32)))
7+
8+
(global $g (mut i32) (i32.const 42))
9+
10+
(export "one" (func $one))
11+
12+
(export "glob" (global $g))
13+
14+
(func $one (param $x (ref $struct)) (result i32 i32 i32)
15+
(unreachable)
16+
)
17+
)
18+
19+
;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s
20+
21+
;; CHECK: {
22+
;; CHECK-NEXT: "imports": [
23+
;; CHECK-NEXT: {
24+
;; CHECK-NEXT: "module": "module",
25+
;; CHECK-NEXT: "base": "base",
26+
;; CHECK-NEXT: "kind": "func",
27+
;; CHECK-NEXT: "type": {
28+
;; CHECK-NEXT: "params": [
29+
;; CHECK-NEXT: "i32",
30+
;; CHECK-NEXT: "f64"
31+
;; CHECK-NEXT: ],
32+
;; CHECK-NEXT: "results": [
33+
;; CHECK-NEXT: "anyref"
34+
;; CHECK-NEXT: ]
35+
;; CHECK-NEXT: }
36+
;; CHECK-NEXT: },
37+
;; CHECK-NEXT: {
38+
;; CHECK-NEXT: "module": "module2",
39+
;; CHECK-NEXT: "base": "other",
40+
;; CHECK-NEXT: "kind": "func",
41+
;; CHECK-NEXT: "type": {
42+
;; CHECK-NEXT: "params": [
43+
;; CHECK-NEXT: ],
44+
;; CHECK-NEXT: "results": [
45+
;; CHECK-NEXT: "i32",
46+
;; CHECK-NEXT: "f32"
47+
;; CHECK-NEXT: ]
48+
;; CHECK-NEXT: }
49+
;; CHECK-NEXT: }
50+
;; CHECK-NEXT: ],
51+
;; CHECK-NEXT: "exports": [
52+
;; CHECK-NEXT: {
53+
;; CHECK-NEXT: "name": "one",
54+
;; CHECK-NEXT: "kind": "func",
55+
;; CHECK-NEXT: "type": {
56+
;; CHECK-NEXT: "params": [
57+
;; CHECK-NEXT: "(ref $struct.0)"
58+
;; CHECK-NEXT: ],
59+
;; CHECK-NEXT: "results": [
60+
;; CHECK-NEXT: "i32",
61+
;; CHECK-NEXT: "i32",
62+
;; CHECK-NEXT: "i32"
63+
;; CHECK-NEXT: ]
64+
;; CHECK-NEXT: }
65+
;; CHECK-NEXT: },
66+
;; CHECK-NEXT: {
67+
;; CHECK-NEXT: "name": "glob",
68+
;; CHECK-NEXT: "kind": "global",
69+
;; CHECK-NEXT: "type": "i32"
70+
;; CHECK-NEXT: }
71+
;; CHECK-NEXT: ]
72+
;; CHECK-NEXT: }

0 commit comments

Comments
 (0)