diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a61bfb6195c..d6f9100aad0 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -89,6 +89,7 @@ set(passes_SOURCES PostEmscripten.cpp Precompute.cpp Print.cpp + PrintBoundary.cpp PrintCallGraph.cpp PrintFeatures.cpp PrintFunctionMap.cpp diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp new file mode 100644 index 00000000000..371e642e2d8 --- /dev/null +++ b/src/passes/PrintBoundary.cpp @@ -0,0 +1,180 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Prints the boundary - the imports and exports - in a convenient JSON format. +// Only enough information for JavaScript is provided (for the full info, parse +// the wat or wasm). +// +// Usage: +// +// wasm-opt --print-boundary=OUTFILE +// +// If OUTFILE is not provided, prints to stdout. +// +// Example: +// +// { +// 'imports': [ +// { +// 'module': 'foo', // foo.bar +// 'base': 'bar', +// 'kind': 'func', +// 'type': { +// 'params': ['i32', '(ref func)'], +// 'results': ['f64'] +// }, +// }, +// [..] +// ], +// 'exports': [ +// { +// 'name': 'foo', +// 'kind': 'global', +// 'type': 'i32', +// }, +// [..] +// ] +// } +// + +#include "ir/module-utils.h" +#include "pass.h" +#include "support/file.h" +#include "support/json.h" +#include "wasm.h" + +namespace wasm { + +struct PrintBoundary : public Pass { + bool modifiesBinaryenIR() override { return false; } + + void run(Module* module) override { + std::string target = getArgumentOrDefault("print-boundary", ""); + + // Imports. + auto imports = json::Value::makeArray(); + + ModuleUtils::iterImportable( + *module, [&](ExternalKind kind, Importable* import) { + auto item = json::Value::makeObject(); + item["module"] = json::Value::make(import->module.view()); + item["base"] = json::Value::make(import->base.view()); + item["kind"] = getKindName(kind); + item["type"] = getExternalType(kind, import->name, *module); + imports->push_back(item); + }); + + // Exports. + auto exports = json::Value::makeArray(); + + for (auto& exp : module->exports) { + auto item = json::Value::makeObject(); + item["name"] = json::Value::make(exp->name.view()); + item["kind"] = getKindName(exp->kind); + item["type"] = + getExternalType(exp->kind, *exp->getInternalName(), *module); + exports->push_back(item); + } + + // Emit the final structure + json::Value root; + root.setObject(); + root["imports"] = imports; + root["exports"] = exports; + + Output output(target, Flags::BinaryOption::Text); + root.stringify(output.getStream(), true /* pretty */); + } + + // Emits an array of multivalue types. For a signature, emits params and + // results. + // + // We emit an array only when needed, unless forceArray is set. + json::Value::Ref getTypes(Type type, bool forceArray = false) { + if (type.isRef()) { + auto heapType = type.getHeapType(); + if (heapType.isSignature()) { + auto sig = heapType.getSignature(); + auto ret = json::Value::makeObject(); + // Always emit arrays for params and results. + ret["params"] = getTypes(sig.params, true); + ret["results"] = getTypes(sig.results, true); + return ret; + } + } + + // Simplify the output, avoiding an array for a single value. + if (!forceArray && type.size() == 1) { + return json::Value::make(type.toString()); + } + + auto ret = json::Value::makeArray(); + for (auto t : type) { + ret->push_back(json::Value::make(t.toString())); + } + return ret; + } + + // For an imported or exported thing (something external), and its name, + // return the type info we report for it. + json::Value::Ref getExternalType(ExternalKind kind, Name name, Module& wasm) { + switch (kind) { + case ExternalKind::Function: + return getTypes(wasm.getFunction(name)->type); + break; + case ExternalKind::Table: + break; + case ExternalKind::Memory: + break; + case ExternalKind::Global: + return getTypes(wasm.getGlobal(name)->type); + case ExternalKind::Tag: + break; + case ExternalKind::Invalid: + WASM_UNREACHABLE("invalid ExternalKind"); + } + return {}; + } + + json::Value::Ref getKindName(ExternalKind kind) { + const char* name = nullptr; + switch (kind) { + case ExternalKind::Function: + name = "func"; + break; + case ExternalKind::Table: + name = "table"; + break; + case ExternalKind::Memory: + name = "memory"; + break; + case ExternalKind::Global: + name = "global"; + break; + case ExternalKind::Tag: + name = "tag"; + break; + case ExternalKind::Invalid: + WASM_UNREACHABLE("invalid ExternalKind"); + } + return json::Value::make(name); + } +}; + +Pass* createPrintBoundaryPass() { return new PrintBoundary(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index dc6d91feb4e..d29a6fcebf5 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -390,6 +390,8 @@ void PassRegistry::registerPasses() { createPrintFeaturesPass); registerPass( "print-full", "print in full s-expression format", createFullPrinterPass); + registerPass( + "print-boundary", "print boundary in JSON format", createPrintBoundaryPass); registerPass( "print-call-graph", "print call graph", createPrintCallGraphPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 2fdacd84ab0..681a259a831 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -128,6 +128,7 @@ Pass* createPostEmscriptenPass(); Pass* createPrecomputePass(); Pass* createPrecomputePropagatePass(); Pass* createPrinterPass(); +Pass* createPrintBoundaryPass(); Pass* createPrintCallGraphPass(); Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 4d5f8e33b89..b35982035d0 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -362,6 +362,8 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --print print in s-expression format ;; CHECK-NEXT: +;; CHECK-NEXT: --print-boundary print boundary in JSON format +;; CHECK-NEXT: ;; CHECK-NEXT: --print-call-graph print call graph ;; CHECK-NEXT: ;; CHECK-NEXT: --print-features print options for enabled diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 8566645db87..1565b9686b7 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -398,6 +398,8 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --print print in s-expression format ;; CHECK-NEXT: +;; CHECK-NEXT: --print-boundary print boundary in JSON format +;; CHECK-NEXT: ;; CHECK-NEXT: --print-call-graph print call graph ;; CHECK-NEXT: ;; CHECK-NEXT: --print-features print options for enabled diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 88d6504b384..32a1f60ed3e 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -326,6 +326,8 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --print print in s-expression format ;; CHECK-NEXT: +;; CHECK-NEXT: --print-boundary print boundary in JSON format +;; CHECK-NEXT: ;; CHECK-NEXT: --print-call-graph print call graph ;; CHECK-NEXT: ;; CHECK-NEXT: --print-features print options for enabled diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast new file mode 100644 index 00000000000..850618f07b1 --- /dev/null +++ b/test/lit/passes/print-boundary.wast @@ -0,0 +1,72 @@ +(module + (type $struct (struct)) + + (import "module" "base" (func $foo (param i32) (param f64) (result anyref))) + + (import "module2" "other" (func $bar (result i32 f32))) + + (global $g (mut i32) (i32.const 42)) + + (export "one" (func $one)) + + (export "glob" (global $g)) + + (func $one (param $x (ref $struct)) (result i32 i32 i32) + (unreachable) + ) +) + +;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s + +;; CHECK: { +;; CHECK-NEXT: "imports": [ +;; CHECK-NEXT: { +;; CHECK-NEXT: "module": "module", +;; CHECK-NEXT: "base": "base", +;; CHECK-NEXT: "kind": "func", +;; CHECK-NEXT: "type": { +;; CHECK-NEXT: "params": [ +;; CHECK-NEXT: "i32", +;; CHECK-NEXT: "f64" +;; CHECK-NEXT: ], +;; CHECK-NEXT: "results": [ +;; CHECK-NEXT: "anyref" +;; CHECK-NEXT: ] +;; CHECK-NEXT: } +;; CHECK-NEXT: }, +;; CHECK-NEXT: { +;; CHECK-NEXT: "module": "module2", +;; CHECK-NEXT: "base": "other", +;; CHECK-NEXT: "kind": "func", +;; CHECK-NEXT: "type": { +;; CHECK-NEXT: "params": [ +;; CHECK-NEXT: ], +;; CHECK-NEXT: "results": [ +;; CHECK-NEXT: "i32", +;; CHECK-NEXT: "f32" +;; CHECK-NEXT: ] +;; CHECK-NEXT: } +;; CHECK-NEXT: } +;; CHECK-NEXT: ], +;; CHECK-NEXT: "exports": [ +;; CHECK-NEXT: { +;; CHECK-NEXT: "name": "one", +;; CHECK-NEXT: "kind": "func", +;; CHECK-NEXT: "type": { +;; CHECK-NEXT: "params": [ +;; CHECK-NEXT: "(ref $struct.0)" +;; CHECK-NEXT: ], +;; CHECK-NEXT: "results": [ +;; CHECK-NEXT: "i32", +;; CHECK-NEXT: "i32", +;; CHECK-NEXT: "i32" +;; CHECK-NEXT: ] +;; CHECK-NEXT: } +;; CHECK-NEXT: }, +;; CHECK-NEXT: { +;; CHECK-NEXT: "name": "glob", +;; CHECK-NEXT: "kind": "global", +;; CHECK-NEXT: "type": "i32" +;; CHECK-NEXT: } +;; CHECK-NEXT: ] +;; CHECK-NEXT: }