From ef5111810845344f23c8a68db1d41d089d0618a5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 10:11:52 -0700 Subject: [PATCH 01/28] fix --- src/passes/CMakeLists.txt | 1 + src/passes/PrintBoundary.cpp | 138 +++++++++++++++++++++++++++++++++++ src/passes/pass.cpp | 2 + src/passes/passes.h | 1 + 4 files changed, 142 insertions(+) create mode 100644 src/passes/PrintBoundary.cpp 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..7cf772f9e43 --- /dev/null +++ b/src/passes/PrintBoundary.cpp @@ -0,0 +1,138 @@ +/* + * 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': [ +// { +// 'name': 'foo', +// 'kind': 'func', +// 'params': ['i32', '(ref func)'], +// 'results': ['f64'] +// }, +// [..] +// ], +// 'exports': [ +// { +// 'name': 'foo', +// 'kind': 'global', +// 'type': 'i32', +// }, +// [..] +// ] +// } +// + +#include "pass.h" +#include "support/file.h" +#include "wasm.h" + +namespace wasm { + +struct PrintCallGraph : public Pass { + bool modifiesBinaryenIR() override { return false; } + + void run(Module* module) override { + std::string target = getArgumentOrDefault("print-boundary", ""); + + Output out(target, Flags::BinaryOption::Text); + + std::ostream& o = std::cout; + o << "digraph call {\n" + " rankdir = LR;\n" + " subgraph cluster_key {\n" + " node [shape=box, fontname=courier, fontsize=10];\n" + " edge [fontname=courier, fontsize=10];\n" + " label = \"Key\";\n" + " \"Import\" [style=\"filled\", fillcolor=\"turquoise\"];\n" + " \"Export\" [style=\"filled\", fillcolor=\"gray\"];\n" + " \"Indirect Target\" [style=\"filled, rounded\", " + "fillcolor=\"white\"];\n" + " \"A\" -> \"B\" [style=\"filled, rounded\", label = \"Direct " + "Call\"];\n" + " }\n\n" + " node [shape=box, fontname=courier, fontsize=10];\n"; + + // Defined functions + ModuleUtils::iterDefinedFunctions(*module, [&](Function* curr) { + std::cout << " \"" << curr->name + << "\" [style=\"filled\", fillcolor=\"white\"];\n"; + }); + + // Imported functions + ModuleUtils::iterImportedFunctions(*module, [&](Function* curr) { + o << " \"" << curr->name + << "\" [style=\"filled\", fillcolor=\"turquoise\"];\n"; + }); + + // Exports + for (auto& curr : module->exports) { + if (curr->kind == ExternalKind::Function) { + Function* func = module->getFunction(*curr->getInternalName()); + o << " \"" << func->name + << "\" [style=\"filled\", fillcolor=\"gray\"];\n"; + } + } + + struct CallPrinter : public PostWalker { + Module* module; + Function* currFunction; + std::set visitedTargets; // Used to avoid printing duplicate edges. + std::vector allIndirectTargets; + CallPrinter(Module* module) : module(module) { + // Walk function bodies. + ModuleUtils::iterDefinedFunctions(*module, [&](Function* curr) { + currFunction = curr; + visitedTargets.clear(); + walk(curr->body); + }); + } + void visitCall(Call* curr) { + auto* target = module->getFunction(curr->target); + if (!visitedTargets.emplace(target->name).second) { + return; + } + std::cout << " \"" << currFunction->name << "\" -> \"" << target->name + << "\"; // call\n"; + } + }; + CallPrinter printer(module); + + // Indirect Targets + ElementUtils::iterAllElementFunctionNames(module, [&](Name name) { + auto* func = module->getFunction(name); + o << " \"" << func->name << "\" [style=\"filled, rounded\"];\n"; + }); + + o << "}\n"; + } +}; + +Pass* createPrintCallGraphPass() { return new PrintCallGraph(); } + +} // 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..d7f272bbbb6 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(); From 8cf542b28224f6ea753ffaa6bb4e5183cf964d23 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 11:15:10 -0700 Subject: [PATCH 02/28] go --- src/passes/PrintBoundary.cpp | 102 +++++++++++++---------------------- src/passes/passes.h | 2 +- src/support/json.h | 2 + 3 files changed, 39 insertions(+), 67 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 7cf772f9e43..2a9e54351cf 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -30,7 +30,8 @@ // { // 'imports': [ // { -// 'name': 'foo', +// 'module': 'foo', // foo.bar +// 'base': 'bar', // 'kind': 'func', // 'params': ['i32', '(ref func)'], // 'results': ['f64'] @@ -50,89 +51,58 @@ #include "pass.h" #include "support/file.h" +#include "support/json.h" #include "wasm.h" namespace wasm { -struct PrintCallGraph : public Pass { +struct PrintBoundary : public Pass { bool modifiesBinaryenIR() override { return false; } void run(Module* module) override { std::string target = getArgumentOrDefault("print-boundary", ""); - Output out(target, Flags::BinaryOption::Text); - - std::ostream& o = std::cout; - o << "digraph call {\n" - " rankdir = LR;\n" - " subgraph cluster_key {\n" - " node [shape=box, fontname=courier, fontsize=10];\n" - " edge [fontname=courier, fontsize=10];\n" - " label = \"Key\";\n" - " \"Import\" [style=\"filled\", fillcolor=\"turquoise\"];\n" - " \"Export\" [style=\"filled\", fillcolor=\"gray\"];\n" - " \"Indirect Target\" [style=\"filled, rounded\", " - "fillcolor=\"white\"];\n" - " \"A\" -> \"B\" [style=\"filled, rounded\", label = \"Direct " - "Call\"];\n" - " }\n\n" - " node [shape=box, fontname=courier, fontsize=10];\n"; + // Imports. + auto imports = json::Value::make(); + imports->setArray(); - // Defined functions - ModuleUtils::iterDefinedFunctions(*module, [&](Function* curr) { - std::cout << " \"" << curr->name - << "\" [style=\"filled\", fillcolor=\"white\"];\n"; - }); + for (auto& func : module->functions) { + if (!func->imported()) { + continue; + } - // Imported functions - ModuleUtils::iterImportedFunctions(*module, [&](Function* curr) { - o << " \"" << curr->name - << "\" [style=\"filled\", fillcolor=\"turquoise\"];\n"; - }); + auto import = json::Value::make(); + import["module"] = json::Value::make(func->module.toString()); + import["base"] = json::Value::make(func->base.toString()); + import["kind"] = json::Value::make("func"); + import["params"] = getTypes(func->getParams()); + import["results"] = getTypes(func->getResults()); - // Exports - for (auto& curr : module->exports) { - if (curr->kind == ExternalKind::Function) { - Function* func = module->getFunction(*curr->getInternalName()); - o << " \"" << func->name - << "\" [style=\"filled\", fillcolor=\"gray\"];\n"; - } + imports->push_back(import); } - struct CallPrinter : public PostWalker { - Module* module; - Function* currFunction; - std::set visitedTargets; // Used to avoid printing duplicate edges. - std::vector allIndirectTargets; - CallPrinter(Module* module) : module(module) { - // Walk function bodies. - ModuleUtils::iterDefinedFunctions(*module, [&](Function* curr) { - currFunction = curr; - visitedTargets.clear(); - walk(curr->body); - }); - } - void visitCall(Call* curr) { - auto* target = module->getFunction(curr->target); - if (!visitedTargets.emplace(target->name).second) { - return; - } - std::cout << " \"" << currFunction->name << "\" -> \"" << target->name - << "\"; // call\n"; - } - }; - CallPrinter printer(module); + // Exports. + + // Emit the final structure + json::Value root; + root.setArray(2); + root[0] = imports; + //root[1] = exports; - // Indirect Targets - ElementUtils::iterAllElementFunctionNames(module, [&](Name name) { - auto* func = module->getFunction(name); - o << " \"" << func->name << "\" [style=\"filled, rounded\"];\n"; - }); + Output output(target, Flags::BinaryOption::Text); + root.stringify(output.getStream(), true /* pretty */); + } - o << "}\n"; + json::Value::Ref getTypes(Type type) { + auto ret = json::Value::make(); + ret->setArray(); + for (auto t : type) { + ret->push_back(json::Value::make(t.toString())); + } + return ret; } }; -Pass* createPrintCallGraphPass() { return new PrintCallGraph(); } +Pass* createPrintBoundaryPass() { return new PrintBoundary(); } } // namespace wasm diff --git a/src/passes/passes.h b/src/passes/passes.h index d7f272bbbb6..681a259a831 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -128,7 +128,7 @@ Pass* createPostEmscriptenPass(); Pass* createPrecomputePass(); Pass* createPrecomputePropagatePass(); Pass* createPrinterPass(); -Pass* createprintBoundaryPass(); +Pass* createPrintBoundaryPass(); Pass* createPrintCallGraphPass(); Pass* createPrintFeaturesPass(); Pass* createPrintFunctionMapPass(); diff --git a/src/support/json.h b/src/support/json.h index 23d0749f045..7c4d2d9e55d 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -67,6 +67,7 @@ struct Value { Ref& operator[](IString x) { return (*this->get())[x]; } }; + static Ref make() { return Ref(new Value); } template static Ref make(T t) { return Ref(new Value(t)); } enum Type { @@ -102,6 +103,7 @@ struct Value { // constructors all copy their input Value() {} explicit Value(const char* s) : type(Null) { setString(s); } + explicit Value(const std::string& s) : type(Null) { setString(s.c_str()); } explicit Value(double n) : type(Null) { setNumber(n); } explicit Value(ArrayStorage& a) : type(Null) { setArray(); From 91ae16e545d02508bdf64bfe6f6b4fffd906064f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 11:23:03 -0700 Subject: [PATCH 03/28] go --- src/passes/PrintBoundary.cpp | 37 +++++++++++++++++++++++++++++++++--- src/support/json.h | 1 + 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 2a9e54351cf..01286a6c022 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -72,8 +72,8 @@ struct PrintBoundary : public Pass { } auto import = json::Value::make(); - import["module"] = json::Value::make(func->module.toString()); - import["base"] = json::Value::make(func->base.toString()); + import["module"] = json::Value::make(func->module.view()); + import["base"] = json::Value::make(func->base.view()); import["kind"] = json::Value::make("func"); import["params"] = getTypes(func->getParams()); import["results"] = getTypes(func->getResults()); @@ -82,12 +82,43 @@ struct PrintBoundary : public Pass { } // Exports. + auto exports = json::Value::make(); + exports->setArray(); + + for (auto& exp : module->exports) { + auto export_ = json::Value::make(); + export_["name"] = json::Value::make(exp->name.view()); + const char* kind; + switch (exp->kind) { + case ExternalKind::Function: + kind = "func"; + break; + case ExternalKind::Table: + kind = "table"; + break; + case ExternalKind::Memory: + kind = "memory"; + break; + case ExternalKind::Global: + kind = "global"; + break; + case ExternalKind::Tag: + kind = "tag"; + break; + case ExternalKind::Invalid: + WASM_UNREACHABLE("invalid ExternalKind"); + } + export_["kind"] = json::Value::make(kind); + export_["type"] = getTypes(exp->type); + + exports->push_back(export_); + } // Emit the final structure json::Value root; root.setArray(2); root[0] = imports; - //root[1] = exports; + root[1] = exports; Output output(target, Flags::BinaryOption::Text); root.stringify(output.getStream(), true /* pretty */); diff --git a/src/support/json.h b/src/support/json.h index 7c4d2d9e55d..cc02c95a465 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -104,6 +104,7 @@ struct Value { Value() {} explicit Value(const char* s) : type(Null) { setString(s); } explicit Value(const std::string& s) : type(Null) { setString(s.c_str()); } + explicit Value(const std::string_view& s) : type(Null) { setString(std::string(s)); } explicit Value(double n) : type(Null) { setNumber(n); } explicit Value(ArrayStorage& a) : type(Null) { setArray(); From 377a466109661e49ac47088dffc01c0721d581c0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 11:36:55 -0700 Subject: [PATCH 04/28] go --- src/ir/module-utils.cpp | 17 +++++++++++++ src/ir/module-utils.h | 3 +++ src/passes/PrintBoundary.cpp | 49 ++++++++++++++++++++---------------- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 6ee94d35d65..ceeea1a4b70 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -293,6 +293,23 @@ void clearModule(Module& wasm) { new (&wasm) Module; } +Type getType(Module& wasm, ExternalKind kind, Name name) { + switch (kind) { + case ExternalKind::Function: + return wasm.getFunction(name)->type; + case ExternalKind::Table: + return wasm.getTable(name)->type; + case ExternalKind::Memory: + return wasm.getMemory(name)->type; + case ExternalKind::Global: + return wasm.getGlobal(name)->type; + case ExternalKind::Tag: + return wasm.getTag(name)->type; + case ExternalKind::Invalid: + WASM_UNREACHABLE("invalid ExternalKind"); + } +} + // Renaming // Rename functions along with all their uses. diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 50b67df7cea..039c90d2639 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -261,6 +261,9 @@ template inline void iterModuleItems(Module& wasm, T visitor) { } } +// Generic type finding of any import/export, similar to the iteration above. +Type getType(Module& wasm, ExternalKind kind, Name name); + // Helper class for performing an operation on all the functions in the module, // in parallel, with an Info object for each one that can contain results of // some computation that the operation performs. diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 01286a6c022..ed4ac5a003e 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -33,8 +33,10 @@ // 'module': 'foo', // foo.bar // 'base': 'bar', // 'kind': 'func', -// 'params': ['i32', '(ref func)'], -// 'results': ['f64'] +// 'type': { +// 'params': ['i32', '(ref func)'], +// 'results': ['f64'] +// }, // }, // [..] // ], @@ -49,6 +51,7 @@ // } // +#include "ir/module-utils.h" #include "pass.h" #include "support/file.h" #include "support/json.h" @@ -66,28 +69,22 @@ struct PrintBoundary : public Pass { auto imports = json::Value::make(); imports->setArray(); - for (auto& func : module->functions) { - if (!func->imported()) { - continue; - } - - auto import = json::Value::make(); - import["module"] = json::Value::make(func->module.view()); - import["base"] = json::Value::make(func->base.view()); - import["kind"] = json::Value::make("func"); - import["params"] = getTypes(func->getParams()); - import["results"] = getTypes(func->getResults()); - - imports->push_back(import); - } + ModuleUtils::iterImportable(*module, [&](ExternalKind kind, Importable* import) { + auto item = json::Value::make(); + item["module"] = json::Value::make(import->module.view()); + item["base"] = json::Value::make(import->base.view()); + item["kind"] = json::Value::make(kind); + item["type"] = getTypes(ModuleUtils::getType(kind, import->name)); + imports->push_back(item); + }); // Exports. auto exports = json::Value::make(); exports->setArray(); for (auto& exp : module->exports) { - auto export_ = json::Value::make(); - export_["name"] = json::Value::make(exp->name.view()); + auto item = json::Value::make(); + item["name"] = json::Value::make(exp->name.view()); const char* kind; switch (exp->kind) { case ExternalKind::Function: @@ -108,10 +105,10 @@ struct PrintBoundary : public Pass { case ExternalKind::Invalid: WASM_UNREACHABLE("invalid ExternalKind"); } - export_["kind"] = json::Value::make(kind); - export_["type"] = getTypes(exp->type); + item["kind"] = json::Value::make(kind); + item["type"] = getTypes(ModuleUtils::getType(kind, exp->getInternalName())); - exports->push_back(export_); + exports->push_back(item); } // Emit the final structure @@ -124,7 +121,17 @@ struct PrintBoundary : public Pass { root.stringify(output.getStream(), true /* pretty */); } + // Emits an array of multivalue types. For a signature, emits params and + // results. json::Value::Ref getTypes(Type type) { + if (type.isSignature()) { + auto sig = type.getSignature(); + auto ret= json::Value::make(); + import["params"] = getTypes(sig.params); + import["results"] = getTypes(sig.results); + return ret; + } + auto ret = json::Value::make(); ret->setArray(); for (auto t : type) { From 573cdf1a65a9b44241d40308081559ea364f358a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:00:56 -0700 Subject: [PATCH 05/28] work --- src/ir/module-utils.cpp | 17 ----------------- src/ir/module-utils.h | 3 --- src/passes/PrintBoundary.cpp | 26 +++++++++++++++++++++++--- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index ceeea1a4b70..6ee94d35d65 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -293,23 +293,6 @@ void clearModule(Module& wasm) { new (&wasm) Module; } -Type getType(Module& wasm, ExternalKind kind, Name name) { - switch (kind) { - case ExternalKind::Function: - return wasm.getFunction(name)->type; - case ExternalKind::Table: - return wasm.getTable(name)->type; - case ExternalKind::Memory: - return wasm.getMemory(name)->type; - case ExternalKind::Global: - return wasm.getGlobal(name)->type; - case ExternalKind::Tag: - return wasm.getTag(name)->type; - case ExternalKind::Invalid: - WASM_UNREACHABLE("invalid ExternalKind"); - } -} - // Renaming // Rename functions along with all their uses. diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 039c90d2639..50b67df7cea 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -261,9 +261,6 @@ template inline void iterModuleItems(Module& wasm, T visitor) { } } -// Generic type finding of any import/export, similar to the iteration above. -Type getType(Module& wasm, ExternalKind kind, Name name); - // Helper class for performing an operation on all the functions in the module, // in parallel, with an Info object for each one that can contain results of // some computation that the operation performs. diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index ed4ac5a003e..3f23e843bf1 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -74,7 +74,7 @@ struct PrintBoundary : public Pass { item["module"] = json::Value::make(import->module.view()); item["base"] = json::Value::make(import->base.view()); item["kind"] = json::Value::make(kind); - item["type"] = getTypes(ModuleUtils::getType(kind, import->name)); + item["type"] = getExternalType(kind, name); imports->push_back(item); }); @@ -106,8 +106,7 @@ struct PrintBoundary : public Pass { WASM_UNREACHABLE("invalid ExternalKind"); } item["kind"] = json::Value::make(kind); - item["type"] = getTypes(ModuleUtils::getType(kind, exp->getInternalName())); - + item["type"] = getExternalType(kind, exp->getInternalName()); exports->push_back(item); } @@ -139,6 +138,27 @@ struct PrintBoundary : public Pass { } 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) { + 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 {}; + } }; Pass* createPrintBoundaryPass() { return new PrintBoundary(); } From 88d29d177d0b43d0f3aeb02e6da6ba571bffebd6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:07:58 -0700 Subject: [PATCH 06/28] work --- src/passes/PrintBoundary.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 3f23e843bf1..4e2587a3feb 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -74,7 +74,7 @@ struct PrintBoundary : public Pass { item["module"] = json::Value::make(import->module.view()); item["base"] = json::Value::make(import->base.view()); item["kind"] = json::Value::make(kind); - item["type"] = getExternalType(kind, name); + item["type"] = getExternalType(kind, name, *module); imports->push_back(item); }); @@ -106,7 +106,7 @@ struct PrintBoundary : public Pass { WASM_UNREACHABLE("invalid ExternalKind"); } item["kind"] = json::Value::make(kind); - item["type"] = getExternalType(kind, exp->getInternalName()); + item["type"] = getExternalType(exp->kind, *exp->getInternalName(), *module); exports->push_back(item); } @@ -123,12 +123,15 @@ struct PrintBoundary : public Pass { // Emits an array of multivalue types. For a signature, emits params and // results. json::Value::Ref getTypes(Type type) { - if (type.isSignature()) { - auto sig = type.getSignature(); - auto ret= json::Value::make(); - import["params"] = getTypes(sig.params); - import["results"] = getTypes(sig.results); - return ret; + if (type.isRef()) { + auto heapType = type.getHeapType(); + if (heapType.isSignature()) { + auto sig = heapType.getSignature(); + auto ret= json::Value::make(); + ret["params"] = getTypes(sig.params); + ret["results"] = getTypes(sig.results); + return ret; + } } auto ret = json::Value::make(); @@ -141,7 +144,7 @@ struct PrintBoundary : public Pass { // 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) { + json::Value::Ref getExternalType(ExternalKind kind, Name name, Module& wasm) { switch (kind) { case ExternalKind::Function: return getTypes(wasm.getFunction(name)->type); From 97843d5a7eda5dead04cec018b2f46ebc2bf4a03 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:08:39 -0700 Subject: [PATCH 07/28] work --- src/passes/PrintBoundary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 4e2587a3feb..4ae148b3c24 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -85,7 +85,7 @@ struct PrintBoundary : public Pass { for (auto& exp : module->exports) { auto item = json::Value::make(); item["name"] = json::Value::make(exp->name.view()); - const char* kind; + const char* kind = nullptr; switch (exp->kind) { case ExternalKind::Function: kind = "func"; From 4354d3e06d40157cb63046063669979de13efe50 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:23:39 -0700 Subject: [PATCH 08/28] work --- src/passes/PrintBoundary.cpp | 12 ++++++------ src/support/json.h | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 4ae148b3c24..9a198aa5055 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -66,11 +66,11 @@ struct PrintBoundary : public Pass { std::string target = getArgumentOrDefault("print-boundary", ""); // Imports. - auto imports = json::Value::make(); + auto imports = json::Value::makeObject(); imports->setArray(); ModuleUtils::iterImportable(*module, [&](ExternalKind kind, Importable* import) { - auto item = json::Value::make(); + auto item = json::Value::makeObject(); item["module"] = json::Value::make(import->module.view()); item["base"] = json::Value::make(import->base.view()); item["kind"] = json::Value::make(kind); @@ -79,11 +79,11 @@ struct PrintBoundary : public Pass { }); // Exports. - auto exports = json::Value::make(); + auto exports = json::Value::makeObject(); exports->setArray(); for (auto& exp : module->exports) { - auto item = json::Value::make(); + auto item = json::Value::makeObject(); item["name"] = json::Value::make(exp->name.view()); const char* kind = nullptr; switch (exp->kind) { @@ -127,14 +127,14 @@ struct PrintBoundary : public Pass { auto heapType = type.getHeapType(); if (heapType.isSignature()) { auto sig = heapType.getSignature(); - auto ret= json::Value::make(); + auto ret= json::Value::makeObject(); ret["params"] = getTypes(sig.params); ret["results"] = getTypes(sig.results); return ret; } } - auto ret = json::Value::make(); + auto ret = json::Value::makeObject(); ret->setArray(); for (auto t : type) { ret->push_back(json::Value::make(t.toString())); diff --git a/src/support/json.h b/src/support/json.h index cc02c95a465..40c02328275 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -67,9 +67,14 @@ struct Value { Ref& operator[](IString x) { return (*this->get())[x]; } }; - static Ref make() { return Ref(new Value); } template static Ref make(T t) { return Ref(new Value(t)); } + static Ref makeObject() { + Ref ret(new Value); + ret->setObject(); + return ret; + } + enum Type { String = 0, Number = 1, From a3e30c6f8fb11cd584e427385a74ee02b5568645 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:41:37 -0700 Subject: [PATCH 09/28] work --- src/passes/PrintBoundary.cpp | 8 ++--- src/support/json.cpp | 70 +++++++++++++++++++++++++----------- src/support/json.h | 7 +++- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 9a198aa5055..1816b239be7 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -74,7 +74,7 @@ struct PrintBoundary : public Pass { item["module"] = json::Value::make(import->module.view()); item["base"] = json::Value::make(import->base.view()); item["kind"] = json::Value::make(kind); - item["type"] = getExternalType(kind, name, *module); + item["type"] = getExternalType(kind, import->name, *module); imports->push_back(item); }); @@ -112,9 +112,9 @@ struct PrintBoundary : public Pass { // Emit the final structure json::Value root; - root.setArray(2); - root[0] = imports; - root[1] = exports; + root.setArray(); + root.push_back(imports); + root.push_back(exports); Output output(target, Flags::BinaryOption::Text); root.stringify(output.getStream(), true /* pretty */); diff --git a/src/support/json.cpp b/src/support/json.cpp index ff393317410..7067b16e9d3 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -20,29 +20,57 @@ namespace json { void Value::stringify(std::ostream& os, bool pretty) { - if (isString()) { - std::stringstream wtf16; - [[maybe_unused]] bool valid = - wasm::String::convertWTF8ToWTF16(wtf16, getIString().view()); - assert(valid); - // TODO: Use wtf16.view() once we have C++20. - wasm::String::printEscapedJSON(os, wtf16.str()); - } else if (isArray()) { - os << '['; - auto first = true; - for (auto& item : getArray()) { - if (first) { - first = false; - } else { - // TODO pretty whitespace - os << ','; + switch (type) { + case String: { + std::stringstream wtf16; + [[maybe_unused]] bool valid = + wasm::String::convertWTF8ToWTF16(wtf16, getIString().view()); + assert(valid); + // TODO: Use wtf16.view() once we have C++20. + wasm::String::printEscapedJSON(os, wtf16.str()); + return; + } + case Array: { + os << '['; + auto first = true; + for (auto& item : getArray()) { + if (first) { + first = false; + } else { + // TODO pretty whitespace + os << ','; + } + item->stringify(os, pretty); + } + os << ']'; + return; + } + case Object: { + os << '{'; + auto first = true; + for (auto& [key, value] : getObject()) { + if (first) { + first = false; + } else { + // TODO pretty whitespace + os << ','; + } + os << "\"" << key << "\": "; + value->stringify(os, pretty); } - item->stringify(os, pretty); + os << ']'; + return; } - os << ']'; - } else { - WASM_UNREACHABLE("TODO: stringify all of JSON"); - } + case Number: + os << getNumber(); + return; + case Null: + os << "null"; + return; + case Bool: + os << (getBool() ? "true" : "false"); + return; + }; } } // namespace json diff --git a/src/support/json.h b/src/support/json.h index 40c02328275..70bdc6a2792 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -37,6 +37,7 @@ #include #include +#include "support/insert_ordered.h" #include "support/istring.h" #include "support/safe_integer.h" #include "support/string.h" @@ -87,7 +88,7 @@ struct Value { Type type = Null; using ArrayStorage = std::vector; - using ObjectStorage = std::unordered_map; + using ObjectStorage = wasm::InsertOrderedMap; // MSVC does not allow unrestricted unions: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf @@ -210,6 +211,10 @@ struct Value { assert(isArray()); return *arr; } + ObjectStorage& getObject() { + assert(isObject()); + return *obj; + } bool& getBool() { assert(isBool()); return boo; From 56ea60e9815aad9ef739b18c45eb60d310a0a1b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 15:52:46 -0700 Subject: [PATCH 10/28] work --- src/passes/PrintBoundary.cpp | 9 +++----- src/support/json.cpp | 40 +++++++++++++++++++++++++++++++----- src/support/json.h | 9 ++++++-- test/gtest/json.cpp | 14 ++++++++++++- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 1816b239be7..ad4c1157042 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -66,8 +66,7 @@ struct PrintBoundary : public Pass { std::string target = getArgumentOrDefault("print-boundary", ""); // Imports. - auto imports = json::Value::makeObject(); - imports->setArray(); + auto imports = json::Value::makeArray(); ModuleUtils::iterImportable(*module, [&](ExternalKind kind, Importable* import) { auto item = json::Value::makeObject(); @@ -79,8 +78,7 @@ struct PrintBoundary : public Pass { }); // Exports. - auto exports = json::Value::makeObject(); - exports->setArray(); + auto exports = json::Value::makeArray(); for (auto& exp : module->exports) { auto item = json::Value::makeObject(); @@ -134,8 +132,7 @@ struct PrintBoundary : public Pass { } } - auto ret = json::Value::makeObject(); - ret->setArray(); + auto ret = json::Value::makeArray(); for (auto t : type) { ret->push_back(json::Value::make(t.toString())); } diff --git a/src/support/json.cpp b/src/support/json.cpp index 7067b16e9d3..03757adf8ab 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -19,7 +19,13 @@ namespace json { -void Value::stringify(std::ostream& os, bool pretty) { +void Value::stringify(std::ostream& os, bool pretty, int indent) { + auto doIndent = [&]() { + for (int i = 0; i < indent; i++) { + os << ' '; + } + }; + switch (type) { case String: { std::stringstream wtf16; @@ -32,31 +38,55 @@ void Value::stringify(std::ostream& os, bool pretty) { } case Array: { os << '['; + indent++; auto first = true; for (auto& item : getArray()) { if (first) { first = false; } else { - // TODO pretty whitespace os << ','; + if (pretty) { + os << ' '; + } + } + if (pretty) { + os << '\n'; + doIndent(); } - item->stringify(os, pretty); + item->stringify(os, pretty, indent + 1); + } + indent--; + if (pretty) { + os << '\n'; + doIndent(); } os << ']'; return; } case Object: { os << '{'; + indent++; auto first = true; for (auto& [key, value] : getObject()) { if (first) { first = false; } else { - // TODO pretty whitespace os << ','; + if (pretty) { + os << ' '; + } + } + if (pretty) { + os << '\n'; + doIndent(); } os << "\"" << key << "\": "; - value->stringify(os, pretty); + value->stringify(os, pretty, indent + 1); + } + indent--; + if (pretty) { + os << '\n'; + doIndent(); } os << ']'; return; diff --git a/src/support/json.h b/src/support/json.h index 70bdc6a2792..3a5842fe8a0 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -68,8 +68,13 @@ struct Value { Ref& operator[](IString x) { return (*this->get())[x]; } }; + static Ref make() { return Ref(new Value); } template static Ref make(T t) { return Ref(new Value(t)); } - + static Ref makeArray() { + Ref ret(new Value); + ret->setArray(); + return ret; + } static Ref makeObject() { Ref ret(new Value); ret->setObject(); @@ -391,7 +396,7 @@ struct Value { return curr; } - void stringify(std::ostream& os, bool pretty = false); + void stringify(std::ostream& os, bool pretty = false, int indent = 0); // String operations diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 626861a626a..0fa2e76d9ca 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -3,7 +3,19 @@ using JSONTest = ::testing::Test; -TEST_F(JSONTest, Stringify) { +TEST_F(JSONTest, RoundtripString) { + // TODO: change the API to not require a copy + auto input = "[\"hello\",\"world\"]"; + auto* copy = strdup(input); + json::Value value; + value.parse(copy, json::Value::ASCII); + std::stringstream ss; + value.stringify(ss); + EXPECT_EQ(ss.str(), input); + free(copy); +} + +TEST_F(JSONTest, StringifyArray) { // TODO: change the API to not require a copy auto input = "[\"hello\",\"world\"]"; auto* copy = strdup(input); From fb5ff358515891782cc8672428003ee0bf490a4f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:00:54 -0700 Subject: [PATCH 11/28] work --- src/support/json.cpp | 6 ------ test/gtest/json.cpp | 27 ++++++++++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index 03757adf8ab..2638ca2bfee 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -45,9 +45,6 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { first = false; } else { os << ','; - if (pretty) { - os << ' '; - } } if (pretty) { os << '\n'; @@ -72,9 +69,6 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { first = false; } else { os << ','; - if (pretty) { - os << ' '; - } } if (pretty) { os << '\n'; diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 0fa2e76d9ca..d0ce24c5305 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -15,14 +15,23 @@ TEST_F(JSONTest, RoundtripString) { free(copy); } -TEST_F(JSONTest, StringifyArray) { - // TODO: change the API to not require a copy - auto input = "[\"hello\",\"world\"]"; - auto* copy = strdup(input); - json::Value value; - value.parse(copy, json::Value::ASCII); +static void checkOutput(json::Value::Ref ref, std::string expected, bool pretty=false) { std::stringstream ss; - value.stringify(ss); - EXPECT_EQ(ss.str(), input); - free(copy); + ref->stringify(ss, pretty); + EXPECT_EQ(ss.str(), expected); +} + +static void checkPrettyOutput(json::Value::Ref ref, std::string expected) { + checkOutput(ref, expected, true); +} + +TEST_F(JSONTest, StringifyArray) { + auto array = json::Value::makeArray(); + array->push_back(json::Value::make(42)); + array->push_back(json::Value::make("1337")); + checkOutput(array, "[42,\"1337\"]"); + checkPrettyOutput(array, R"([ + 42, + "1337" +])"); } From b8492ff1a05b2e377ae0c7f86e3ed445b37f9ca2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:11:08 -0700 Subject: [PATCH 12/28] work --- test/gtest/json.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index d0ce24c5305..2fe3981e62b 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -29,9 +29,11 @@ TEST_F(JSONTest, StringifyArray) { auto array = json::Value::makeArray(); array->push_back(json::Value::make(42)); array->push_back(json::Value::make("1337")); - checkOutput(array, "[42,\"1337\"]"); + array->push_back(json::Value::make()); // null + checkOutput(array, "[42,\"1337\",null]"); checkPrettyOutput(array, R"([ 42, - "1337" + "1337", + null ])"); } From e5ca754122be2a15e97c335d508d4e4d63107cc7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:13:53 -0700 Subject: [PATCH 13/28] work --- src/support/json.cpp | 7 +++++-- test/gtest/json.cpp | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index 2638ca2bfee..3d9a2f3629c 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -74,7 +74,10 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { os << '\n'; doIndent(); } - os << "\"" << key << "\": "; + os << "\"" << key << "\":"; + if (pretty) { + os << ' '; + } value->stringify(os, pretty, indent + 1); } indent--; @@ -82,7 +85,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { os << '\n'; doIndent(); } - os << ']'; + os << '}'; return; } case Number: diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 2fe3981e62b..92d32e761b6 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -37,3 +37,14 @@ TEST_F(JSONTest, StringifyArray) { null ])"); } + +TEST_F(JSONTest, StringifyObject) { + auto object = json::Value::makeObject(); + object["foo"] = json::Value::make(42); + object["bar"] = json::Value::make("1337"); + checkOutput(object, "{\"foo\":42,\"bar\":\"1337\"}"); + checkPrettyOutput(object, R"({ + "foo": 42, + "bar": "1337" +})"); +} From 5b67220f5bbe9664002cf7bb4d57270fee99c0ae Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:15:50 -0700 Subject: [PATCH 14/28] work --- test/gtest/json.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 92d32e761b6..52d997dbd28 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -48,3 +48,24 @@ TEST_F(JSONTest, StringifyObject) { "bar": "1337" })"); } + +TEST_F(JSONTest, StringifyNesting) { + auto array = json::Value::makeArray(); + auto object = json::Value::makeObject(); + auto array1 = json::Value::makeArray(); + auto object1 = json::Value::makeObject(); + array->push_back(object); + object["body"] = array1; + array1->push_back(object1); + object1["value"] = json::Value::make(42); + checkPrettyOutput(array, R"([ + { + "body": [ + { + " + 42, + "1337", + null +])"); +} + From c23c910a358658c2f1097493aa47df192684a9b9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:20:21 -0700 Subject: [PATCH 15/28] work --- src/support/json.cpp | 4 ++-- test/gtest/json.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index 3d9a2f3629c..506f9cc475f 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -50,7 +50,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { os << '\n'; doIndent(); } - item->stringify(os, pretty, indent + 1); + item->stringify(os, pretty, indent); } indent--; if (pretty) { @@ -78,7 +78,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { if (pretty) { os << ' '; } - value->stringify(os, pretty, indent + 1); + value->stringify(os, pretty, indent); } indent--; if (pretty) { diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 52d997dbd28..bcb12470c5c 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -62,10 +62,10 @@ TEST_F(JSONTest, StringifyNesting) { { "body": [ { - " - 42, - "1337", - null + "value": 42 + } + ] + } ])"); } From a2812a0caf47cecc87c18e04e432beedc20fce4f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:20:38 -0700 Subject: [PATCH 16/28] format --- src/passes/PrintBoundary.cpp | 22 ++++++++++++---------- src/support/json.h | 4 +++- test/gtest/json.cpp | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index ad4c1157042..0c237736763 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -68,14 +68,15 @@ struct PrintBoundary : public Pass { // 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"] = json::Value::make(kind); - item["type"] = getExternalType(kind, import->name, *module); - imports->push_back(item); - }); + 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"] = json::Value::make(kind); + item["type"] = getExternalType(kind, import->name, *module); + imports->push_back(item); + }); // Exports. auto exports = json::Value::makeArray(); @@ -104,7 +105,8 @@ struct PrintBoundary : public Pass { WASM_UNREACHABLE("invalid ExternalKind"); } item["kind"] = json::Value::make(kind); - item["type"] = getExternalType(exp->kind, *exp->getInternalName(), *module); + item["type"] = + getExternalType(exp->kind, *exp->getInternalName(), *module); exports->push_back(item); } @@ -125,7 +127,7 @@ struct PrintBoundary : public Pass { auto heapType = type.getHeapType(); if (heapType.isSignature()) { auto sig = heapType.getSignature(); - auto ret= json::Value::makeObject(); + auto ret = json::Value::makeObject(); ret["params"] = getTypes(sig.params); ret["results"] = getTypes(sig.results); return ret; diff --git a/src/support/json.h b/src/support/json.h index 3a5842fe8a0..98e46b063fd 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -115,7 +115,9 @@ struct Value { Value() {} explicit Value(const char* s) : type(Null) { setString(s); } explicit Value(const std::string& s) : type(Null) { setString(s.c_str()); } - explicit Value(const std::string_view& s) : type(Null) { setString(std::string(s)); } + explicit Value(const std::string_view& s) : type(Null) { + setString(std::string(s)); + } explicit Value(double n) : type(Null) { setNumber(n); } explicit Value(ArrayStorage& a) : type(Null) { setArray(); diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index bcb12470c5c..5cd737ac6df 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -15,7 +15,8 @@ TEST_F(JSONTest, RoundtripString) { free(copy); } -static void checkOutput(json::Value::Ref ref, std::string expected, bool pretty=false) { +static void +checkOutput(json::Value::Ref ref, std::string expected, bool pretty = false) { std::stringstream ss; ref->stringify(ss, pretty); EXPECT_EQ(ss.str(), expected); @@ -68,4 +69,3 @@ TEST_F(JSONTest, StringifyNesting) { } ])"); } - From 6b1ea4a352922b0183d011310b344cb04e805446 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:22:49 -0700 Subject: [PATCH 17/28] work --- test/lit/passes/print-boundary.wast | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/lit/passes/print-boundary.wast diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast new file mode 100644 index 00000000000..b1caf0b4801 --- /dev/null +++ b/test/lit/passes/print-boundary.wast @@ -0,0 +1,63 @@ +(module + (type $struct (struct)) + + (import "module" "base" (func $foo (param i32) (param f64) (result anyref))) + + (import "module2" "other" (func $bar (result i32 f32))) + + (export "one" (func $one)) + + (func $one (param $x (ref $struct)) (result i32 i32 i32) + (unreachable) + ) +) + +;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck + +;; CHECK: [ +;; CHECK-NEXT: [ +;; CHECK-NEXT: { +;; CHECK-NEXT: "module": "module", +;; CHECK-NEXT: "base": "base", +;; CHECK-NEXT: "kind": 0, +;; 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": 0, +;; 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: [ +;; 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: ] From 71ce04be636c7e1cb0421fe14fc48c7fd83af2b0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 12 May 2026 16:23:20 -0700 Subject: [PATCH 18/28] work --- test/lit/passes/print-boundary.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index b1caf0b4801..300d64d34b6 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -12,7 +12,7 @@ ) ) -;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck +;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s ;; CHECK: [ ;; CHECK-NEXT: [ From 4b500a200fc04211d6373fcd02933ae15b9549e8 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 09:01:32 -0700 Subject: [PATCH 19/28] work --- src/support/json.cpp | 99 ++++++++++++++++++++++++++++++++++---------- src/support/json.h | 24 ++++++++++- test/gtest/json.cpp | 57 ++++++++++++++++++++++++- 3 files changed, 155 insertions(+), 25 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index ff393317410..506f9cc475f 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -19,30 +19,85 @@ namespace json { -void Value::stringify(std::ostream& os, bool pretty) { - if (isString()) { - std::stringstream wtf16; - [[maybe_unused]] bool valid = - wasm::String::convertWTF8ToWTF16(wtf16, getIString().view()); - assert(valid); - // TODO: Use wtf16.view() once we have C++20. - wasm::String::printEscapedJSON(os, wtf16.str()); - } else if (isArray()) { - os << '['; - auto first = true; - for (auto& item : getArray()) { - if (first) { - first = false; - } else { - // TODO pretty whitespace - os << ','; +void Value::stringify(std::ostream& os, bool pretty, int indent) { + auto doIndent = [&]() { + for (int i = 0; i < indent; i++) { + os << ' '; + } + }; + + switch (type) { + case String: { + std::stringstream wtf16; + [[maybe_unused]] bool valid = + wasm::String::convertWTF8ToWTF16(wtf16, getIString().view()); + assert(valid); + // TODO: Use wtf16.view() once we have C++20. + wasm::String::printEscapedJSON(os, wtf16.str()); + return; + } + case Array: { + os << '['; + indent++; + auto first = true; + for (auto& item : getArray()) { + if (first) { + first = false; + } else { + os << ','; + } + if (pretty) { + os << '\n'; + doIndent(); + } + item->stringify(os, pretty, indent); + } + indent--; + if (pretty) { + os << '\n'; + doIndent(); + } + os << ']'; + return; + } + case Object: { + os << '{'; + indent++; + auto first = true; + for (auto& [key, value] : getObject()) { + if (first) { + first = false; + } else { + os << ','; + } + if (pretty) { + os << '\n'; + doIndent(); + } + os << "\"" << key << "\":"; + if (pretty) { + os << ' '; + } + value->stringify(os, pretty, indent); + } + indent--; + if (pretty) { + os << '\n'; + doIndent(); } - item->stringify(os, pretty); + os << '}'; + return; } - os << ']'; - } else { - WASM_UNREACHABLE("TODO: stringify all of JSON"); - } + case Number: + os << getNumber(); + return; + case Null: + os << "null"; + return; + case Bool: + os << (getBool() ? "true" : "false"); + return; + }; } } // namespace json diff --git a/src/support/json.h b/src/support/json.h index 23d0749f045..98e46b063fd 100644 --- a/src/support/json.h +++ b/src/support/json.h @@ -37,6 +37,7 @@ #include #include +#include "support/insert_ordered.h" #include "support/istring.h" #include "support/safe_integer.h" #include "support/string.h" @@ -67,7 +68,18 @@ struct Value { Ref& operator[](IString x) { return (*this->get())[x]; } }; + static Ref make() { return Ref(new Value); } template static Ref make(T t) { return Ref(new Value(t)); } + static Ref makeArray() { + Ref ret(new Value); + ret->setArray(); + return ret; + } + static Ref makeObject() { + Ref ret(new Value); + ret->setObject(); + return ret; + } enum Type { String = 0, @@ -81,7 +93,7 @@ struct Value { Type type = Null; using ArrayStorage = std::vector; - using ObjectStorage = std::unordered_map; + using ObjectStorage = wasm::InsertOrderedMap; // MSVC does not allow unrestricted unions: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf @@ -102,6 +114,10 @@ struct Value { // constructors all copy their input Value() {} explicit Value(const char* s) : type(Null) { setString(s); } + explicit Value(const std::string& s) : type(Null) { setString(s.c_str()); } + explicit Value(const std::string_view& s) : type(Null) { + setString(std::string(s)); + } explicit Value(double n) : type(Null) { setNumber(n); } explicit Value(ArrayStorage& a) : type(Null) { setArray(); @@ -202,6 +218,10 @@ struct Value { assert(isArray()); return *arr; } + ObjectStorage& getObject() { + assert(isObject()); + return *obj; + } bool& getBool() { assert(isBool()); return boo; @@ -378,7 +398,7 @@ struct Value { return curr; } - void stringify(std::ostream& os, bool pretty = false); + void stringify(std::ostream& os, bool pretty = false, int indent = 0); // String operations diff --git a/test/gtest/json.cpp b/test/gtest/json.cpp index 626861a626a..5cd737ac6df 100644 --- a/test/gtest/json.cpp +++ b/test/gtest/json.cpp @@ -3,7 +3,7 @@ using JSONTest = ::testing::Test; -TEST_F(JSONTest, Stringify) { +TEST_F(JSONTest, RoundtripString) { // TODO: change the API to not require a copy auto input = "[\"hello\",\"world\"]"; auto* copy = strdup(input); @@ -14,3 +14,58 @@ TEST_F(JSONTest, Stringify) { EXPECT_EQ(ss.str(), input); free(copy); } + +static void +checkOutput(json::Value::Ref ref, std::string expected, bool pretty = false) { + std::stringstream ss; + ref->stringify(ss, pretty); + EXPECT_EQ(ss.str(), expected); +} + +static void checkPrettyOutput(json::Value::Ref ref, std::string expected) { + checkOutput(ref, expected, true); +} + +TEST_F(JSONTest, StringifyArray) { + auto array = json::Value::makeArray(); + array->push_back(json::Value::make(42)); + array->push_back(json::Value::make("1337")); + array->push_back(json::Value::make()); // null + checkOutput(array, "[42,\"1337\",null]"); + checkPrettyOutput(array, R"([ + 42, + "1337", + null +])"); +} + +TEST_F(JSONTest, StringifyObject) { + auto object = json::Value::makeObject(); + object["foo"] = json::Value::make(42); + object["bar"] = json::Value::make("1337"); + checkOutput(object, "{\"foo\":42,\"bar\":\"1337\"}"); + checkPrettyOutput(object, R"({ + "foo": 42, + "bar": "1337" +})"); +} + +TEST_F(JSONTest, StringifyNesting) { + auto array = json::Value::makeArray(); + auto object = json::Value::makeObject(); + auto array1 = json::Value::makeArray(); + auto object1 = json::Value::makeObject(); + array->push_back(object); + object["body"] = array1; + array1->push_back(object1); + object1["value"] = json::Value::make(42); + checkPrettyOutput(array, R"([ + { + "body": [ + { + "value": 42 + } + ] + } +])"); +} From 7d01d8861183e9666e1a662f1d9dc3c5956f6a5a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 11:41:46 -0700 Subject: [PATCH 20/28] c++20 --- src/support/json.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index 506f9cc475f..111ccd4e6eb 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -32,8 +32,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { [[maybe_unused]] bool valid = wasm::String::convertWTF8ToWTF16(wtf16, getIString().view()); assert(valid); - // TODO: Use wtf16.view() once we have C++20. - wasm::String::printEscapedJSON(os, wtf16.str()); + wasm::String::printEscapedJSON(os, wtf16.view()); return; } case Array: { From 43f58d2fb5d232dce73f940a31254b6d74ff232d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 11:42:00 -0700 Subject: [PATCH 21/28] maybeNewline helper --- src/support/json.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/support/json.cpp b/src/support/json.cpp index 111ccd4e6eb..94f3df082e7 100644 --- a/src/support/json.cpp +++ b/src/support/json.cpp @@ -26,6 +26,13 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { } }; + auto maybeNewline = [&]() { + if (pretty) { + os << '\n'; + doIndent(); + } + }; + switch (type) { case String: { std::stringstream wtf16; @@ -45,17 +52,11 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { } else { os << ','; } - if (pretty) { - os << '\n'; - doIndent(); - } + maybeNewline(); item->stringify(os, pretty, indent); } indent--; - if (pretty) { - os << '\n'; - doIndent(); - } + maybeNewline(); os << ']'; return; } @@ -69,10 +70,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { } else { os << ','; } - if (pretty) { - os << '\n'; - doIndent(); - } + maybeNewline(); os << "\"" << key << "\":"; if (pretty) { os << ' '; @@ -80,10 +78,7 @@ void Value::stringify(std::ostream& os, bool pretty, int indent) { value->stringify(os, pretty, indent); } indent--; - if (pretty) { - os << '\n'; - doIndent(); - } + maybeNewline(); os << '}'; return; } From 5d8f9e61404eb8faa858a5ee09462691228640e3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:03:46 -0700 Subject: [PATCH 22/28] help --- test/lit/help/wasm-metadce.test | 2 ++ test/lit/help/wasm-opt.test | 2 ++ test/lit/help/wasm2js.test | 2 ++ 3 files changed, 6 insertions(+) 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 From 1fa90862265693563dd5b4a66b93b4a7bfbef596 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:13:49 -0700 Subject: [PATCH 23/28] fix --- src/passes/PrintBoundary.cpp | 48 ++++++++++++++++------------- test/lit/passes/print-boundary.wast | 15 +++++++-- 2 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 0c237736763..94f876d98ea 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -73,7 +73,7 @@ struct PrintBoundary : public Pass { auto item = json::Value::makeObject(); item["module"] = json::Value::make(import->module.view()); item["base"] = json::Value::make(import->base.view()); - item["kind"] = json::Value::make(kind); + item["kind"] = getKindName(kind); item["type"] = getExternalType(kind, import->name, *module); imports->push_back(item); }); @@ -84,27 +84,7 @@ struct PrintBoundary : public Pass { for (auto& exp : module->exports) { auto item = json::Value::makeObject(); item["name"] = json::Value::make(exp->name.view()); - const char* kind = nullptr; - switch (exp->kind) { - case ExternalKind::Function: - kind = "func"; - break; - case ExternalKind::Table: - kind = "table"; - break; - case ExternalKind::Memory: - kind = "memory"; - break; - case ExternalKind::Global: - kind = "global"; - break; - case ExternalKind::Tag: - kind = "tag"; - break; - case ExternalKind::Invalid: - WASM_UNREACHABLE("invalid ExternalKind"); - } - item["kind"] = json::Value::make(kind); + item["kind"] = getKindName(exp->kind); item["type"] = getExternalType(exp->kind, *exp->getInternalName(), *module); exports->push_back(item); @@ -161,6 +141,30 @@ struct PrintBoundary : public Pass { } 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(); } diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index 300d64d34b6..936b451e5da 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -5,8 +5,12 @@ (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) ) @@ -19,7 +23,7 @@ ;; CHECK-NEXT: { ;; CHECK-NEXT: "module": "module", ;; CHECK-NEXT: "base": "base", -;; CHECK-NEXT: "kind": 0, +;; CHECK-NEXT: "kind": "func", ;; CHECK-NEXT: "type": { ;; CHECK-NEXT: "params": [ ;; CHECK-NEXT: "i32", @@ -33,7 +37,7 @@ ;; CHECK-NEXT: { ;; CHECK-NEXT: "module": "module2", ;; CHECK-NEXT: "base": "other", -;; CHECK-NEXT: "kind": 0, +;; CHECK-NEXT: "kind": "func", ;; CHECK-NEXT: "type": { ;; CHECK-NEXT: "params": [ ;; CHECK-NEXT: ], @@ -58,6 +62,13 @@ ;; CHECK-NEXT: "i32" ;; CHECK-NEXT: ] ;; CHECK-NEXT: } +;; CHECK-NEXT: }, +;; CHECK-NEXT: { +;; CHECK-NEXT: "name": "glob", +;; CHECK-NEXT: "kind": "global", +;; CHECK-NEXT: "type": [ +;; CHECK-NEXT: "i32" +;; CHECK-NEXT: ] ;; CHECK-NEXT: } ;; CHECK-NEXT: ] ;; CHECK-NEXT: ] From 4046717ea24b42dec69769788d23d942ce71b28f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:16:02 -0700 Subject: [PATCH 24/28] nice --- src/passes/PrintBoundary.cpp | 6 +++--- test/lit/passes/print-boundary.wast | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 94f876d98ea..da7f8b8a08c 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -92,9 +92,9 @@ struct PrintBoundary : public Pass { // Emit the final structure json::Value root; - root.setArray(); - root.push_back(imports); - root.push_back(exports); + root.setObject(); + root["imports"] = imports; + root["exports"] = exports; Output output(target, Flags::BinaryOption::Text); root.stringify(output.getStream(), true /* pretty */); diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index 936b451e5da..6a1c27ada4f 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -18,8 +18,8 @@ ;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s -;; CHECK: [ -;; CHECK-NEXT: [ +;; CHECK: { +;; CHECK-NEXT: "imports": ;; CHECK-NEXT: { ;; CHECK-NEXT: "module": "module", ;; CHECK-NEXT: "base": "base", @@ -48,7 +48,7 @@ ;; CHECK-NEXT: } ;; CHECK-NEXT: } ;; CHECK-NEXT: ], -;; CHECK-NEXT: [ +;; CHECK-NEXT: "exports": [ ;; CHECK-NEXT: { ;; CHECK-NEXT: "name": "one", ;; CHECK-NEXT: "kind": "func", @@ -71,4 +71,4 @@ ;; CHECK-NEXT: ] ;; CHECK-NEXT: } ;; CHECK-NEXT: ] -;; CHECK-NEXT: ] +;; CHECK-NEXT: } From 0bb32cb91827a3dac64d287c3a8b4dc7cae631e0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:16:12 -0700 Subject: [PATCH 25/28] nice --- test/lit/passes/print-boundary.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index 6a1c27ada4f..120b5b39858 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -18,7 +18,7 @@ ;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s -;; CHECK: { +;; CHECK: { ;; CHECK-NEXT: "imports": ;; CHECK-NEXT: { ;; CHECK-NEXT: "module": "module", From 9de1bf5626dd620943b9396c69e234f44bb96e03 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 14 May 2026 13:16:27 -0700 Subject: [PATCH 26/28] nice --- test/lit/passes/print-boundary.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index 120b5b39858..f58d7c16345 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -19,7 +19,7 @@ ;; RUN: wasm-opt %s -all --print-boundary -S -o - | filecheck %s ;; CHECK: { -;; CHECK-NEXT: "imports": +;; CHECK-NEXT: "imports": [ ;; CHECK-NEXT: { ;; CHECK-NEXT: "module": "module", ;; CHECK-NEXT: "base": "base", From 24308d4e7cd6436a649e51d63bedb15e68ce29a2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 May 2026 09:45:44 -0700 Subject: [PATCH 27/28] polish API: avoid lists for globals etc. --- src/passes/PrintBoundary.cpp | 14 +++++++++++--- test/lit/passes/print-boundary.wast | 4 +--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index da7f8b8a08c..6469d5e8c3a 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -102,18 +102,26 @@ struct PrintBoundary : public Pass { // Emits an array of multivalue types. For a signature, emits params and // results. - json::Value::Ref getTypes(Type type) { + // + // 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(); - ret["params"] = getTypes(sig.params); - ret["results"] = getTypes(sig.results); + // 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())); diff --git a/test/lit/passes/print-boundary.wast b/test/lit/passes/print-boundary.wast index f58d7c16345..850618f07b1 100644 --- a/test/lit/passes/print-boundary.wast +++ b/test/lit/passes/print-boundary.wast @@ -66,9 +66,7 @@ ;; CHECK-NEXT: { ;; CHECK-NEXT: "name": "glob", ;; CHECK-NEXT: "kind": "global", -;; CHECK-NEXT: "type": [ -;; CHECK-NEXT: "i32" -;; CHECK-NEXT: ] +;; CHECK-NEXT: "type": "i32" ;; CHECK-NEXT: } ;; CHECK-NEXT: ] ;; CHECK-NEXT: } From 03ea8b6d3332de68ba8e1cc1153d5495c5501bb2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 15 May 2026 11:34:52 -0700 Subject: [PATCH 28/28] format --- src/passes/PrintBoundary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/PrintBoundary.cpp b/src/passes/PrintBoundary.cpp index 6469d5e8c3a..371e642e2d8 100644 --- a/src/passes/PrintBoundary.cpp +++ b/src/passes/PrintBoundary.cpp @@ -104,7 +104,7 @@ struct PrintBoundary : public Pass { // results. // // We emit an array only when needed, unless forceArray is set. - json::Value::Ref getTypes(Type type, bool forceArray=false) { + json::Value::Ref getTypes(Type type, bool forceArray = false) { if (type.isRef()) { auto heapType = type.getHeapType(); if (heapType.isSignature()) {