/src/WasmEdge/lib/validator/component_validator.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | // SPDX-FileCopyrightText: Copyright The WasmEdge Authors |
3 | | |
4 | | #include "common/errinfo.h" |
5 | | #include "common/spdlog.h" |
6 | | #include "validator/component_name.h" |
7 | | #include "validator/validator.h" |
8 | | |
9 | | #include <algorithm> |
10 | | #include <unordered_set> |
11 | | #include <variant> |
12 | | |
13 | | namespace WasmEdge { |
14 | | namespace Validator { |
15 | | |
16 | | using namespace std::literals; |
17 | | |
18 | | namespace { |
19 | 2.75k | std::string toLowerStr(std::string_view SV) { |
20 | 2.75k | std::string Result(SV); |
21 | 2.75k | std::transform( |
22 | 2.75k | Result.begin(), Result.end(), Result.begin(), |
23 | 5.44k | [](unsigned char C) { return static_cast<char>(std::tolower(C)); }); |
24 | 2.75k | return Result; |
25 | 2.75k | } |
26 | | |
27 | | // Maps a component-side ExternDesc::DescType to its Sort::SortType. |
28 | | // Returns nullopt for `CoreType` (= `(core module (type i))`), which has no |
29 | | // representation in Sort::SortType; callers must handle that sort separately |
30 | | // via Sort::CoreSortType::Module. |
31 | | std::optional<AST::Component::Sort::SortType> |
32 | 281 | descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept { |
33 | 281 | switch (DT) { |
34 | 0 | case AST::Component::ExternDesc::DescType::CoreType: |
35 | 0 | return std::nullopt; |
36 | 19 | case AST::Component::ExternDesc::DescType::FuncType: |
37 | 19 | return AST::Component::Sort::SortType::Func; |
38 | 242 | case AST::Component::ExternDesc::DescType::ValueBound: |
39 | 242 | return AST::Component::Sort::SortType::Value; |
40 | 19 | case AST::Component::ExternDesc::DescType::TypeBound: |
41 | 19 | return AST::Component::Sort::SortType::Type; |
42 | 0 | case AST::Component::ExternDesc::DescType::ComponentType: |
43 | 0 | return AST::Component::Sort::SortType::Component; |
44 | 1 | case AST::Component::ExternDesc::DescType::InstanceType: |
45 | 1 | return AST::Component::Sort::SortType::Instance; |
46 | 0 | default: |
47 | 0 | assumingUnreachable(); |
48 | 281 | } |
49 | 281 | } |
50 | | |
51 | | // Shallow sort-kind match between a Sort and an ExternDesc. The spec's |
52 | | // instantiation / export-ascription rules require the supplied sortidx to be |
53 | | // a subtype of the externdesc. Here we only enforce that the kind agrees; |
54 | | // deep structural subtyping (record fields, func signatures, etc.) is not |
55 | | // yet implemented and is the main remaining correctness gap at these sites. |
56 | | bool sortMatchesDescType(const AST::Component::Sort &S, |
57 | 39 | AST::Component::ExternDesc::DescType DT) noexcept { |
58 | 39 | auto Mapped = descTypeToSortType(DT); |
59 | 39 | if (S.isCore()) { |
60 | 1 | return !Mapped.has_value() && |
61 | 0 | S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module; |
62 | 1 | } |
63 | 38 | return Mapped.has_value() && S.getSortType() == *Mapped; |
64 | 39 | } |
65 | | |
66 | | // Fallback type-index lookup against an InstanceType's own local |
67 | | // type-decl space (used when the outer ComponentContext scope doesn't |
68 | | // own the InstanceType). |
69 | | const AST::Component::InstanceType * |
70 | | resolveNestedInstanceType(const AST::Component::InstanceType &Parent, |
71 | 0 | uint32_t TypeIdx) noexcept { |
72 | 0 | uint32_t LocalIdx = 0; |
73 | 0 | for (const auto &LocalDecl : Parent.getDecl()) { |
74 | 0 | if (!LocalDecl.isType()) { |
75 | 0 | continue; |
76 | 0 | } |
77 | 0 | if (LocalIdx == TypeIdx) { |
78 | 0 | const auto *LocalDT = LocalDecl.getType(); |
79 | 0 | if (LocalDT != nullptr && LocalDT->isInstanceType()) { |
80 | 0 | return &LocalDT->getInstanceType(); |
81 | 0 | } |
82 | 0 | return nullptr; |
83 | 0 | } |
84 | 0 | LocalIdx++; |
85 | 0 | } |
86 | 0 | return nullptr; |
87 | 0 | } |
88 | | |
89 | | // Resolve a type index in `Comp`'s own type index space to an InstanceType. |
90 | | // Returns nullptr when the index does not refer to an inline InstanceType |
91 | | // definition — callers treat nullptr as "no required shape" and fall back |
92 | | // to inferred exports. TypeBound imports and outer-alias type imports |
93 | | // currently fall through to nullptr; a more complete resolver would walk |
94 | | // the alias chain to recover the underlying InstanceType. |
95 | | const AST::Component::InstanceType * |
96 | | resolveChildInstanceType(const AST::Component::Component &Comp, |
97 | 0 | uint32_t TypeIdx) { |
98 | 0 | uint32_t CurrentIdx = 0; |
99 | 0 | for (const auto &Sec : Comp.getSections()) { |
100 | 0 | if (std::holds_alternative<AST::Component::TypeSection>(Sec)) { |
101 | 0 | const auto &TSec = std::get<AST::Component::TypeSection>(Sec); |
102 | 0 | for (const auto &DT : TSec.getContent()) { |
103 | 0 | if (CurrentIdx == TypeIdx) { |
104 | 0 | if (DT.isInstanceType()) { |
105 | 0 | return &DT.getInstanceType(); |
106 | 0 | } |
107 | 0 | return nullptr; |
108 | 0 | } |
109 | 0 | CurrentIdx++; |
110 | 0 | } |
111 | 0 | } else if (std::holds_alternative<AST::Component::ImportSection>(Sec)) { |
112 | 0 | const auto &ISec = std::get<AST::Component::ImportSection>(Sec); |
113 | 0 | for (const auto &Import : ISec.getContent()) { |
114 | 0 | if (Import.getDesc().getDescType() == |
115 | 0 | AST::Component::ExternDesc::DescType::TypeBound) { |
116 | 0 | if (CurrentIdx == TypeIdx) { |
117 | 0 | return nullptr; |
118 | 0 | } |
119 | 0 | CurrentIdx++; |
120 | 0 | } |
121 | 0 | } |
122 | 0 | } else if (std::holds_alternative<AST::Component::AliasSection>(Sec)) { |
123 | 0 | const auto &ASec = std::get<AST::Component::AliasSection>(Sec); |
124 | 0 | for (const auto &Alias : ASec.getContent()) { |
125 | 0 | if (!Alias.getSort().isCore() && |
126 | 0 | Alias.getSort().getSortType() == |
127 | 0 | AST::Component::Sort::SortType::Type) { |
128 | 0 | if (CurrentIdx == TypeIdx) { |
129 | 0 | return nullptr; |
130 | 0 | } |
131 | 0 | CurrentIdx++; |
132 | 0 | } |
133 | 0 | } |
134 | 0 | } |
135 | 0 | } |
136 | 0 | return nullptr; |
137 | 0 | } |
138 | | |
139 | | // Validate that a name may appear at an export position: reject the |
140 | | // `relative-url=` prefix (not part of the extern-name grammar) and any |
141 | | // plainname/interfacename kind that isn't allowed on an export. |
142 | 1.13k | Expect<ComponentName> validateExportName(std::string_view Name) noexcept { |
143 | 1.13k | if (Name.rfind("relative-url="sv, 0) == 0) { |
144 | 0 | spdlog::error(ErrCode::Value::InvalidExternName); |
145 | 0 | spdlog::error(" Export name '{}' is not a valid extern name"sv, Name); |
146 | 0 | return Unexpect(ErrCode::Value::InvalidExternName); |
147 | 0 | } |
148 | 2.18k | EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name)); |
149 | 2.18k | switch (CName.getKind()) { |
150 | 383 | case ComponentNameKind::Label: |
151 | 703 | case ComponentNameKind::Constructor: |
152 | 703 | case ComponentNameKind::Method: |
153 | 756 | case ComponentNameKind::Static: |
154 | 1.04k | case ComponentNameKind::InterfaceType: |
155 | 1.04k | return CName; |
156 | 2 | default: |
157 | 2 | spdlog::error(ErrCode::Value::InvalidExportName); |
158 | 2 | spdlog::error(" Export name '{}' kind is not valid for exports"sv, Name); |
159 | 2 | return Unexpect(ErrCode::Value::InvalidExportName); |
160 | 2.18k | } |
161 | 2.18k | } |
162 | | |
163 | | } // namespace |
164 | | |
165 | | void Validator::populateInstanceFromType( |
166 | 107 | uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept { |
167 | 107 | for (const auto &Decl : IT.getDecl()) { |
168 | 16 | if (!Decl.isExportDecl()) { |
169 | 3 | continue; |
170 | 3 | } |
171 | 13 | const auto &Exp = Decl.getExport(); |
172 | 13 | const auto &ED = Exp.getExternDesc(); |
173 | 13 | auto ST = descTypeToSortType(ED.getDescType()); |
174 | 13 | if (!ST.has_value()) { |
175 | | // InstanceExport::ST has no `(core module)` variant — skip rather |
176 | | // than crash. TODO: extend InstanceExport with a core-sort alternative. |
177 | 0 | spdlog::debug( |
178 | 0 | " populateInstanceFromType: skipping `(core module)` export " |
179 | 0 | "'{}'"sv, |
180 | 0 | Exp.getName()); |
181 | 0 | continue; |
182 | 0 | } |
183 | 13 | const AST::Component::InstanceType *NestedIT = nullptr; |
184 | 13 | if (ED.getDescType() == |
185 | 13 | AST::Component::ExternDesc::DescType::InstanceType) { |
186 | 0 | NestedIT = CompCtx.getInstanceType(ED.getTypeIndex()); |
187 | 0 | if (NestedIT == nullptr) { |
188 | 0 | NestedIT = resolveNestedInstanceType(IT, ED.getTypeIndex()); |
189 | 0 | } |
190 | 0 | } |
191 | | // Resource-typed exports carry a canonical id so an alias-export of |
192 | | // the type slot can preserve identity. `(sub resource)` introduces a |
193 | | // fresh id; `(eq i)` should inherit, but cross-scope resource lookup |
194 | | // inside an InstanceType body is not yet implemented (TODO). |
195 | 13 | std::optional<uint64_t> ResourceId; |
196 | 13 | if (ED.getDescType() == AST::Component::ExternDesc::DescType::TypeBound && |
197 | 0 | !ED.isEqType()) { |
198 | 0 | ResourceId = CompCtx.allocateFreshResourceId(); |
199 | 0 | } |
200 | 13 | CompCtx.addInstanceExport(InstIdx, Exp.getName(), *ST, NestedIT, |
201 | 13 | /*NestedInstIdx=*/std::nullopt, ResourceId); |
202 | 13 | } |
203 | 107 | } |
204 | | |
205 | | bool Validator::exportSatisfies( |
206 | | const AST::Component::InstanceType &RequiredCtx, |
207 | | const ComponentContext::InstanceExport &Provided, |
208 | 0 | const AST::Component::ExternDesc &Required) const noexcept { |
209 | 0 | auto RequiredST = descTypeToSortType(Required.getDescType()); |
210 | 0 | if (!RequiredST.has_value()) { |
211 | | // `(core module)` has no InstanceExport::ST variant — no constraint. |
212 | 0 | return true; |
213 | 0 | } |
214 | 0 | if (Provided.ST != *RequiredST) { |
215 | 0 | return false; |
216 | 0 | } |
217 | | // Instance-on-instance: recurse if both sides resolve to an |
218 | | // InstanceType. Otherwise sort-kind match (pre-Phase-3 behaviour). |
219 | 0 | if (Required.getDescType() == |
220 | 0 | AST::Component::ExternDesc::DescType::InstanceType && |
221 | 0 | Provided.IT != nullptr) { |
222 | 0 | const auto *RequiredIT = CompCtx.getInstanceType(Required.getTypeIndex()); |
223 | 0 | if (RequiredIT == nullptr) { |
224 | | // Required's idx lives in RequiredCtx's local type-decls. |
225 | 0 | RequiredIT = |
226 | 0 | resolveNestedInstanceType(RequiredCtx, Required.getTypeIndex()); |
227 | 0 | } |
228 | 0 | if (RequiredIT != nullptr) { |
229 | 0 | return isInstanceSubtype(*Provided.IT, *RequiredIT); |
230 | 0 | } |
231 | 0 | } |
232 | 0 | return true; |
233 | 0 | } |
234 | | |
235 | | std::optional<std::string> Validator::findMissingRequiredExport( |
236 | | uint32_t ProvidedInstIdx, |
237 | 0 | const AST::Component::InstanceType &RequiredIT) const noexcept { |
238 | 0 | const auto &Exports = CompCtx.getInstance(ProvidedInstIdx).Exports; |
239 | 0 | for (const auto &Decl : RequiredIT.getDecl()) { |
240 | 0 | if (!Decl.isExportDecl()) { |
241 | 0 | continue; |
242 | 0 | } |
243 | 0 | const auto &Exp = Decl.getExport(); |
244 | 0 | auto It = Exports.find(std::string(Exp.getName())); |
245 | 0 | if (It == Exports.end()) { |
246 | 0 | return std::string(Exp.getName()); |
247 | 0 | } |
248 | 0 | if (!exportSatisfies(RequiredIT, It->second, Exp.getExternDesc())) { |
249 | 0 | return std::string(Exp.getName()); |
250 | 0 | } |
251 | 0 | } |
252 | 0 | return std::nullopt; |
253 | 0 | } |
254 | | |
255 | | bool Validator::isInstanceSubtype( |
256 | | const AST::Component::InstanceType &S, |
257 | 0 | const AST::Component::InstanceType &T) const noexcept { |
258 | | // S subtype T iff every export declared by T is present in S with a |
259 | | // satisfying type. Build a quick (name → externdesc) lookup of S's |
260 | | // exports for the lookup. |
261 | 0 | std::unordered_map<std::string, const AST::Component::ExternDesc *> SExports; |
262 | 0 | for (const auto &Decl : S.getDecl()) { |
263 | 0 | if (Decl.isExportDecl()) { |
264 | 0 | const auto &E = Decl.getExport(); |
265 | 0 | SExports.emplace(std::string(E.getName()), &E.getExternDesc()); |
266 | 0 | } |
267 | 0 | } |
268 | 0 | for (const auto &Decl : T.getDecl()) { |
269 | 0 | if (!Decl.isExportDecl()) { |
270 | 0 | continue; |
271 | 0 | } |
272 | 0 | const auto &E = Decl.getExport(); |
273 | 0 | auto It = SExports.find(std::string(E.getName())); |
274 | 0 | if (It == SExports.end()) { |
275 | 0 | return false; |
276 | 0 | } |
277 | 0 | auto SKind = descTypeToSortType(It->second->getDescType()); |
278 | 0 | auto TKind = descTypeToSortType(E.getExternDesc().getDescType()); |
279 | 0 | if (SKind != TKind) { |
280 | 0 | return false; |
281 | 0 | } |
282 | | // Instance-on-instance: nested type indices on each side belong to |
283 | | // that side's own decls, so fall back via resolveNestedInstanceType. |
284 | 0 | if (E.getExternDesc().getDescType() == |
285 | 0 | AST::Component::ExternDesc::DescType::InstanceType) { |
286 | 0 | const auto *SubIT = CompCtx.getInstanceType(It->second->getTypeIndex()); |
287 | 0 | if (SubIT == nullptr) { |
288 | 0 | SubIT = resolveNestedInstanceType(S, It->second->getTypeIndex()); |
289 | 0 | } |
290 | 0 | const auto *ReqIT = |
291 | 0 | CompCtx.getInstanceType(E.getExternDesc().getTypeIndex()); |
292 | 0 | if (ReqIT == nullptr) { |
293 | 0 | ReqIT = resolveNestedInstanceType(T, E.getExternDesc().getTypeIndex()); |
294 | 0 | } |
295 | 0 | if (SubIT != nullptr && ReqIT != nullptr && |
296 | 0 | !isInstanceSubtype(*SubIT, *ReqIT)) { |
297 | 0 | return false; |
298 | 0 | } |
299 | 0 | } |
300 | 0 | } |
301 | 0 | return true; |
302 | 0 | } |
303 | | |
304 | | Expect<void> |
305 | 4.36k | Validator::validate(const AST::Component::Component &Comp) noexcept { |
306 | 4.36k | spdlog::warn("Component Model Validation is in active development."sv); |
307 | 4.36k | CompCtx.reset(); |
308 | 4.36k | return validateComponent(Comp).and_then([&]() { |
309 | 2.17k | const_cast<AST::Component::Component &>(Comp).setIsValidated(); |
310 | 2.17k | return Expect<void>{}; |
311 | 2.17k | }); |
312 | 4.36k | } |
313 | | |
314 | | Expect<void> |
315 | 9.08k | Validator::validateComponent(const AST::Component::Component &Comp) noexcept { |
316 | | // Validation enters a fresh component scope and walks sections in their |
317 | | // binary order. The per-sort index spaces are built incrementally as |
318 | | // definitions are validated, so sortidx references in later sections |
319 | | // only resolve against entries already introduced. Custom sections are |
320 | | // ignored. Nested components recurse through this same function. |
321 | 9.08k | auto ReportError = [](auto E) { |
322 | 2.20k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component)); |
323 | 2.20k | return E; |
324 | 2.20k | }; |
325 | | |
326 | 9.08k | CompCtx.enterComponent(&Comp); |
327 | 4.35M | for (const auto &Sec : Comp.getSections()) { |
328 | 4.35M | auto Func = [&](auto &&S) -> Expect<void> { |
329 | 4.35M | using T = std::decay_t<decltype(S)>; |
330 | 4.35M | if constexpr (std::is_same_v<T, AST::CustomSection>) { |
331 | | // Always pass validation. |
332 | 2.39M | } else { |
333 | 2.39M | EXPECTED_TRY(validate(S).map_error(ReportError)); |
334 | 2.39M | } |
335 | 2.38M | return {}; |
336 | 4.35M | }; component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::CustomSection const&>(WasmEdge::AST::CustomSection const&) const Line | Count | Source | 328 | 1.95M | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 1.95M | using T = std::decay_t<decltype(S)>; | 330 | 1.95M | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | | } else { | 333 | | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | | } | 335 | 1.95M | return {}; | 336 | 1.95M | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreModuleSection const&>(WasmEdge::AST::Component::CoreModuleSection const&) const Line | Count | Source | 328 | 1.38k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 1.38k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 1.38k | } else { | 333 | 1.38k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 1.38k | } | 335 | 1.36k | return {}; | 336 | 1.38k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreInstanceSection const&>(WasmEdge::AST::Component::CoreInstanceSection const&) const Line | Count | Source | 328 | 17.2k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 17.2k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 17.2k | } else { | 333 | 17.2k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 17.2k | } | 335 | 17.1k | return {}; | 336 | 17.2k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CoreTypeSection const&>(WasmEdge::AST::Component::CoreTypeSection const&) const Line | Count | Source | 328 | 375k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 375k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 375k | } else { | 333 | 375k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 375k | } | 335 | 375k | return {}; | 336 | 375k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ComponentSection const&>(WasmEdge::AST::Component::ComponentSection const&) const Line | Count | Source | 328 | 4.71k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 4.71k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 4.71k | } else { | 333 | 4.71k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 4.71k | } | 335 | 4.70k | return {}; | 336 | 4.71k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::InstanceSection const&>(WasmEdge::AST::Component::InstanceSection const&) const Line | Count | Source | 328 | 156k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 156k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 156k | } else { | 333 | 156k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 156k | } | 335 | 155k | return {}; | 336 | 156k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::AliasSection const&>(WasmEdge::AST::Component::AliasSection const&) const Line | Count | Source | 328 | 7.64k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 7.64k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 7.64k | } else { | 333 | 7.64k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 7.64k | } | 335 | 7.45k | return {}; | 336 | 7.64k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::TypeSection const&>(WasmEdge::AST::Component::TypeSection const&) const Line | Count | Source | 328 | 1.77M | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 1.77M | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 1.77M | } else { | 333 | 1.77M | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 1.77M | } | 335 | 1.77M | return {}; | 336 | 1.77M | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::CanonSection const&>(WasmEdge::AST::Component::CanonSection const&) const Line | Count | Source | 328 | 30.5k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 30.5k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 30.5k | } else { | 333 | 30.5k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 30.5k | } | 335 | 30.2k | return {}; | 336 | 30.5k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::StartSection const&>(WasmEdge::AST::Component::StartSection const&) const Line | Count | Source | 328 | 1.12k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 1.12k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 1.12k | } else { | 333 | 1.12k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 1.12k | } | 335 | 1.06k | return {}; | 336 | 1.12k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ImportSection const&>(WasmEdge::AST::Component::ImportSection const&) const Line | Count | Source | 328 | 19.7k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 19.7k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 19.7k | } else { | 333 | 19.7k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 19.7k | } | 335 | 18.8k | return {}; | 336 | 19.7k | }; |
component_validator.cpp:cxx20::expected<void, WasmEdge::ErrCode> WasmEdge::Validator::Validator::validateComponent(WasmEdge::AST::Component::Component const&)::$_0::operator()<WasmEdge::AST::Component::ExportSection const&>(WasmEdge::AST::Component::ExportSection const&) const Line | Count | Source | 328 | 2.40k | auto Func = [&](auto &&S) -> Expect<void> { | 329 | 2.40k | using T = std::decay_t<decltype(S)>; | 330 | | if constexpr (std::is_same_v<T, AST::CustomSection>) { | 331 | | // Always pass validation. | 332 | 2.40k | } else { | 333 | 2.40k | EXPECTED_TRY(validate(S).map_error(ReportError)); | 334 | 2.40k | } | 335 | 2.27k | return {}; | 336 | 2.40k | }; |
|
337 | 4.35M | EXPECTED_TRY(std::visit(Func, Sec)); |
338 | 4.35M | } |
339 | 6.87k | CompCtx.exitComponent(); |
340 | 6.87k | return {}; |
341 | 9.08k | } |
342 | | |
343 | | Expect<void> |
344 | 1.38k | Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept { |
345 | 1.38k | EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) { |
346 | 1.36k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod)); |
347 | 1.36k | return E; |
348 | 1.36k | })); |
349 | 1.36k | const_cast<AST::Module &>(ModSec.getContent()).setIsValidated(); |
350 | 1.36k | CompCtx.addCoreModule(ModSec.getContent()); |
351 | 1.36k | return {}; |
352 | 1.38k | } |
353 | | |
354 | | Expect<void> Validator::validate( |
355 | 17.2k | const AST::Component::CoreInstanceSection &InstSec) noexcept { |
356 | 17.2k | for (const auto &Inst : InstSec.getContent()) { |
357 | 16.7k | EXPECTED_TRY(validate(Inst).map_error([](auto E) { |
358 | 16.7k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance)); |
359 | 16.7k | return E; |
360 | 16.7k | })); |
361 | 16.7k | } |
362 | 17.1k | return {}; |
363 | 17.2k | } |
364 | | |
365 | | Expect<void> |
366 | 375k | Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept { |
367 | 375k | for (const auto &Type : TypeSec.getContent()) { |
368 | 296k | EXPECTED_TRY(validate(Type).map_error([](auto E) { |
369 | 296k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType)); |
370 | 296k | return E; |
371 | 296k | })); |
372 | 296k | } |
373 | 375k | return {}; |
374 | 375k | } |
375 | | |
376 | | Expect<void> |
377 | 4.71k | Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept { |
378 | 4.71k | EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) { |
379 | 4.70k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component)); |
380 | 4.70k | return E; |
381 | 4.70k | })); |
382 | 4.70k | CompCtx.addComponent(CompSec.getContent()); |
383 | 4.70k | return {}; |
384 | 4.71k | } |
385 | | |
386 | | Expect<void> |
387 | 156k | Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept { |
388 | 156k | for (const auto &Inst : InstSec.getContent()) { |
389 | 65.3k | EXPECTED_TRY(validate(Inst).map_error([](auto E) { |
390 | 65.3k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance)); |
391 | 65.3k | return E; |
392 | 65.3k | })); |
393 | 65.3k | } |
394 | 155k | return {}; |
395 | 156k | } |
396 | | |
397 | | Expect<void> |
398 | 7.64k | Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept { |
399 | 7.64k | for (const auto &Alias : AliasSec.getContent()) { |
400 | 7.15k | EXPECTED_TRY(validate(Alias).map_error([](auto E) { |
401 | 6.96k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias)); |
402 | 6.96k | return E; |
403 | 6.96k | })); |
404 | 6.96k | const auto &Sort = Alias.getSort(); |
405 | 6.96k | const bool IsOuter = |
406 | 6.96k | Alias.getTargetType() == AST::Component::Alias::TargetType::Outer; |
407 | 6.96k | if (Sort.isCore()) { |
408 | 584 | uint32_t NewCoreIdx = |
409 | 584 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
410 | | // Carry the outer-aliased module's slot so the alias stays enumerable |
411 | | // when instantiated. |
412 | 584 | if (IsOuter && Sort.getCoreSortType() == |
413 | 275 | AST::Component::Sort::CoreSortType::Module) { |
414 | 0 | CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first, |
415 | 0 | Alias.getOuter().second); |
416 | 0 | } |
417 | 6.38k | } else { |
418 | 6.38k | uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType()); |
419 | | // Component analogue of the outer core-module carry above. |
420 | 6.38k | if (IsOuter && |
421 | 5.18k | Sort.getSortType() == AST::Component::Sort::SortType::Component) { |
422 | 507 | CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first, |
423 | 507 | Alias.getOuter().second); |
424 | 507 | } |
425 | | // Outer-aliasing a resource type keeps the resource's identity in this |
426 | | // scope so later own/borrow and (eq i) checks treat the slot correctly. |
427 | 6.38k | if (IsOuter && |
428 | 5.18k | Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
429 | 4.67k | CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first, |
430 | 4.67k | Alias.getOuter().second); |
431 | 4.67k | } |
432 | | // If the alias creates a new instance entry out of an `alias export` |
433 | | // on another instance, propagate the source instance's export table |
434 | | // into the new slot so a subsequent `alias export` on this slot can |
435 | | // resolve nested exports. |
436 | 6.38k | if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) { |
437 | 1.19k | const auto SrcInstIdx = Alias.getExport().first; |
438 | 1.19k | const auto &SrcName = Alias.getExport().second; |
439 | 1.19k | const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports; |
440 | 1.19k | auto It = SrcExports.find(std::string(SrcName)); |
441 | 1.19k | if (It != SrcExports.end()) { |
442 | 1.19k | if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
443 | 1.19k | if (It->second.IT != nullptr) { |
444 | 0 | populateInstanceFromType(NewIdx, *It->second.IT); |
445 | 1.19k | } else if (It->second.NestedInstIdx.has_value()) { |
446 | 1.19k | const auto &NestedExports = |
447 | 1.19k | CompCtx.getInstance(*It->second.NestedInstIdx).Exports; |
448 | 1.19k | for (const auto &[Name, IE] : NestedExports) { |
449 | 1.19k | CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT, |
450 | 1.19k | IE.NestedInstIdx, IE.ResourceId); |
451 | 1.19k | } |
452 | 1.19k | } |
453 | 1.19k | } else if (Sort.getSortType() == |
454 | 0 | AST::Component::Sort::SortType::Type && |
455 | 0 | It->second.ResourceId.has_value()) { |
456 | | // Alias-export of a resource type — same identity. |
457 | 0 | CompCtx.addResource(NewIdx, {*It->second.ResourceId, |
458 | 0 | /*LocallyDefined=*/false}); |
459 | 0 | } |
460 | 1.19k | } |
461 | 1.19k | } |
462 | 6.38k | } |
463 | 6.96k | } |
464 | 7.45k | return {}; |
465 | 7.64k | } |
466 | | |
467 | | Expect<void> |
468 | 1.77M | Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept { |
469 | 1.77M | for (const auto &Type : TypeSec.getContent()) { |
470 | 1.77M | EXPECTED_TRY(validate(Type).map_error([](auto E) { |
471 | 1.77M | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type)); |
472 | 1.77M | return E; |
473 | 1.77M | })); |
474 | 1.77M | } |
475 | 1.77M | return {}; |
476 | 1.77M | } |
477 | | |
478 | | Expect<void> |
479 | 30.5k | Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept { |
480 | 30.5k | for (const auto &C : CanonSec.getContent()) { |
481 | 9.08k | EXPECTED_TRY(validate(C).map_error([](auto E) { |
482 | 9.08k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon)); |
483 | 9.08k | return E; |
484 | 9.08k | })); |
485 | 9.08k | } |
486 | 30.2k | return {}; |
487 | 30.5k | } |
488 | | |
489 | | Expect<void> |
490 | 1.12k | Validator::validate(const AST::Component::StartSection &StartSec) noexcept { |
491 | | // Validation steps: |
492 | | // 1. `f` is in bounds of the component func index space. |
493 | | // 2. `f`'s functype param arity equals |arg*| and result arity equals |
494 | | // `r`. Per-argument value-type subtype is checked once subtype |
495 | | // machinery exists (Phase 3). |
496 | | // 3. Each argument index is in bounds of the value index space. |
497 | | // Per-value linearity (consume-once) is the responsibility of |
498 | | // GAP-EX-4: the per-value `consumed` flag and the end-of-component |
499 | | // "all consumed" pass. They are not yet wired here. |
500 | | // 4. The function's result types are appended to the value index space |
501 | | // as fresh values, so later definitions can reference them. |
502 | 1.12k | const auto &Start = StartSec.getContent(); |
503 | | |
504 | | // 1. Function index bounds. |
505 | 1.12k | const uint32_t FuncIdx = Start.getFunctionIndex(); |
506 | 1.12k | const uint32_t FuncSpaceSize = |
507 | 1.12k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func); |
508 | 1.12k | if (FuncIdx >= FuncSpaceSize) { |
509 | 26 | spdlog::error(ErrCode::Value::InvalidIndex); |
510 | 26 | spdlog::error( |
511 | 26 | " Start: function index {} exceeds func index space size {}"sv, |
512 | 26 | FuncIdx, FuncSpaceSize); |
513 | 26 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
514 | 26 | return Unexpect(ErrCode::Value::InvalidIndex); |
515 | 26 | } |
516 | | |
517 | | // 2. Look up the func type. If null (e.g. imported component func without |
518 | | // populated FuncType yet), skip arity check — the bound check above is |
519 | | // enough for the moment. |
520 | 1.09k | const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx); |
521 | 1.09k | if (FT != nullptr) { |
522 | 519 | const auto Args = Start.getArguments(); |
523 | 519 | const auto &ParamList = FT->getParamList(); |
524 | 519 | if (Args.size() != ParamList.size()) { |
525 | 1 | spdlog::error(ErrCode::Value::InvalidIndex); |
526 | 1 | spdlog::error( |
527 | 1 | " Start: argument count {} does not match func {} param arity {}"sv, |
528 | 1 | Args.size(), FuncIdx, ParamList.size()); |
529 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
530 | 1 | return Unexpect(ErrCode::Value::InvalidIndex); |
531 | 1 | } |
532 | 518 | const uint32_t ResultArity = |
533 | 518 | static_cast<uint32_t>(FT->getResultList().size()); |
534 | 518 | if (Start.getResult() != ResultArity) { |
535 | 4 | spdlog::error(ErrCode::Value::InvalidIndex); |
536 | 4 | spdlog::error( |
537 | 4 | " Start: declared result count {} does not match func {} result arity {}"sv, |
538 | 4 | Start.getResult(), FuncIdx, ResultArity); |
539 | 4 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
540 | 4 | return Unexpect(ErrCode::Value::InvalidIndex); |
541 | 4 | } |
542 | 518 | } |
543 | | |
544 | | // 3. Argument indices must be in value index space bounds. Per-arg |
545 | | // consume-once tracking is deferred (GAP-EX-4). |
546 | 1.09k | const uint32_t ValueSpaceSize = |
547 | 1.09k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value); |
548 | 1.09k | for (const uint32_t ArgIdx : Start.getArguments()) { |
549 | 774 | if (ArgIdx >= ValueSpaceSize) { |
550 | 28 | spdlog::error(ErrCode::Value::InvalidIndex); |
551 | 28 | spdlog::error( |
552 | 28 | " Start: argument value index {} exceeds value index space size {}"sv, |
553 | 28 | ArgIdx, ValueSpaceSize); |
554 | 28 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
555 | 28 | return Unexpect(ErrCode::Value::InvalidIndex); |
556 | 28 | } |
557 | 774 | } |
558 | | |
559 | | // 4. Append result values to the value index space. |
560 | 1.43G | for (uint32_t I = 0; I < Start.getResult(); ++I) { |
561 | 1.43G | CompCtx.addValue(); |
562 | 1.43G | } |
563 | 1.06k | return {}; |
564 | 1.09k | } |
565 | | |
566 | | Expect<void> |
567 | 19.7k | Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept { |
568 | 19.7k | for (const auto &Imp : ImpSec.getContent()) { |
569 | 6.95k | EXPECTED_TRY(validate(Imp).map_error([](auto E) { |
570 | 6.95k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import)); |
571 | 6.95k | return E; |
572 | 6.95k | })); |
573 | 6.95k | } |
574 | 18.8k | return {}; |
575 | 19.7k | } |
576 | | |
577 | | Expect<void> |
578 | 2.40k | Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept { |
579 | 2.40k | for (const auto &Exp : ExpSec.getContent()) { |
580 | 894 | EXPECTED_TRY(validate(Exp).map_error([](auto E) { |
581 | 894 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export)); |
582 | 894 | return E; |
583 | 894 | })); |
584 | 894 | } |
585 | 2.27k | return {}; |
586 | 2.40k | } |
587 | | |
588 | | Expect<void> |
589 | 16.7k | Validator::validate(const AST::Component::CoreInstance &Inst) noexcept { |
590 | 16.7k | if (Inst.isInstantiateModule()) { |
591 | | // Instantiate module case. |
592 | | |
593 | | // Check the module index bound first. |
594 | 259 | const uint32_t ModIdx = Inst.getModuleIndex(); |
595 | 259 | if (ModIdx >= CompCtx.getCoreSortIndexSize( |
596 | 259 | AST::Component::Sort::CoreSortType::Module)) { |
597 | 38 | spdlog::error(ErrCode::Value::InvalidIndex); |
598 | 38 | spdlog::error( |
599 | 38 | " CoreInstance: Module index {} exceeds available core modules {}"sv, |
600 | 38 | ModIdx, |
601 | 38 | CompCtx.getCoreSortIndexSize( |
602 | 38 | AST::Component::Sort::CoreSortType::Module)); |
603 | 38 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
604 | 38 | return Unexpect(ErrCode::Value::InvalidIndex); |
605 | 38 | } |
606 | | // Reject duplicate argument names on an instantiate expression. The |
607 | | // spec requires argument names to be strongly-unique per instantiation. |
608 | 221 | { |
609 | 221 | std::unordered_set<std::string_view> SeenArgs; |
610 | 221 | for (const auto &Arg : Inst.getInstantiateArgs()) { |
611 | 0 | if (!SeenArgs.insert(Arg.getName()).second) { |
612 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
613 | 0 | spdlog::error(" CoreInstance: Duplicate argument name '{}'"sv, |
614 | 0 | Arg.getName()); |
615 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
616 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
617 | 0 | } |
618 | 0 | } |
619 | 221 | } |
620 | | |
621 | | // Imports + exports come from the raw Module (inline) or the |
622 | | // CoreModuleType (imported / aliased) — GAP-CI-1. |
623 | 221 | const auto &CoreModSlot = CompCtx.getCoreModule(ModIdx); |
624 | 221 | const auto *Mod = CoreModSlot.Body; |
625 | 221 | const auto *ModTy = CoreModSlot.Type; |
626 | | |
627 | | // Required arg module-names (one per distinct CoreImportDecl module). |
628 | 221 | std::vector<std::string_view> RequiredArgNames; |
629 | 221 | if (Mod != nullptr) { |
630 | 221 | for (const auto &Import : Mod->getImportSection().getContent()) { |
631 | 5 | RequiredArgNames.push_back(Import.getModuleName()); |
632 | 5 | } |
633 | 221 | } else if (ModTy != nullptr && ModTy->isModuleType()) { |
634 | 0 | for (const auto &Decl : ModTy->getModuleType()) { |
635 | 0 | if (Decl.isImport()) { |
636 | 0 | RequiredArgNames.push_back(Decl.getImport().getModuleName()); |
637 | 0 | } |
638 | 0 | } |
639 | 0 | } |
640 | | |
641 | 221 | auto Args = Inst.getInstantiateArgs(); |
642 | 221 | for (const auto ImportName : RequiredArgNames) { |
643 | 3 | const auto ArgIt = |
644 | 3 | std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) { |
645 | 0 | return Arg.getName() == ImportName; |
646 | 0 | }); |
647 | 3 | if (ArgIt == Args.end()) { |
648 | 3 | spdlog::error(ErrCode::Value::MissingArgument); |
649 | 3 | spdlog::error( |
650 | 3 | " CoreInstance: Module index {} missing argument for import '{}'"sv, |
651 | 3 | Inst.getModuleIndex(), ImportName); |
652 | 3 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
653 | 3 | return Unexpect(ErrCode::Value::MissingArgument); |
654 | 3 | } |
655 | 3 | } |
656 | | |
657 | | // Allocate the core:instance and bind exports to it. |
658 | 218 | uint32_t InstanceIdx = CompCtx.addCoreInstance(); |
659 | 218 | if (Mod != nullptr) { |
660 | 218 | for (const auto &ExportDesc : Mod->getExportSection().getContent()) { |
661 | 0 | CompCtx.addCoreInstanceExport(InstanceIdx, ExportDesc.getExternalName(), |
662 | 0 | ExportDesc.getExternalType()); |
663 | 0 | } |
664 | 218 | } else if (ModTy != nullptr && ModTy->isModuleType()) { |
665 | 0 | for (const auto &Decl : ModTy->getModuleType()) { |
666 | 0 | if (!Decl.isExport()) { |
667 | 0 | continue; |
668 | 0 | } |
669 | 0 | const auto &Exp = Decl.getExport(); |
670 | 0 | const auto &ImpDesc = Exp.getImportDesc(); |
671 | 0 | ExternalType ET; |
672 | 0 | if (ImpDesc.isFunc()) { |
673 | 0 | ET = ExternalType::Function; |
674 | 0 | } else if (ImpDesc.isTable()) { |
675 | 0 | ET = ExternalType::Table; |
676 | 0 | } else if (ImpDesc.isMemory()) { |
677 | 0 | ET = ExternalType::Memory; |
678 | 0 | } else if (ImpDesc.isGlobal()) { |
679 | 0 | ET = ExternalType::Global; |
680 | 0 | } else if (ImpDesc.isTag()) { |
681 | 0 | ET = ExternalType::Tag; |
682 | 0 | } else { |
683 | 0 | continue; |
684 | 0 | } |
685 | 0 | CompCtx.addCoreInstanceExport(InstanceIdx, Exp.getName(), ET); |
686 | 0 | } |
687 | 0 | } |
688 | 16.4k | } else if (Inst.isInlineExport()) { |
689 | | // Inline export case. |
690 | | // Allocate the core instance first, then register each inline export. |
691 | 16.4k | uint32_t InstanceIdx = CompCtx.addCoreInstance(); |
692 | | |
693 | | // Check the core:sort index bound and register the inline exports. |
694 | | // Inline-export names on a core instance must be strongly-unique. |
695 | 16.4k | std::unordered_set<std::string_view> SeenExports; |
696 | 16.4k | for (const auto &Export : Inst.getInlineExports()) { |
697 | 1.92k | if (!SeenExports.insert(Export.getName()).second) { |
698 | 1 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
699 | 1 | spdlog::error(" CoreInstance: Duplicate inline-export name '{}'"sv, |
700 | 1 | Export.getName()); |
701 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
702 | 1 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
703 | 1 | } |
704 | 1.91k | const auto &Sort = Export.getSortIdx().getSort(); |
705 | 1.91k | uint32_t Idx = Export.getSortIdx().getIdx(); |
706 | 1.91k | assuming(Sort.isCore()); |
707 | 1.91k | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
708 | | // The error message differs of the tag core sort. |
709 | 51 | ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex; |
710 | 51 | if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) { |
711 | 2 | ErrValue = ErrCode::Value::UnknownCoreTag; |
712 | 2 | } |
713 | 51 | spdlog::error(ErrValue); |
714 | 51 | spdlog::error( |
715 | 51 | " CoreInstance: Inline export '{}' refers to invalid index {}"sv, |
716 | 51 | Export.getName(), Idx); |
717 | 51 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
718 | 51 | return Unexpect(ErrValue); |
719 | 51 | } |
720 | | // Map CoreSortType to ExternalType for the instance export map. |
721 | 1.86k | ExternalType ET; |
722 | 1.86k | switch (Sort.getCoreSortType()) { |
723 | 1.86k | case AST::Component::Sort::CoreSortType::Func: |
724 | 1.86k | ET = ExternalType::Function; |
725 | 1.86k | break; |
726 | 0 | case AST::Component::Sort::CoreSortType::Table: |
727 | 0 | ET = ExternalType::Table; |
728 | 0 | break; |
729 | 0 | case AST::Component::Sort::CoreSortType::Memory: |
730 | 0 | ET = ExternalType::Memory; |
731 | 0 | break; |
732 | 0 | case AST::Component::Sort::CoreSortType::Global: |
733 | 0 | ET = ExternalType::Global; |
734 | 0 | break; |
735 | 0 | case AST::Component::Sort::CoreSortType::Tag: |
736 | 0 | ET = ExternalType::Tag; |
737 | 0 | break; |
738 | 3 | default: |
739 | 3 | spdlog::error(ErrCode::Value::InvalidIndex); |
740 | 3 | spdlog::error( |
741 | 3 | " CoreInstance: Inline export '{}' has unsupported core sort"sv, |
742 | 3 | Export.getName()); |
743 | 3 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
744 | 3 | return Unexpect(ErrCode::Value::InvalidIndex); |
745 | 1.86k | } |
746 | 1.86k | CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET); |
747 | 1.86k | } |
748 | 16.4k | } else { |
749 | 0 | assumingUnreachable(); |
750 | 0 | } |
751 | 16.6k | return {}; |
752 | 16.7k | } |
753 | | |
754 | | Expect<void> |
755 | 65.3k | Validator::validate(const AST::Component::Instance &Inst) noexcept { |
756 | 65.3k | if (Inst.isInstantiateModule()) { |
757 | | // Instantiate module case. |
758 | | |
759 | | // Check the component index bound first. |
760 | 4.08k | const uint32_t CompIdx = Inst.getComponentIndex(); |
761 | 4.08k | if (CompIdx >= |
762 | 4.08k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) { |
763 | 104 | spdlog::error(ErrCode::Value::InvalidIndex); |
764 | 104 | spdlog::error( |
765 | 104 | " Instance: Component index {} exceeds available components {}"sv, |
766 | 104 | CompIdx, |
767 | 104 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)); |
768 | 104 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
769 | 104 | return Unexpect(ErrCode::Value::InvalidIndex); |
770 | 104 | } |
771 | | // Reject duplicate argument names on an instantiate expression. The |
772 | | // spec requires argument names to be strongly-unique per instantiation. |
773 | 3.97k | { |
774 | 3.97k | std::unordered_set<std::string_view> SeenArgs; |
775 | 4.24k | for (const auto &Arg : Inst.getInstantiateArgs()) { |
776 | 4.24k | if (!SeenArgs.insert(Arg.getName()).second) { |
777 | 20 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
778 | 20 | spdlog::error(" Instance: Duplicate argument name '{}'"sv, |
779 | 20 | Arg.getName()); |
780 | 20 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
781 | 20 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
782 | 20 | } |
783 | 4.24k | } |
784 | 3.97k | } |
785 | | |
786 | | // Source: raw Component (inline) or ComponentType (imported / aliased). |
787 | 3.95k | const auto &CompSlot = CompCtx.getComponent(CompIdx); |
788 | 3.95k | const auto *Comp = CompSlot.Body; |
789 | 3.95k | const auto *CompTy = CompSlot.Type; |
790 | | |
791 | | // Verify each component import is satisfied by some instantiate arg. |
792 | 3.95k | auto Args = Inst.getInstantiateArgs(); |
793 | 3.95k | auto checkImport = |
794 | 3.95k | [&](std::string_view ImportName, |
795 | 3.95k | const AST::Component::ExternDesc &ImportDesc) -> Expect<void> { |
796 | 32 | const auto ArgIt = |
797 | 107 | std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) { |
798 | 107 | return Arg.getName() == ImportName; |
799 | 107 | }); |
800 | 32 | if (ArgIt == Args.end()) { |
801 | 10 | spdlog::error(ErrCode::Value::MissingArgument); |
802 | 10 | spdlog::error( |
803 | 10 | " Instance: Component index {} missing argument for import '{}'"sv, |
804 | 10 | Inst.getComponentIndex(), ImportName); |
805 | 10 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
806 | 10 | return Unexpect(ErrCode::Value::MissingArgument); |
807 | 10 | } |
808 | 22 | const auto &Sort = ArgIt->getIndex().getSort(); |
809 | 22 | const uint32_t Idx = ArgIt->getIndex().getIdx(); |
810 | | // Only `core module` is admissible as a core-side import externdesc. |
811 | 22 | if (Sort.isCore() && Sort.getCoreSortType() != |
812 | 4 | AST::Component::Sort::CoreSortType::Module) { |
813 | 3 | spdlog::error(ErrCode::Value::ArgTypeMismatch); |
814 | 3 | spdlog::error(" Instance: Argument '{}' uses a core sort other than " |
815 | 3 | "`core module`, which no import externdesc can accept"sv, |
816 | 3 | ImportName); |
817 | 3 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
818 | 3 | return Unexpect(ErrCode::Value::ArgTypeMismatch); |
819 | 3 | } |
820 | 19 | if (!sortMatchesDescType(Sort, ImportDesc.getDescType())) { |
821 | 4 | spdlog::error(ErrCode::Value::ArgTypeMismatch); |
822 | 4 | spdlog::error(" Instance: Argument '{}' sort mismatch for import"sv, |
823 | 4 | ImportName); |
824 | 4 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
825 | 4 | return Unexpect(ErrCode::Value::ArgTypeMismatch); |
826 | 4 | } |
827 | 15 | if (Sort.isCore()) { |
828 | 0 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
829 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
830 | 0 | spdlog::error( |
831 | 0 | " Instance: Argument '{}' refers to invalid index {}"sv, |
832 | 0 | ImportName, Idx); |
833 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
834 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
835 | 0 | } |
836 | 0 | return {}; |
837 | 0 | } |
838 | 15 | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
839 | 7 | spdlog::error(ErrCode::Value::InvalidIndex); |
840 | 7 | spdlog::error( |
841 | 7 | " Instance: Argument '{}' refers to invalid index {}"sv, |
842 | 7 | ImportName, Idx); |
843 | 7 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
844 | 7 | return Unexpect(ErrCode::Value::InvalidIndex); |
845 | 7 | } |
846 | | // Partial subtype: for instance-typed imports, verify the provided |
847 | | // instance has every required export (raw-Component path only; |
848 | | // ComponentType path needs GAP-DECL-ED). |
849 | 8 | if (Comp != nullptr && |
850 | 8 | ImportDesc.getDescType() == |
851 | 8 | AST::Component::ExternDesc::DescType::InstanceType && |
852 | 0 | Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
853 | 0 | const auto *RequiredIT = |
854 | 0 | resolveChildInstanceType(*Comp, ImportDesc.getTypeIndex()); |
855 | 0 | if (RequiredIT != nullptr) { |
856 | 0 | if (auto Missing = findMissingRequiredExport(Idx, *RequiredIT)) { |
857 | 0 | spdlog::error(ErrCode::Value::InstanceMissingExpectedExport); |
858 | 0 | spdlog::error(" Instance: Argument '{}' missing required export " |
859 | 0 | "'{}' for import"sv, |
860 | 0 | ImportName, *Missing); |
861 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
862 | 0 | return Unexpect(ErrCode::Value::InstanceMissingExpectedExport); |
863 | 0 | } |
864 | 0 | } |
865 | 0 | } |
866 | 8 | return {}; |
867 | 8 | }; |
868 | 3.95k | if (Comp != nullptr) { |
869 | 7.84k | for (const auto &Sec : Comp->getSections()) { |
870 | 7.84k | if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) { |
871 | 596 | for (const auto &Imp : IS->getContent()) { |
872 | 31 | EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc())); |
873 | 31 | } |
874 | 596 | } |
875 | 7.84k | } |
876 | 2.28k | } else if (CompTy != nullptr) { |
877 | 593 | for (const auto &CD : CompTy->getDecl()) { |
878 | 230 | if (CD.isImportDecl()) { |
879 | 1 | const auto &ID = CD.getImport(); |
880 | 1 | EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc())); |
881 | 1 | } |
882 | 230 | } |
883 | 593 | } |
884 | | |
885 | | // Allocate the slot + populate exports so alias-export can resolve. |
886 | | // Raw-Component path resolves the IT for instance-typed exports; |
887 | | // ComponentType path registers sort-kind only (GAP-DECL-ED). |
888 | | // TODO (GAP-I-3): fresh ResourceIds + per-export resource remapping. |
889 | 3.93k | uint32_t InstanceIdx = CompCtx.addInstance(); |
890 | 3.93k | if (Comp != nullptr) { |
891 | 7.73k | for (const auto &Sec : Comp->getSections()) { |
892 | 7.73k | const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec); |
893 | 7.73k | if (ES == nullptr) { |
894 | 6.17k | continue; |
895 | 6.17k | } |
896 | 1.55k | for (const auto &Exp : ES->getContent()) { |
897 | 0 | const auto &ExpSort = Exp.getSortIndex().getSort(); |
898 | 0 | if (ExpSort.isCore()) { |
899 | 0 | continue; |
900 | 0 | } |
901 | 0 | const AST::Component::InstanceType *IT = nullptr; |
902 | 0 | if (ExpSort.getSortType() == |
903 | 0 | AST::Component::Sort::SortType::Instance && |
904 | 0 | Exp.getDesc().has_value() && |
905 | 0 | Exp.getDesc()->getDescType() == |
906 | 0 | AST::Component::ExternDesc::DescType::InstanceType) { |
907 | 0 | IT = resolveChildInstanceType(*Comp, Exp.getDesc()->getTypeIndex()); |
908 | 0 | } |
909 | 0 | CompCtx.addInstanceExport(InstanceIdx, Exp.getName(), |
910 | 0 | ExpSort.getSortType(), IT); |
911 | 0 | } |
912 | 1.55k | } |
913 | 2.28k | } else if (CompTy != nullptr) { |
914 | 592 | for (const auto &CD : CompTy->getDecl()) { |
915 | 229 | if (!CD.isInstanceDecl()) { |
916 | 0 | continue; |
917 | 0 | } |
918 | 229 | const auto &ID = CD.getInstance(); |
919 | 229 | if (!ID.isExportDecl()) { |
920 | 0 | continue; |
921 | 0 | } |
922 | 229 | const auto &ED = ID.getExport(); |
923 | 229 | const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType()); |
924 | 229 | if (!OptST.has_value()) { |
925 | 0 | continue; // `(core module)` export — not a component-side entry. |
926 | 0 | } |
927 | 229 | CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST); |
928 | 229 | } |
929 | 592 | } |
930 | 61.2k | } else if (Inst.isInlineExport()) { |
931 | | // Allocate the instance first so exports can be registered on it. |
932 | 61.2k | uint32_t InstanceIdx = CompCtx.addInstance(); |
933 | | |
934 | | // Check the sort index bound of the inline exports, then record each |
935 | | // export on the new instance with a NestedInstIdx fallback so alias |
936 | | // chains through inline-export instances can follow the source. |
937 | | // Inline-export names on a component instance must be strongly-unique. |
938 | 61.2k | std::unordered_set<std::string_view> SeenExports; |
939 | 61.2k | for (const auto &Export : Inst.getInlineExports()) { |
940 | 2.38k | if (!SeenExports.insert(Export.getName()).second) { |
941 | 1 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
942 | 1 | spdlog::error(" Instance: Duplicate inline-export name '{}'"sv, |
943 | 1 | Export.getName()); |
944 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
945 | 1 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
946 | 1 | } |
947 | 2.38k | const auto &Sort = Export.getSortIdx().getSort(); |
948 | 2.38k | uint32_t Idx = Export.getSortIdx().getIdx(); |
949 | 2.38k | if (Sort.isCore()) { |
950 | 215 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
951 | 23 | spdlog::error(ErrCode::Value::InvalidIndex); |
952 | 23 | spdlog::error( |
953 | 23 | " Instance: Inline export '{}' refers to invalid index {}"sv, |
954 | 23 | Export.getName(), Idx); |
955 | 23 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
956 | 23 | return Unexpect(ErrCode::Value::InvalidIndex); |
957 | 23 | } |
958 | 192 | continue; |
959 | 215 | } |
960 | 2.16k | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
961 | 38 | spdlog::error(ErrCode::Value::InvalidIndex); |
962 | 38 | spdlog::error( |
963 | 38 | " Instance: Inline export '{}' refers to invalid index {}"sv, |
964 | 38 | Export.getName(), Idx); |
965 | 38 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
966 | 38 | return Unexpect(ErrCode::Value::InvalidIndex); |
967 | 38 | } |
968 | 2.12k | if (Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
969 | 1.02k | auto SubstitutedIdx = |
970 | 1.02k | CompCtx.getSubstitutedType(std::string(Export.getName())); |
971 | 1.02k | if (SubstitutedIdx.has_value() && Idx != SubstitutedIdx.value()) { |
972 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
973 | 0 | spdlog::error( |
974 | 0 | " Instance: Inline export '{}' type index {} does not match substituted type index {}"sv, |
975 | 0 | Export.getName(), Idx, *SubstitutedIdx); |
976 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
977 | 0 | } |
978 | 1.02k | } |
979 | 2.12k | std::optional<uint32_t> NestedIdx; |
980 | 2.12k | const AST::Component::InstanceType *PropagatedIT = nullptr; |
981 | 2.12k | if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
982 | 973 | NestedIdx = Idx; |
983 | | // GAP-I-5b: forward source's InstanceType so later ascription / |
984 | | // subtype checks have a concrete type. Populated only when the |
985 | | // source slot was bound via ExternDesc::InstanceType; inline- |
986 | | // export / instantiate sources read nullptr (Phase-3 follow-up). |
987 | 973 | PropagatedIT = CompCtx.getInstance(Idx).Type; |
988 | 973 | } |
989 | 2.12k | CompCtx.addInstanceExport(InstanceIdx, Export.getName(), |
990 | 2.12k | Sort.getSortType(), PropagatedIT, NestedIdx); |
991 | 2.12k | } |
992 | 61.2k | } else { |
993 | 0 | assumingUnreachable(); |
994 | 0 | } |
995 | 65.1k | return {}; |
996 | 65.3k | } |
997 | | |
998 | 199 | Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept { |
999 | | // CoreAlias is always an outer alias. |
1000 | 199 | uint32_t Ct = A.getComponentJump(); |
1001 | 199 | uint32_t Idx = A.getIndex(); |
1002 | | |
1003 | 199 | uint32_t OutLinkCompCnt = 0; |
1004 | 199 | const auto *TargetCtx = &CompCtx.getCurrentContext(); |
1005 | 239 | while (Ct > OutLinkCompCnt && TargetCtx != nullptr) { |
1006 | 40 | TargetCtx = TargetCtx->Parent; |
1007 | 40 | OutLinkCompCnt++; |
1008 | 40 | } |
1009 | 199 | if (TargetCtx == nullptr) { |
1010 | 10 | spdlog::error(ErrCode::Value::InvalidIndex); |
1011 | | // The final hop is the one that ran off the top of the scope chain, so the |
1012 | | // number of actually-enclosing components is one less than the hop count. |
1013 | 10 | spdlog::error( |
1014 | 10 | " CoreAlias: outer count {} exceeds enclosing component count {}"sv, |
1015 | 10 | Ct, OutLinkCompCnt - 1); |
1016 | 10 | return Unexpect(ErrCode::Value::InvalidIndex); |
1017 | 10 | } |
1018 | | |
1019 | 189 | const auto &Sort = A.getSort(); |
1020 | 189 | if (Sort.isCore()) { |
1021 | 189 | if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) { |
1022 | 23 | spdlog::error(ErrCode::Value::InvalidIndex); |
1023 | 23 | spdlog::error(" CoreAlias: outer index {} out of bounds"sv, Idx); |
1024 | 23 | return Unexpect(ErrCode::Value::InvalidIndex); |
1025 | 23 | } |
1026 | 166 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
1027 | 166 | } |
1028 | 166 | return {}; |
1029 | 189 | } |
1030 | | |
1031 | 7.33k | Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept { |
1032 | 7.33k | const auto &Sort = Alias.getSort(); |
1033 | 7.33k | switch (Alias.getTargetType()) { |
1034 | 1.25k | case AST::Component::Alias::TargetType::Export: { |
1035 | 1.25k | const auto Idx = Alias.getExport().first; |
1036 | 1.25k | const auto &Name = Alias.getExport().second; |
1037 | | |
1038 | 1.25k | if (Sort.isCore()) { |
1039 | 2 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1040 | 2 | spdlog::error(" Alias export: Mapping an export '{}' to core:sort"sv, |
1041 | 2 | Name); |
1042 | 2 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1043 | 2 | } |
1044 | | |
1045 | 1.25k | if (Idx >= |
1046 | 1.25k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) { |
1047 | 36 | spdlog::error(ErrCode::Value::InvalidIndex); |
1048 | 36 | spdlog::error( |
1049 | 36 | " Alias export: Export index {} exceeds available component instance index {}"sv, |
1050 | 36 | Idx, |
1051 | 36 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)); |
1052 | 36 | return Unexpect(ErrCode::Value::InvalidIndex); |
1053 | 36 | } |
1054 | | |
1055 | 1.22k | const auto &InstExports = CompCtx.getInstance(Idx).Exports; |
1056 | 1.22k | auto It = InstExports.find(std::string(Name)); |
1057 | 1.22k | if (It == InstExports.cend()) { |
1058 | 21 | spdlog::error(ErrCode::Value::ExportNotFound); |
1059 | 21 | spdlog::error( |
1060 | 21 | " Alias export: No matching export '{}' found in component instance index {}"sv, |
1061 | 21 | Name, Idx); |
1062 | 21 | return Unexpect(ErrCode::Value::ExportNotFound); |
1063 | 21 | } |
1064 | | |
1065 | 1.20k | if (It->second.ST != Sort.getSortType()) { |
1066 | 2 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1067 | 2 | spdlog::error(" Alias export: Type mapping mismatch for export '{}'"sv, |
1068 | 2 | Name); |
1069 | 2 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1070 | 2 | } |
1071 | 1.19k | return {}; |
1072 | 1.20k | } |
1073 | 362 | case AST::Component::Alias::TargetType::CoreExport: { |
1074 | 362 | const auto Idx = Alias.getExport().first; |
1075 | 362 | const auto &Name = Alias.getExport().second; |
1076 | | |
1077 | 362 | if (!Sort.isCore()) { |
1078 | 4 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1079 | 4 | spdlog::error(" Alias core:export: Mapping a export '{}' to sort"sv, |
1080 | 4 | Name); |
1081 | 4 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1082 | 4 | } |
1083 | | |
1084 | 358 | if (Idx >= CompCtx.getCoreSortIndexSize( |
1085 | 358 | AST::Component::Sort::CoreSortType::Instance)) { |
1086 | 35 | spdlog::error(ErrCode::Value::InvalidIndex); |
1087 | 35 | spdlog::error( |
1088 | 35 | " Alias core:export: Export index {} exceeds available core instance index {}"sv, |
1089 | 35 | Idx, |
1090 | 35 | CompCtx.getCoreSortIndexSize( |
1091 | 35 | AST::Component::Sort::CoreSortType::Instance) - |
1092 | 35 | 1); |
1093 | 35 | return Unexpect(ErrCode::Value::InvalidIndex); |
1094 | 35 | } |
1095 | | |
1096 | 323 | const auto &CoreExports = CompCtx.getCoreInstance(Idx); |
1097 | 323 | auto It = CoreExports.find(std::string(Name)); |
1098 | 323 | if (It == CoreExports.end()) { |
1099 | 10 | spdlog::error(ErrCode::Value::ExportNotFound); |
1100 | 10 | spdlog::error( |
1101 | 10 | " Alias core:export: No matching export '{}' found in core instance index {}"sv, |
1102 | 10 | Name, Idx); |
1103 | 10 | return Unexpect(ErrCode::Value::ExportNotFound); |
1104 | 10 | } |
1105 | | |
1106 | 313 | const auto ExternTy = It->second; |
1107 | 313 | AST::Component::Sort::CoreSortType ST; |
1108 | 313 | switch (ExternTy) { |
1109 | 313 | case ExternalType::Function: |
1110 | 313 | ST = AST::Component::Sort::CoreSortType::Func; |
1111 | 313 | break; |
1112 | 0 | case ExternalType::Table: |
1113 | 0 | ST = AST::Component::Sort::CoreSortType::Table; |
1114 | 0 | break; |
1115 | 0 | case ExternalType::Memory: |
1116 | 0 | ST = AST::Component::Sort::CoreSortType::Memory; |
1117 | 0 | break; |
1118 | 0 | case ExternalType::Global: |
1119 | 0 | ST = AST::Component::Sort::CoreSortType::Global; |
1120 | 0 | break; |
1121 | 0 | case ExternalType::Tag: |
1122 | 0 | ST = AST::Component::Sort::CoreSortType::Tag; |
1123 | 0 | break; |
1124 | 0 | default: |
1125 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1126 | 0 | spdlog::error( |
1127 | 0 | " Alias core:export: Type mapping mismatch for export '{}'"sv, |
1128 | 0 | Name); |
1129 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1130 | 313 | } |
1131 | 313 | if (ST != Sort.getCoreSortType()) { |
1132 | | // The error message differs of the tag core sort. |
1133 | 4 | ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex; |
1134 | 4 | if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) { |
1135 | 3 | ErrValue = ErrCode::Value::UnknownCoreTag; |
1136 | 3 | } |
1137 | 4 | spdlog::error(ErrValue); |
1138 | 4 | spdlog::error( |
1139 | 4 | " Alias core:export: Type mapping mismatch for export '{}'"sv, |
1140 | 4 | Name); |
1141 | 4 | return Unexpect(ErrValue); |
1142 | 4 | } |
1143 | 309 | return {}; |
1144 | 313 | } |
1145 | 5.71k | case AST::Component::Alias::TargetType::Outer: { |
1146 | 5.71k | const auto Ct = Alias.getOuter().first; |
1147 | 5.71k | const auto Idx = Alias.getOuter().second; |
1148 | | |
1149 | 5.71k | uint32_t OutLinkCompCnt = 0; |
1150 | 5.71k | const auto *TargetCtx = &CompCtx.getCurrentContext(); |
1151 | 6.19k | while (Ct > OutLinkCompCnt && TargetCtx != nullptr) { |
1152 | 481 | TargetCtx = TargetCtx->Parent; |
1153 | 481 | OutLinkCompCnt++; |
1154 | 481 | } |
1155 | 5.71k | if (TargetCtx == nullptr) { |
1156 | 65 | spdlog::error(ErrCode::Value::InvalidIndex); |
1157 | | // The final hop is the one that ran off the top of the scope chain, so |
1158 | | // the number of actually-enclosing components is one less than the hop |
1159 | | // count. |
1160 | 65 | spdlog::error( |
1161 | 65 | " Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv, |
1162 | 65 | Ct, OutLinkCompCnt - 1); |
1163 | 65 | return Unexpect(ErrCode::Value::InvalidIndex); |
1164 | 65 | } |
1165 | | |
1166 | 5.65k | if (Sort.isCore()) { |
1167 | 292 | if (Sort.getCoreSortType() != |
1168 | 292 | AST::Component::Sort::CoreSortType::Module && |
1169 | 282 | Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) { |
1170 | 4 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1171 | 4 | spdlog::error( |
1172 | 4 | " Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv); |
1173 | 4 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1174 | 4 | } |
1175 | 288 | if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) { |
1176 | 13 | spdlog::error(ErrCode::Value::InvalidIndex); |
1177 | 13 | spdlog::error( |
1178 | 13 | " Alias outer: core:sort index {} invalid in component context"sv, |
1179 | 13 | Idx); |
1180 | 13 | return Unexpect(ErrCode::Value::InvalidIndex); |
1181 | 13 | } |
1182 | 5.35k | } else { |
1183 | 5.35k | if (Sort.getSortType() != AST::Component::Sort::SortType::Type && |
1184 | 512 | Sort.getSortType() != AST::Component::Sort::SortType::Component) { |
1185 | 2 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1186 | 2 | spdlog::error( |
1187 | 2 | " Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv); |
1188 | 2 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1189 | 2 | } |
1190 | 5.35k | if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) { |
1191 | 6 | spdlog::error(ErrCode::Value::InvalidIndex); |
1192 | 6 | spdlog::error( |
1193 | 6 | " Alias outer: sort index {} invalid in component context"sv, |
1194 | 6 | Idx); |
1195 | 6 | return Unexpect(ErrCode::Value::InvalidIndex); |
1196 | 6 | } |
1197 | | // Outer-aliasing a resource type is permitted: the alias preserves the |
1198 | | // resource's type identity rather than introducing a fresh generative |
1199 | | // resource, so a nested type/component can legitimately refer to a |
1200 | | // resource in an enclosing scope (e.g. an instance type referring to a |
1201 | | // resource imported by its enclosing component type). |
1202 | | // |
1203 | | // TODO: implement wasm-tools' full free-variable rule — reject an outer |
1204 | | // alias to a type whose transitive free variables include resources that |
1205 | | // would escape a crossed component boundary. That needs a free-variable |
1206 | | // walker over type bodies, tracked alongside the Phase 3/4 type-handle |
1207 | | // work (GAP-TH-1, GAP-EX-5). |
1208 | 5.35k | } |
1209 | 5.62k | return {}; |
1210 | 5.65k | } |
1211 | 0 | default: |
1212 | 0 | assumingUnreachable(); |
1213 | 7.33k | } |
1214 | 7.33k | } |
1215 | | |
1216 | | Expect<void> |
1217 | 296k | Validator::validate(const AST::Component::CoreDefType &DType) noexcept { |
1218 | 296k | if (DType.isRecType()) { |
1219 | | // Each sub-type in the rec group gets its own entry in core:type. |
1220 | 158k | for (const auto &ST : DType.getSubTypes()) { |
1221 | 158k | CompCtx.addCoreType(&ST); |
1222 | 158k | } |
1223 | 158k | } else if (DType.isModuleType()) { |
1224 | | // Module types are validated with an initially-empty type index space. |
1225 | 137k | CompCtx.enterTypeDefinition(); |
1226 | 137k | for (const auto &Decl : DType.getModuleType()) { |
1227 | 1.74k | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
1228 | 1.74k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType)); |
1229 | 1.74k | return E; |
1230 | 1.74k | })); |
1231 | 1.74k | } |
1232 | 137k | CompCtx.exitComponent(); |
1233 | | // Module type gets a core:type slot (Body=nullptr) bound to the |
1234 | | // CoreDefType so (core module (type i)) can recover the body — GAP-CI-1. |
1235 | 137k | uint32_t NewTypeIdx = CompCtx.addCoreType(); |
1236 | 137k | CompCtx.setCoreModuleType(NewTypeIdx, &DType); |
1237 | 137k | } else { |
1238 | 0 | assumingUnreachable(); |
1239 | 0 | } |
1240 | 296k | return {}; |
1241 | 296k | } |
1242 | | |
1243 | | Expect<void> |
1244 | 1.77M | Validator::validate(const AST::Component::DefType &DType) noexcept { |
1245 | 1.77M | auto ReportError = [](auto E) { |
1246 | 391 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
1247 | 391 | return E; |
1248 | 391 | }; |
1249 | | |
1250 | 1.77M | if (DType.isDefValType()) { |
1251 | 86.5k | EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError)); |
1252 | 1.69M | } else if (DType.isFuncType()) { |
1253 | 11.7k | EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError)); |
1254 | 1.68M | } else if (DType.isComponentType()) { |
1255 | 1.12M | EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError)); |
1256 | 1.12M | } else if (DType.isInstanceType()) { |
1257 | 535k | EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError)); |
1258 | 535k | } else if (DType.isResourceType()) { |
1259 | 21.0k | EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError)); |
1260 | 21.0k | } else { |
1261 | 0 | assumingUnreachable(); |
1262 | 0 | } |
1263 | | // addType records body/id/locality for resource DefTypes in one step. |
1264 | 1.77M | CompCtx.addType(&DType); |
1265 | 1.77M | return {}; |
1266 | 1.77M | } |
1267 | | |
1268 | | Expect<void> |
1269 | 9.08k | Validator::validate(const AST::Component::Canonical &Canon) noexcept { |
1270 | 9.08k | switch (Canon.getOpCode()) { |
1271 | 4.88k | case ComponentCanonOpCode::Lift: |
1272 | 4.88k | return validateCanonLift(Canon); |
1273 | 1.21k | case ComponentCanonOpCode::Lower: |
1274 | 1.21k | return validateCanonLower(Canon); |
1275 | 782 | case ComponentCanonOpCode::Resource__new: |
1276 | 782 | return validateCanonResourceNew(Canon); |
1277 | 456 | case ComponentCanonOpCode::Resource__rep: |
1278 | 456 | return validateCanonResourceRep(Canon); |
1279 | 1.33k | case ComponentCanonOpCode::Resource__drop: |
1280 | 1.72k | case ComponentCanonOpCode::Resource__drop_async: |
1281 | 1.72k | return validateCanonResourceDrop(Canon); |
1282 | 11 | default: |
1283 | 11 | spdlog::error(ErrCode::Value::ComponentNotImplValidator); |
1284 | 11 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1285 | 11 | return Unexpect(ErrCode::Value::ComponentNotImplValidator); |
1286 | 9.08k | } |
1287 | 9.08k | } |
1288 | | |
1289 | | Expect<void> Validator::validateCanonOptions( |
1290 | | ComponentCanonOpCode Code, |
1291 | 8.84k | Span<const AST::Component::CanonOpt> Opts) noexcept { |
1292 | 8.84k | using OptCode = ComponentCanonOptCode; |
1293 | 8.84k | using CanonOp = ComponentCanonOpCode; |
1294 | | |
1295 | | // Only canon lift/lower accept canonical options. Any other built-in |
1296 | | // (resource.new/rep/drop, drop_async, ...) must be invoked without options. |
1297 | 8.84k | if (Code != CanonOp::Lift && Code != CanonOp::Lower && !Opts.empty()) { |
1298 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1299 | 0 | spdlog::error( |
1300 | 0 | " canonical options are not allowed for this canon built-in"sv); |
1301 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1302 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1303 | 0 | } |
1304 | | |
1305 | 8.84k | bool HasEncoding = false; |
1306 | 8.84k | bool HasMemory = false; |
1307 | 8.84k | bool HasRealloc = false; |
1308 | 8.84k | bool HasPostReturn = false; |
1309 | 8.84k | bool HasAsync = false; |
1310 | 8.84k | bool HasCallback = false; |
1311 | 8.84k | bool HasAlwaysTaskReturn = false; |
1312 | 8.84k | uint32_t ReallocIdx = 0; |
1313 | 8.84k | uint32_t CallbackIdx = 0; |
1314 | 8.84k | uint32_t PostReturnIdx = 0; |
1315 | 8.84k | uint32_t MemoryIdx = 0; |
1316 | | |
1317 | 8.84k | auto RejectDup = [&](const char *Name) -> Expect<void> { |
1318 | 13 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1319 | 13 | spdlog::error(" canonical option '{}' appears more than once"sv, Name); |
1320 | 13 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1321 | 13 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1322 | 13 | }; |
1323 | 8.84k | auto RejectSite = [&](const char *Name) -> Expect<void> { |
1324 | 3 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1325 | 3 | spdlog::error( |
1326 | 3 | " canonical option '{}' is not allowed in this canon built-in"sv, |
1327 | 3 | Name); |
1328 | 3 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1329 | 3 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1330 | 3 | }; |
1331 | | |
1332 | 8.84k | for (const auto &Opt : Opts) { |
1333 | 1.25k | switch (Opt.getCode()) { |
1334 | 558 | case OptCode::Encode_UTF8: |
1335 | 1.04k | case OptCode::Encode_UTF16: |
1336 | 1.15k | case OptCode::Encode_Latin1: |
1337 | 1.15k | if (HasEncoding) { |
1338 | 8 | return RejectDup("string-encoding"); |
1339 | 8 | } |
1340 | 1.14k | HasEncoding = true; |
1341 | 1.14k | break; |
1342 | 10 | case OptCode::Memory: |
1343 | 10 | if (HasMemory) { |
1344 | 1 | return RejectDup("memory"); |
1345 | 1 | } |
1346 | 9 | HasMemory = true; |
1347 | 9 | MemoryIdx = Opt.getIndex(); |
1348 | 9 | break; |
1349 | 5 | case OptCode::Realloc: |
1350 | 5 | if (HasRealloc) { |
1351 | 1 | return RejectDup("realloc"); |
1352 | 1 | } |
1353 | 4 | HasRealloc = true; |
1354 | 4 | ReallocIdx = Opt.getIndex(); |
1355 | 4 | break; |
1356 | 3 | case OptCode::PostReturn: |
1357 | 3 | if (Code != CanonOp::Lift) { |
1358 | 1 | return RejectSite("post-return"); |
1359 | 1 | } |
1360 | 2 | if (HasPostReturn) { |
1361 | 0 | return RejectDup("post-return"); |
1362 | 0 | } |
1363 | 2 | HasPostReturn = true; |
1364 | 2 | PostReturnIdx = Opt.getIndex(); |
1365 | 2 | break; |
1366 | 72 | case OptCode::Async: |
1367 | 72 | if (HasAsync) { |
1368 | 1 | return RejectDup("async"); |
1369 | 1 | } |
1370 | 71 | HasAsync = true; |
1371 | 71 | break; |
1372 | 5 | case OptCode::Callback: |
1373 | 5 | if (Code != CanonOp::Lift) { |
1374 | 1 | return RejectSite("callback"); |
1375 | 1 | } |
1376 | 4 | if (HasCallback) { |
1377 | 0 | return RejectDup("callback"); |
1378 | 0 | } |
1379 | 4 | HasCallback = true; |
1380 | 4 | CallbackIdx = Opt.getIndex(); |
1381 | 4 | break; |
1382 | 8 | case OptCode::AlwaysTaskReturn: |
1383 | 8 | if (Code != CanonOp::Lift) { |
1384 | 1 | return RejectSite("always-task-return"); |
1385 | 1 | } |
1386 | 7 | if (HasAlwaysTaskReturn) { |
1387 | 2 | return RejectDup("always-task-return"); |
1388 | 2 | } |
1389 | 5 | HasAlwaysTaskReturn = true; |
1390 | 5 | break; |
1391 | 0 | default: |
1392 | 0 | spdlog::error(ErrCode::Value::UnknownCanonicalOption); |
1393 | 0 | spdlog::error(" unknown canonical option code 0x{:02x}"sv, |
1394 | 0 | static_cast<unsigned>(Opt.getCode())); |
1395 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1396 | 0 | return Unexpect(ErrCode::Value::UnknownCanonicalOption); |
1397 | 1.25k | } |
1398 | 1.25k | } |
1399 | | |
1400 | | // Structural rules. |
1401 | 8.82k | if (HasPostReturn && HasAsync) { |
1402 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1403 | 0 | spdlog::error( |
1404 | 0 | " canonical options 'post-return' and 'async' are mutually exclusive"sv); |
1405 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1406 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1407 | 0 | } |
1408 | 8.82k | if (HasCallback && !HasAsync) { |
1409 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1410 | 0 | spdlog::error(" canonical option 'callback' requires 'async'"sv); |
1411 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1412 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1413 | 0 | } |
1414 | 8.82k | if (HasAlwaysTaskReturn && !HasAsync) { |
1415 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1416 | 0 | spdlog::error( |
1417 | 0 | " canonical option 'always-task-return' requires 'async'"sv); |
1418 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1419 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1420 | 0 | } |
1421 | 8.82k | if (HasRealloc && !HasMemory) { |
1422 | 1 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1423 | 1 | spdlog::error( |
1424 | 1 | " canonical option 'realloc' requires 'memory' to also be specified"sv); |
1425 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1426 | 1 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1427 | 1 | } |
1428 | | |
1429 | | // Index bounds checks. Core func signature body checks (realloc/callback) |
1430 | | // deferred as GAP-C-5b once getCoreFunc signatures are populated. |
1431 | 8.82k | if (HasMemory && |
1432 | 7 | MemoryIdx >= CompCtx.getCoreSortIndexSize( |
1433 | 7 | AST::Component::Sort::CoreSortType::Memory)) { |
1434 | 7 | spdlog::error(ErrCode::Value::InvalidIndex); |
1435 | 7 | spdlog::error( |
1436 | 7 | " canonical option 'memory': core memory index {} out of bounds"sv, |
1437 | 7 | MemoryIdx); |
1438 | 7 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1439 | 7 | return Unexpect(ErrCode::Value::InvalidIndex); |
1440 | 7 | } |
1441 | 8.81k | const uint32_t CoreFuncSpaceSize = |
1442 | 8.81k | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func); |
1443 | 8.81k | if (HasRealloc && ReallocIdx >= CoreFuncSpaceSize) { |
1444 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1445 | 0 | spdlog::error( |
1446 | 0 | " canonical option 'realloc': core func index {} out of bounds"sv, |
1447 | 0 | ReallocIdx); |
1448 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1449 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1450 | 0 | } |
1451 | 8.81k | if (HasCallback && CallbackIdx >= CoreFuncSpaceSize) { |
1452 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1453 | 0 | spdlog::error( |
1454 | 0 | " canonical option 'callback': core func index {} out of bounds"sv, |
1455 | 0 | CallbackIdx); |
1456 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1457 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1458 | 0 | } |
1459 | 8.81k | if (HasPostReturn && PostReturnIdx >= CoreFuncSpaceSize) { |
1460 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1461 | 0 | spdlog::error( |
1462 | 0 | " canonical option 'post-return': core func index {} out of bounds"sv, |
1463 | 0 | PostReturnIdx); |
1464 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1465 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1466 | 0 | } |
1467 | 8.81k | return {}; |
1468 | 8.81k | } |
1469 | | |
1470 | | Expect<void> |
1471 | 4.88k | Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept { |
1472 | 4.88k | const uint32_t CoreFuncIdx = Canon.getIndex(); |
1473 | 4.88k | const uint32_t CoreFuncSpaceSize = |
1474 | 4.88k | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func); |
1475 | | // 1. Core func index bounds. |
1476 | 4.88k | if (CoreFuncIdx >= CoreFuncSpaceSize) { |
1477 | 35 | spdlog::error(ErrCode::Value::InvalidIndex); |
1478 | 35 | spdlog::error( |
1479 | 35 | " canon lift: core func index {} exceeds core func index space size {}"sv, |
1480 | 35 | CoreFuncIdx, CoreFuncSpaceSize); |
1481 | 35 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1482 | 35 | return Unexpect(ErrCode::Value::InvalidIndex); |
1483 | 35 | } |
1484 | 4.85k | const uint32_t TypeIdx = Canon.getTargetIndex(); |
1485 | 4.85k | const uint32_t TypeSpaceSize = |
1486 | 4.85k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1487 | | // 2. Target type index bounds. |
1488 | 4.85k | if (TypeIdx >= TypeSpaceSize) { |
1489 | 8 | spdlog::error(ErrCode::Value::InvalidIndex); |
1490 | 8 | spdlog::error( |
1491 | 8 | " canon lift: type index {} exceeds type index space size {}"sv, |
1492 | 8 | TypeIdx, TypeSpaceSize); |
1493 | 8 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1494 | 8 | return Unexpect(ErrCode::Value::InvalidIndex); |
1495 | 8 | } |
1496 | | // 3. Target type must be a component FuncType. |
1497 | 4.84k | const auto *DT = CompCtx.getDefType(TypeIdx); |
1498 | 4.84k | if (DT == nullptr) { |
1499 | | // Unresolved slot: the index was registered by an import or outer alias |
1500 | | // but the concrete definition has not been filled in yet. |
1501 | 1 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1502 | 1 | spdlog::error( |
1503 | 1 | " canon lift: target type index {} is an unresolved type slot"sv, |
1504 | 1 | TypeIdx); |
1505 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1506 | 1 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1507 | 1 | } |
1508 | 4.84k | if (!DT->isFuncType()) { |
1509 | 2 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1510 | 2 | spdlog::error( |
1511 | 2 | " canon lift: target type index {} does not reference a component func type"sv, |
1512 | 2 | TypeIdx); |
1513 | 2 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1514 | 2 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1515 | 2 | } |
1516 | | // 4. Validate canonical options (Lift site allows all). |
1517 | 4.84k | EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions())); |
1518 | | // 5. Allocate component func, binding the resolved FuncType. Full ABI |
1519 | | // signature match (flat_lifted) deferred as GAP-C-1b. |
1520 | 4.83k | CompCtx.addFunc(&DT->getFuncType()); |
1521 | 4.83k | return {}; |
1522 | 4.84k | } |
1523 | | |
1524 | | Expect<void> |
1525 | 1.21k | Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept { |
1526 | 1.21k | const uint32_t FuncIdx = Canon.getIndex(); |
1527 | 1.21k | const uint32_t FuncSpaceSize = |
1528 | 1.21k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func); |
1529 | | // 1. Component func index bounds. |
1530 | 1.21k | if (FuncIdx >= FuncSpaceSize) { |
1531 | 6 | spdlog::error(ErrCode::Value::InvalidIndex); |
1532 | 6 | spdlog::error( |
1533 | 6 | " canon lower: component func index {} exceeds func index space size {}"sv, |
1534 | 6 | FuncIdx, FuncSpaceSize); |
1535 | 6 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1536 | 6 | return Unexpect(ErrCode::Value::InvalidIndex); |
1537 | 6 | } |
1538 | | // 2. Validate canonical options (per-site rules for Lower). |
1539 | 1.21k | EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions())); |
1540 | | // 3. Allocate the resulting core func. Full ABI signature synthesis |
1541 | | // (flatten_functype for lower) deferred as GAP-C-2b. |
1542 | 1.19k | CompCtx.addCoreFunc(); |
1543 | 1.19k | return {}; |
1544 | 1.21k | } |
1545 | | |
1546 | | Expect<void> Validator::validateCanonResourceNew( |
1547 | 782 | const AST::Component::Canonical &Canon) noexcept { |
1548 | 782 | const uint32_t Idx = Canon.getIndex(); |
1549 | 782 | const uint32_t TypeSpaceSize = |
1550 | 782 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1551 | | // 1. Type index bounds. |
1552 | 782 | if (Idx >= TypeSpaceSize) { |
1553 | 22 | spdlog::error(ErrCode::Value::InvalidIndex); |
1554 | 22 | spdlog::error( |
1555 | 22 | " canon resource.new: type index {} exceeds type index space size {}"sv, |
1556 | 22 | Idx, TypeSpaceSize); |
1557 | 22 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1558 | 22 | return Unexpect(ErrCode::Value::InvalidIndex); |
1559 | 22 | } |
1560 | | // 2. Type must be a locally-defined resource. |
1561 | 760 | const auto *RInfo = CompCtx.getResource(Idx); |
1562 | 760 | if (RInfo == nullptr) { |
1563 | 19 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1564 | 19 | spdlog::error( |
1565 | 19 | " canon resource.new: type index {} does not reference a resource"sv, |
1566 | 19 | Idx); |
1567 | 19 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1568 | 19 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1569 | 19 | } |
1570 | 741 | if (!RInfo->LocallyDefined) { |
1571 | 1 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1572 | 1 | spdlog::error( |
1573 | 1 | " canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv, |
1574 | 1 | Idx); |
1575 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1576 | 1 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1577 | 1 | } |
1578 | | // 4. Validate canonical options. |
1579 | 740 | EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions())); |
1580 | | // 5. Allocate the resulting core func with synthesized signature |
1581 | | // [i32] -> [i32] (rep i32 in, new handle out). |
1582 | 740 | CompCtx.addCoreFunc(&CoreFuncType_I32_I32); |
1583 | 740 | return {}; |
1584 | 740 | } |
1585 | | |
1586 | | Expect<void> Validator::validateCanonResourceRep( |
1587 | 456 | const AST::Component::Canonical &Canon) noexcept { |
1588 | 456 | const uint32_t Idx = Canon.getIndex(); |
1589 | 456 | const uint32_t TypeSpaceSize = |
1590 | 456 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1591 | | // 1. Type index bounds. |
1592 | 456 | if (Idx >= TypeSpaceSize) { |
1593 | 31 | spdlog::error(ErrCode::Value::InvalidIndex); |
1594 | 31 | spdlog::error( |
1595 | 31 | " canon resource.rep: type index {} exceeds type index space size {}"sv, |
1596 | 31 | Idx, TypeSpaceSize); |
1597 | 31 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1598 | 31 | return Unexpect(ErrCode::Value::InvalidIndex); |
1599 | 31 | } |
1600 | | // 2. Type must be a locally-defined resource. |
1601 | 425 | const auto *RInfo = CompCtx.getResource(Idx); |
1602 | 425 | if (RInfo == nullptr) { |
1603 | 34 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1604 | 34 | spdlog::error( |
1605 | 34 | " canon resource.rep: type index {} does not reference a resource"sv, |
1606 | 34 | Idx); |
1607 | 34 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1608 | 34 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1609 | 34 | } |
1610 | 391 | if (!RInfo->LocallyDefined) { |
1611 | 3 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1612 | 3 | spdlog::error( |
1613 | 3 | " canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv, |
1614 | 3 | Idx); |
1615 | 3 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1616 | 3 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1617 | 3 | } |
1618 | | // 4. Validate canonical options. |
1619 | 388 | EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions())); |
1620 | | // 5. Allocate the resulting core func with synthesized signature |
1621 | | // [i32] -> [i32] (handle in, rep out). |
1622 | 388 | CompCtx.addCoreFunc(&CoreFuncType_I32_I32); |
1623 | 388 | return {}; |
1624 | 388 | } |
1625 | | |
1626 | | Expect<void> Validator::validateCanonResourceDrop( |
1627 | 1.72k | const AST::Component::Canonical &Canon) noexcept { |
1628 | 1.72k | const uint32_t Idx = Canon.getIndex(); |
1629 | 1.72k | const uint32_t TypeSpaceSize = |
1630 | 1.72k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1631 | | // 1. Type index bounds. |
1632 | 1.72k | if (Idx >= TypeSpaceSize) { |
1633 | 41 | spdlog::error(ErrCode::Value::InvalidIndex); |
1634 | 41 | spdlog::error( |
1635 | 41 | " canon resource.drop: type index {} exceeds type index space size {}"sv, |
1636 | 41 | Idx, TypeSpaceSize); |
1637 | 41 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1638 | 41 | return Unexpect(ErrCode::Value::InvalidIndex); |
1639 | 41 | } |
1640 | | // 2. Type must be a resource type. |
1641 | 1.68k | if (CompCtx.getResource(Idx) == nullptr) { |
1642 | 24 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1643 | 24 | spdlog::error( |
1644 | 24 | " canon resource.drop: type index {} does not reference a resource"sv, |
1645 | 24 | Idx); |
1646 | 24 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1647 | 24 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1648 | 24 | } |
1649 | | // 3. resource.drop accepts both local and imported resources — no locality |
1650 | | // check. |
1651 | | // 4. Validate canonical options. |
1652 | 1.66k | EXPECTED_TRY(validateCanonOptions(Canon.getOpCode(), Canon.getOptions())); |
1653 | | // 5. Allocate the resulting core func with synthesized signature |
1654 | | // [i32] -> [] (handle in, no result — matches the resource destructor |
1655 | | // shape required by validate(ResourceType)). |
1656 | 1.66k | CompCtx.addCoreFunc(&CoreFuncType_I32_Void); |
1657 | 1.66k | return {}; |
1658 | 1.66k | } |
1659 | | |
1660 | 6.95k | Expect<void> Validator::validate(const AST::Component::Import &Im) noexcept { |
1661 | | // Validation steps: |
1662 | | // 1. Validate the externdesc and introduce the imported entity into |
1663 | | // its sort's index space. |
1664 | | // 2. Parse the import name per the structured import-name grammar. |
1665 | | // 3. Enforce the annotated-name constraints ([constructor]/[method]/ |
1666 | | // [static] only on func imports). |
1667 | | // 4. Reject duplicate import names (strongly-unique across imports). |
1668 | | // |
1669 | | // The per-annotated-name structural checks (constructor result type, |
1670 | | // method `self` param, static resource name in scope) are not yet |
1671 | | // implemented and tracked as follow-ups below. |
1672 | | // Snapshot the type space size before validating the desc so we can |
1673 | | // recover the new type index allocated by a TypeBound (sub resource). |
1674 | 6.95k | const uint32_t TypeSpaceBefore = |
1675 | 6.95k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1676 | | |
1677 | 6.95k | EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) { |
1678 | 6.87k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1679 | 6.87k | return E; |
1680 | 6.87k | })); |
1681 | | |
1682 | 13.1k | EXPECTED_TRY(ComponentName CName, |
1683 | 13.1k | ComponentName::parse(Im.getName()).map_error([](auto E) { |
1684 | 13.1k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1685 | 13.1k | return E; |
1686 | 13.1k | })); |
1687 | | |
1688 | | // Annotated plainnames ([constructor], [method], [static]) can only appear |
1689 | | // on func imports. |
1690 | 13.1k | switch (CName.getKind()) { |
1691 | 6 | case ComponentNameKind::Constructor: |
1692 | 10 | case ComponentNameKind::Method: |
1693 | 20 | case ComponentNameKind::Static: |
1694 | 20 | if (Im.getDesc().getDescType() != |
1695 | 20 | AST::Component::ExternDesc::DescType::FuncType) { |
1696 | 20 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
1697 | 20 | spdlog::error(" Import: annotated name requires func type"sv); |
1698 | 20 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1699 | 20 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
1700 | 20 | } |
1701 | 0 | break; |
1702 | 6.21k | default: |
1703 | 6.21k | break; |
1704 | 13.1k | } |
1705 | | |
1706 | | // Resolve the resource name referenced by an annotated name. The resource |
1707 | | // must have been previously introduced by a TypeBound import or export |
1708 | | // with a kebab-case label in this scope. |
1709 | | // TODO: extend to the full structural checks once func-type bodies are |
1710 | | // walked here: |
1711 | | // - [constructor]R: result type must be (own $R). |
1712 | | // - [method]R.f: first param must be (borrow $R). |
1713 | | // - [static]R.f: no `self` param of (borrow $R). |
1714 | 6.21k | std::string_view ResourceLabel; |
1715 | 6.21k | switch (CName.getKind()) { |
1716 | 0 | case ComponentNameKind::Constructor: |
1717 | 0 | ResourceLabel = CName.getDetail().get<ConstructorDetail>().Label; |
1718 | 0 | break; |
1719 | 0 | case ComponentNameKind::Method: |
1720 | 0 | ResourceLabel = CName.getDetail().get<MethodDetail>().Resource; |
1721 | 0 | break; |
1722 | 0 | case ComponentNameKind::Static: |
1723 | 0 | ResourceLabel = CName.getDetail().get<StaticDetail>().Resource; |
1724 | 0 | break; |
1725 | 6.21k | default: |
1726 | 6.21k | break; |
1727 | 6.21k | } |
1728 | 6.21k | if (!ResourceLabel.empty() && !CompCtx.hasResourceLabel(ResourceLabel)) { |
1729 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
1730 | 0 | spdlog::error( |
1731 | 0 | " Import: annotated name references unknown resource '{}'"sv, |
1732 | 0 | ResourceLabel); |
1733 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1734 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
1735 | 0 | } |
1736 | | |
1737 | 6.21k | if (!CompCtx.addImportedName(CName)) { |
1738 | 93 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
1739 | 93 | spdlog::error(" Import: Duplicate import name"sv); |
1740 | 93 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1741 | 93 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
1742 | 93 | } |
1743 | | |
1744 | | // If this import introduced a TypeBound resource with a label name, |
1745 | | // register the label so subsequent annotated names can reference it. |
1746 | 6.12k | if (Im.getDesc().getDescType() == |
1747 | 6.12k | AST::Component::ExternDesc::DescType::TypeBound && |
1748 | 3.01k | CName.getKind() == ComponentNameKind::Label) { |
1749 | 2.97k | CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore); |
1750 | 2.97k | } |
1751 | | |
1752 | 6.12k | return {}; |
1753 | 6.21k | } |
1754 | | |
1755 | 894 | Expect<void> Validator::validate(const AST::Component::Export &Ex) noexcept { |
1756 | | // Validation steps: |
1757 | | // 1. `sortidx` is in-bounds and (for core sorts) must be `core module`. |
1758 | | // 2. If an `externdesc` ascription is present, it must be a supertype |
1759 | | // of the inferred externdesc of the `sortidx` (kind-only check for |
1760 | | // now; structural subtype is a follow-up). |
1761 | | // 3. The export name parses under the export-name grammar. |
1762 | | // 4. The export name is strongly-unique across exports. |
1763 | | // 5. The export introduces a new index aliasing the definition in the |
1764 | | // component's own index space for its sort. |
1765 | | // |
1766 | | // Not yet enforced: transitive resource-avoidance in exported types; |
1767 | | // flipping the "consumed" flag for value exports. |
1768 | | |
1769 | | // Validate the sortidx bounds. |
1770 | 894 | const auto &Sort = Ex.getSortIndex().getSort(); |
1771 | 894 | uint32_t Idx = Ex.getSortIndex().getIdx(); |
1772 | 894 | if (Sort.isCore()) { |
1773 | | // The externdesc grammar permits only `core module` as a component-level |
1774 | | // core export — no other core sort is exportable from a component. |
1775 | 10 | if (Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Module) { |
1776 | 2 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1777 | 2 | spdlog::error( |
1778 | 2 | " Export: core sort other than `core module` is not allowed"sv); |
1779 | 2 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1780 | 2 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1781 | 2 | } |
1782 | 8 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
1783 | 7 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
1784 | 7 | spdlog::error(" Export: sort index {} out of bounds"sv, Idx); |
1785 | 7 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1786 | 7 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
1787 | 7 | } |
1788 | 884 | } else { |
1789 | 884 | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
1790 | 10 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
1791 | 10 | spdlog::error(" Export: sort index {} out of bounds"sv, Idx); |
1792 | 10 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1793 | 10 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
1794 | 10 | } |
1795 | 884 | } |
1796 | | |
1797 | | // If an externdesc ascription is present, the spec requires it to be a |
1798 | | // supertype of the inferred externdesc of the sortidx. For now we only |
1799 | | // enforce that the ascription's kind matches the sortidx's sort; full |
1800 | | // structural subtype between ascription and inferred type is not yet |
1801 | | // implemented. |
1802 | 875 | if (Ex.getDesc().has_value() && |
1803 | 20 | !sortMatchesDescType(Sort, Ex.getDesc()->getDescType())) { |
1804 | 1 | spdlog::error(ErrCode::Value::ExportAscriptionIncompatible); |
1805 | 1 | spdlog::error( |
1806 | 1 | " Export: ascribed externdesc kind does not match sortidx sort"sv); |
1807 | 1 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1808 | 1 | return Unexpect(ErrCode::Value::ExportAscriptionIncompatible); |
1809 | 1 | } |
1810 | | |
1811 | | // Validate name grammar, then enforce strong-uniqueness across exports. |
1812 | 1.66k | EXPECTED_TRY(ComponentName CName, |
1813 | 1.66k | validateExportName(Ex.getName()).map_error([](auto E) { |
1814 | 1.66k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1815 | 1.66k | return E; |
1816 | 1.66k | })); |
1817 | 1.66k | if (!CompCtx.addExportedName(CName)) { |
1818 | 20 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
1819 | 20 | spdlog::error(" Export: Duplicate export name '{}'"sv, Ex.getName()); |
1820 | 20 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1821 | 20 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
1822 | 20 | } |
1823 | | |
1824 | | // Every export introduces a new index that aliases the exported |
1825 | | // definition in the component's own index space for that sort. For |
1826 | | // instance-sort exports we also copy/build the export table so that a |
1827 | | // later alias-export against this slot can resolve its sub-exports. |
1828 | 769 | if (Sort.isCore()) { |
1829 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
1830 | 769 | } else { |
1831 | 769 | const AST::Component::InstanceType *IT = nullptr; |
1832 | 769 | const bool IsInst = |
1833 | 769 | Sort.getSortType() == AST::Component::Sort::SortType::Instance; |
1834 | 769 | const bool HasInstAscription = |
1835 | 769 | IsInst && Ex.getDesc().has_value() && |
1836 | 0 | Ex.getDesc()->getDescType() == |
1837 | 0 | AST::Component::ExternDesc::DescType::InstanceType; |
1838 | 769 | if (HasInstAscription) { |
1839 | 0 | IT = CompCtx.getInstanceType(Ex.getDesc()->getTypeIndex()); |
1840 | 0 | if (IT != nullptr) { |
1841 | 0 | if (auto Missing = findMissingRequiredExport(Idx, *IT)) { |
1842 | 0 | spdlog::error(ErrCode::Value::ExportAscriptionIncompatible); |
1843 | 0 | spdlog::error( |
1844 | 0 | " Export: ascribed instance type requires export '{}' " |
1845 | 0 | "not present in inferred type"sv, |
1846 | 0 | *Missing); |
1847 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1848 | 0 | return Unexpect(ErrCode::Value::ExportAscriptionIncompatible); |
1849 | 0 | } |
1850 | 0 | } |
1851 | 0 | } |
1852 | 769 | uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType()); |
1853 | 769 | if (IsInst) { |
1854 | 6 | if (IT != nullptr) { |
1855 | 0 | populateInstanceFromType(NewIdx, *IT); |
1856 | 6 | } else { |
1857 | | // Either no ascription, or the ascription's type index didn't |
1858 | | // resolve to an inline InstanceType here (cross-scope type-index |
1859 | | // resolution is not yet implemented). Copy the source instance's |
1860 | | // inferred exports so a later alias-export on this slot can still |
1861 | | // find them. |
1862 | 6 | const auto &SrcExports = CompCtx.getInstance(Idx).Exports; |
1863 | 6 | for (const auto &[Name, IE] : SrcExports) { |
1864 | 3 | CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT, |
1865 | 3 | IE.NestedInstIdx); |
1866 | 3 | } |
1867 | 6 | } |
1868 | 6 | } |
1869 | 769 | } |
1870 | 769 | return {}; |
1871 | 769 | } |
1872 | | |
1873 | | Expect<void> |
1874 | 7.90k | Validator::validate(const AST::Component::ExternDesc &Desc) noexcept { |
1875 | | // Validating an externdesc introduces a new entry into the index space |
1876 | | // matching the descriptor's sort: |
1877 | | // * core module (CoreType) → core:module |
1878 | | // * func → func |
1879 | | // * value → value (consumed=false; not yet tracked) |
1880 | | // * type (eq i) → type aliased to i (resource property |
1881 | | // propagated when i refers to a resource) |
1882 | | // * type (sub resource) → fresh abstract resource type |
1883 | | // * component / instance → component / instance |
1884 | | // |
1885 | | // GAP-ED-1: bounds-check the referenced type index. The CoreType |
1886 | | // externdesc indexes the core:type space (the moduletype lives there); |
1887 | | // FuncType / ComponentType / InstanceType all index the component-level |
1888 | | // type space. Kind-of-type checks (e.g. that a FuncType index actually |
1889 | | // resolves to a component func type, not a record) are a follow-up that |
1890 | | // needs the type body to be retained on every type entry. |
1891 | 7.90k | switch (Desc.getDescType()) { |
1892 | 174 | case AST::Component::ExternDesc::DescType::CoreType: { |
1893 | 174 | const uint32_t RefIdx = Desc.getTypeIndex(); |
1894 | 174 | const uint32_t CoreTypeSize = |
1895 | 174 | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type); |
1896 | 174 | if (RefIdx >= CoreTypeSize) { |
1897 | 16 | spdlog::error(ErrCode::Value::InvalidIndex); |
1898 | 16 | spdlog::error( |
1899 | 16 | " ExternDesc: core type index {} exceeds core:type index space size {}"sv, |
1900 | 16 | RefIdx, CoreTypeSize); |
1901 | 16 | return Unexpect(ErrCode::Value::InvalidIndex); |
1902 | 16 | } |
1903 | 158 | break; |
1904 | 174 | } |
1905 | 559 | case AST::Component::ExternDesc::DescType::FuncType: |
1906 | 961 | case AST::Component::ExternDesc::DescType::ComponentType: |
1907 | 1.24k | case AST::Component::ExternDesc::DescType::InstanceType: { |
1908 | 1.24k | const uint32_t RefIdx = Desc.getTypeIndex(); |
1909 | 1.24k | const uint32_t TypeSize = |
1910 | 1.24k | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1911 | 1.24k | if (RefIdx >= TypeSize) { |
1912 | 50 | spdlog::error(ErrCode::Value::InvalidIndex); |
1913 | 50 | spdlog::error( |
1914 | 50 | " ExternDesc: referenced type index {} exceeds type index space size {}"sv, |
1915 | 50 | RefIdx, TypeSize); |
1916 | 50 | return Unexpect(ErrCode::Value::InvalidIndex); |
1917 | 50 | } |
1918 | 1.19k | break; |
1919 | 1.24k | } |
1920 | 6.48k | default: |
1921 | 6.48k | break; |
1922 | 7.90k | } |
1923 | | |
1924 | 7.83k | switch (Desc.getDescType()) { |
1925 | 158 | case AST::Component::ExternDesc::DescType::CoreType: { |
1926 | | // The CoreType externdesc is `(core module (type i))`, so it introduces |
1927 | | // a new core:module slot bound to the moduletype at core:type idx i. |
1928 | 158 | const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex()); |
1929 | 158 | CompCtx.addCoreModule(CT); |
1930 | 158 | break; |
1931 | 0 | } |
1932 | 523 | case AST::Component::ExternDesc::DescType::FuncType: |
1933 | 523 | CompCtx.addFunc(); |
1934 | 523 | break; |
1935 | 3.39k | case AST::Component::ExternDesc::DescType::ValueBound: |
1936 | 3.39k | CompCtx.addValue(); |
1937 | 3.39k | break; |
1938 | 3.09k | case AST::Component::ExternDesc::DescType::TypeBound: |
1939 | 3.09k | if (Desc.isEqType()) { |
1940 | | // (type (eq i)) — alias type i |
1941 | 91 | uint32_t RefIdx = Desc.getTypeIndex(); |
1942 | 91 | if (RefIdx >= |
1943 | 91 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
1944 | 18 | spdlog::error(ErrCode::Value::InvalidIndex); |
1945 | 18 | spdlog::error(" ExternDesc: eq type bound index {} out of bounds"sv, |
1946 | 18 | RefIdx); |
1947 | 18 | return Unexpect(ErrCode::Value::InvalidIndex); |
1948 | 18 | } |
1949 | | // (eq i): inherits the source resource's id; body lives on the |
1950 | | // shared registry entry. |
1951 | 73 | uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false); |
1952 | 73 | if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) { |
1953 | 25 | CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false}); |
1954 | 25 | } |
1955 | 3.00k | } else { |
1956 | | // (sub resource): abstract import — fresh id with no body. |
1957 | 3.00k | uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false); |
1958 | 3.00k | CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(), |
1959 | 3.00k | /*LocallyDefined=*/false}); |
1960 | 3.00k | } |
1961 | 3.07k | break; |
1962 | 3.07k | case AST::Component::ExternDesc::DescType::ComponentType: { |
1963 | | // Bind the new component slot to its ComponentType so a later |
1964 | | // instantiation can pull imports/exports from it (GAP-I-1). |
1965 | 396 | const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex()); |
1966 | 396 | CompCtx.addComponent(CT); |
1967 | 396 | break; |
1968 | 3.09k | } |
1969 | 272 | case AST::Component::ExternDesc::DescType::InstanceType: { |
1970 | | // GAP-ED-2: populate exports from the referenced InstanceType so |
1971 | | // alias-export resolves. GAP-I-5b: also bind it to the slot. |
1972 | 272 | const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex()); |
1973 | 272 | uint32_t InstIdx = CompCtx.addInstance(IT); |
1974 | 272 | if (IT != nullptr) { |
1975 | 107 | populateInstanceFromType(InstIdx, *IT); |
1976 | 107 | } |
1977 | | // If the type index resolved against an outer scope's InstanceType, |
1978 | | // populating from it requires cross-scope walking (the rest of |
1979 | | // GAP-DECL-ED). For now the instance keeps an empty export table when |
1980 | | // the InstanceType body isn't visible in this scope. |
1981 | 272 | break; |
1982 | 3.09k | } |
1983 | 0 | default: |
1984 | 0 | assumingUnreachable(); |
1985 | 7.83k | } |
1986 | | |
1987 | 7.81k | return {}; |
1988 | 7.83k | } |
1989 | | |
1990 | | Expect<void> |
1991 | 1.48k | Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept { |
1992 | 1.48k | if (Desc.isFunc()) { |
1993 | 42 | uint32_t TypeIdx = Desc.getTypeIndex(); |
1994 | 42 | if (TypeIdx >= CompCtx.getCoreSortIndexSize( |
1995 | 42 | AST::Component::Sort::CoreSortType::Type)) { |
1996 | 36 | spdlog::error(ErrCode::Value::InvalidIndex); |
1997 | 36 | spdlog::error(" CoreImportDesc: func type index {} out of bounds"sv, |
1998 | 36 | TypeIdx); |
1999 | 36 | return Unexpect(ErrCode::Value::InvalidIndex); |
2000 | 36 | } |
2001 | 6 | CompCtx.addCoreFunc(); |
2002 | 1.44k | } else if (Desc.isTable()) { |
2003 | 389 | CompCtx.addCoreTable(); |
2004 | 1.05k | } else if (Desc.isMemory()) { |
2005 | 474 | CompCtx.addCoreMemory(); |
2006 | 578 | } else if (Desc.isGlobal()) { |
2007 | 380 | CompCtx.addCoreGlobal(); |
2008 | 380 | } else if (Desc.isTag()) { |
2009 | 198 | CompCtx.addCoreTag(); |
2010 | 198 | } else { |
2011 | 0 | assumingUnreachable(); |
2012 | 0 | } |
2013 | 1.44k | return {}; |
2014 | 1.48k | } |
2015 | | |
2016 | | Expect<void> |
2017 | 661 | Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept { |
2018 | 661 | return validate(Decl.getImportDesc()); |
2019 | 661 | } |
2020 | | |
2021 | | Expect<void> |
2022 | 822 | Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept { |
2023 | 822 | return validate(Decl.getImportDesc()); |
2024 | 822 | } |
2025 | | |
2026 | | Expect<void> |
2027 | 1.74k | Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept { |
2028 | 1.74k | auto ReportError = [](auto E) { |
2029 | 83 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule)); |
2030 | 83 | return E; |
2031 | 83 | }; |
2032 | | |
2033 | 1.74k | if (Decl.isImport()) { |
2034 | 661 | EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError)); |
2035 | 1.08k | } else if (Decl.isType()) { |
2036 | 66 | EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError)); |
2037 | 1.02k | } else if (Decl.isAlias()) { |
2038 | 199 | EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError)); |
2039 | 822 | } else if (Decl.isExport()) { |
2040 | 822 | EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError)); |
2041 | 822 | } else { |
2042 | 0 | assumingUnreachable(); |
2043 | 0 | } |
2044 | 1.66k | return {}; |
2045 | 1.74k | } |
2046 | | |
2047 | | Expect<void> |
2048 | 694 | Validator::validate(const AST::Component::ImportDecl &Decl) noexcept { |
2049 | 694 | auto ReportError = [](auto E) { |
2050 | 11 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import)); |
2051 | 11 | return E; |
2052 | 11 | }; |
2053 | | |
2054 | | // Validate the extern descriptor (also increments sort index spaces). |
2055 | 694 | EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError)); |
2056 | | |
2057 | | // Parse and validate the import name. |
2058 | 1.37k | EXPECTED_TRY(ComponentName CName, |
2059 | 1.37k | ComponentName::parse(Decl.getName()).map_error(ReportError)); |
2060 | | |
2061 | | // Annotated plainnames can only appear on func imports. |
2062 | 1.37k | switch (CName.getKind()) { |
2063 | 0 | case ComponentNameKind::Constructor: |
2064 | 0 | case ComponentNameKind::Method: |
2065 | 0 | case ComponentNameKind::Static: |
2066 | 0 | if (Decl.getExternDesc().getDescType() != |
2067 | 0 | AST::Component::ExternDesc::DescType::FuncType) { |
2068 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2069 | 0 | spdlog::error(" ImportDecl: annotated name requires func type"sv); |
2070 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import)); |
2071 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2072 | 0 | } |
2073 | 0 | break; |
2074 | 683 | default: |
2075 | 683 | break; |
2076 | 1.37k | } |
2077 | | |
2078 | | // Check import name uniqueness. |
2079 | 683 | if (!CompCtx.addImportedName(CName)) { |
2080 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
2081 | 0 | spdlog::error(" ImportDecl: Duplicate import name"sv); |
2082 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import)); |
2083 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
2084 | 0 | } |
2085 | | |
2086 | 683 | return {}; |
2087 | 683 | } |
2088 | | |
2089 | | Expect<void> |
2090 | 258 | Validator::validate(const AST::Component::ExportDecl &Decl) noexcept { |
2091 | | // Check export name grammar and uniqueness before mutating the index |
2092 | | // spaces so a duplicate or malformed name doesn't widen the scope's |
2093 | | // sort counts. |
2094 | 258 | EXPECTED_TRY(ComponentName CName, |
2095 | 257 | validateExportName(Decl.getName()).map_error([](auto E) { |
2096 | 257 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export)); |
2097 | 257 | return E; |
2098 | 257 | })); |
2099 | 257 | if (!CompCtx.addExportedName(CName)) { |
2100 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
2101 | 0 | spdlog::error(" ExportDecl: Duplicate export name '{}'"sv, |
2102 | 0 | Decl.getName()); |
2103 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export)); |
2104 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
2105 | 0 | } |
2106 | | |
2107 | | // Validate the extern descriptor (also increments sort index spaces and |
2108 | | // performs type-index bounds checking). For nested ExportDecls whose |
2109 | | // ExternDesc references a type index defined in an outer scope, the |
2110 | | // current scope's lookup may return nullptr — validate(ExternDesc) handles |
2111 | | // that gracefully by allocating an empty instance slot. Cross-scope |
2112 | | // type-index resolution (walking the parent CompCtxs chain to resolve |
2113 | | // names like `(export "f" (func (type 0)))` where type 0 lives outside |
2114 | | // the InstanceType body) is the structural follow-up tracked by the rest |
2115 | | // of GAP-DECL-ED / GAP-ED-1 / GAP-ED-2. |
2116 | 257 | EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) { |
2117 | 255 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export)); |
2118 | 255 | return E; |
2119 | 255 | })); |
2120 | | |
2121 | 255 | return {}; |
2122 | 257 | } |
2123 | | |
2124 | | Expect<void> |
2125 | 1.27k | Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept { |
2126 | 1.27k | auto ReportError = [](auto E) { |
2127 | 73 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance)); |
2128 | 73 | return E; |
2129 | 73 | }; |
2130 | | |
2131 | 1.27k | if (Decl.isCoreType()) { |
2132 | 38 | EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError)); |
2133 | 1.23k | } else if (Decl.isType()) { |
2134 | 799 | EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError)); |
2135 | 799 | } else if (Decl.isAlias()) { |
2136 | 179 | EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError)); |
2137 | 166 | const auto &A = Decl.getAlias(); |
2138 | 166 | const auto &Sort = A.getSort(); |
2139 | 166 | if (Sort.isCore()) { |
2140 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
2141 | 166 | } else { |
2142 | 166 | uint32_t NewInstIdx = CompCtx.incSortIndexSize(Sort.getSortType()); |
2143 | | // Outer-aliasing a resource type keeps the resource's identity in this |
2144 | | // scope so later own/borrow and (eq i) checks treat the slot correctly. |
2145 | 166 | if (A.getTargetType() == AST::Component::Alias::TargetType::Outer && |
2146 | 166 | Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
2147 | 166 | CompCtx.carryOuterResource(NewInstIdx, A.getOuter().first, |
2148 | 166 | A.getOuter().second); |
2149 | 166 | } |
2150 | | // When aliasing an instance export, propagate either the source |
2151 | | // instance's export table (for Instance targets) or the source |
2152 | | // export's resource identity (for Type targets that are resources) |
2153 | | // — mirrors the AliasSection handling. |
2154 | 166 | if (A.getTargetType() == AST::Component::Alias::TargetType::Export) { |
2155 | 0 | const auto SrcInstIdx = A.getExport().first; |
2156 | 0 | const auto &SrcName = A.getExport().second; |
2157 | 0 | const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports; |
2158 | 0 | auto It = SrcExports.find(std::string(SrcName)); |
2159 | 0 | if (It != SrcExports.end()) { |
2160 | 0 | if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
2161 | 0 | if (It->second.IT != nullptr) { |
2162 | 0 | populateInstanceFromType(NewInstIdx, *It->second.IT); |
2163 | 0 | } else if (It->second.NestedInstIdx.has_value()) { |
2164 | 0 | const auto &NestedExports = |
2165 | 0 | CompCtx.getInstance(*It->second.NestedInstIdx).Exports; |
2166 | 0 | for (const auto &[Name, IE] : NestedExports) { |
2167 | 0 | CompCtx.addInstanceExport(NewInstIdx, Name, IE.ST, IE.IT, |
2168 | 0 | IE.NestedInstIdx, IE.ResourceId); |
2169 | 0 | } |
2170 | 0 | } |
2171 | 0 | } else if (Sort.getSortType() == |
2172 | 0 | AST::Component::Sort::SortType::Type && |
2173 | 0 | It->second.ResourceId.has_value()) { |
2174 | 0 | CompCtx.addResource(NewInstIdx, {*It->second.ResourceId, |
2175 | 0 | /*LocallyDefined=*/false}); |
2176 | 0 | } |
2177 | 0 | } |
2178 | 0 | } |
2179 | 166 | } |
2180 | 258 | } else if (Decl.isExportDecl()) { |
2181 | 258 | EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError)); |
2182 | 258 | } else { |
2183 | 0 | assumingUnreachable(); |
2184 | 0 | } |
2185 | 1.20k | return {}; |
2186 | 1.27k | } |
2187 | | |
2188 | | Expect<void> |
2189 | 1.48k | Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept { |
2190 | 1.48k | auto ReportError = [](auto E) { |
2191 | 45 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component)); |
2192 | 45 | return E; |
2193 | 45 | }; |
2194 | | |
2195 | 1.48k | if (Decl.isImportDecl()) { |
2196 | 694 | EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError)); |
2197 | 790 | } else if (Decl.isInstanceDecl()) { |
2198 | 790 | EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError)); |
2199 | 790 | } else { |
2200 | 0 | assumingUnreachable(); |
2201 | 0 | } |
2202 | 1.43k | return {}; |
2203 | 1.48k | } |
2204 | | |
2205 | 23.8k | Expect<void> Validator::validate(const ComponentValType &VT) noexcept { |
2206 | 23.8k | if (VT.getCode() == ComponentTypeCode::TypeIndex) { |
2207 | 19.2k | uint32_t Idx = VT.getTypeIndex(); |
2208 | 19.2k | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2209 | 80 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2210 | 80 | spdlog::error(" ComponentValType: type index {} out of bounds"sv, Idx); |
2211 | 80 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2212 | 80 | } |
2213 | 19.1k | const auto *DT = CompCtx.getDefType(Idx); |
2214 | 19.1k | if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) { |
2215 | 13 | spdlog::error(ErrCode::Value::NotADefinedType); |
2216 | 13 | spdlog::error( |
2217 | 13 | " ComponentValType: type index {} is not a defined value type"sv, |
2218 | 13 | Idx); |
2219 | 13 | return Unexpect(ErrCode::Value::NotADefinedType); |
2220 | 13 | } |
2221 | 19.1k | } |
2222 | 23.7k | return {}; |
2223 | 23.8k | } |
2224 | | |
2225 | | Expect<void> |
2226 | 86.5k | Validator::validate(const AST::Component::DefValType &DVT) noexcept { |
2227 | 86.5k | if (DVT.isOwnTy()) { |
2228 | 565 | uint32_t Idx = DVT.getOwn().Idx; |
2229 | 565 | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2230 | 34 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2231 | 34 | spdlog::error(" DefValType: own type index {} out of bounds"sv, Idx); |
2232 | 34 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2233 | 34 | } |
2234 | 531 | if (CompCtx.getResource(Idx) == nullptr) { |
2235 | 53 | spdlog::error(ErrCode::Value::NotADefinedType); |
2236 | 53 | spdlog::error( |
2237 | 53 | " DefValType: own type index {} does not refer to a resource type"sv, |
2238 | 53 | Idx); |
2239 | 53 | return Unexpect(ErrCode::Value::NotADefinedType); |
2240 | 53 | } |
2241 | 85.9k | } else if (DVT.isBorrowTy()) { |
2242 | 692 | uint32_t Idx = DVT.getBorrow().Idx; |
2243 | 692 | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2244 | 39 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2245 | 39 | spdlog::error(" DefValType: borrow type index {} out of bounds"sv, |
2246 | 39 | Idx); |
2247 | 39 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2248 | 39 | } |
2249 | 653 | if (CompCtx.getResource(Idx) == nullptr) { |
2250 | 18 | spdlog::error(ErrCode::Value::NotADefinedType); |
2251 | 18 | spdlog::error( |
2252 | 18 | " DefValType: borrow type index {} does not refer to a resource type"sv, |
2253 | 18 | Idx); |
2254 | 18 | return Unexpect(ErrCode::Value::NotADefinedType); |
2255 | 18 | } |
2256 | 85.2k | } else if (DVT.isRecordTy()) { |
2257 | 1.03k | const auto &Rec = DVT.getRecord(); |
2258 | 1.03k | if (Rec.LabelTypes.empty()) { |
2259 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2260 | 0 | spdlog::error(" DefValType: record must have at least one field"sv); |
2261 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2262 | 0 | } |
2263 | 1.03k | std::unordered_set<std::string> Seen; |
2264 | 1.04k | for (const auto < : Rec.LabelTypes) { |
2265 | 1.04k | if (LT.getLabel().empty()) { |
2266 | 3 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2267 | 3 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2268 | 3 | } |
2269 | 1.04k | if (!isKebabString(LT.getLabel())) { |
2270 | 3 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2271 | 3 | spdlog::error( |
2272 | 3 | " DefValType: record field '{}' is not valid kebab-case"sv, |
2273 | 3 | LT.getLabel()); |
2274 | 3 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2275 | 3 | } |
2276 | 1.03k | if (!Seen.insert(toLowerStr(LT.getLabel())).second) { |
2277 | 1 | spdlog::error(ErrCode::Value::RecordFieldNameConflicts); |
2278 | 1 | spdlog::error(" DefValType: duplicate record field '{}'"sv, |
2279 | 1 | LT.getLabel()); |
2280 | 1 | return Unexpect(ErrCode::Value::RecordFieldNameConflicts); |
2281 | 1 | } |
2282 | 1.03k | EXPECTED_TRY(validate(LT.getValType())); |
2283 | 1.03k | } |
2284 | 84.2k | } else if (DVT.isVariantTy()) { |
2285 | 637 | const auto &Var = DVT.getVariant(); |
2286 | 637 | if (Var.Cases.empty()) { |
2287 | 2 | spdlog::error(ErrCode::Value::VariantMustHaveCase); |
2288 | 2 | return Unexpect(ErrCode::Value::VariantMustHaveCase); |
2289 | 2 | } |
2290 | 635 | std::unordered_set<std::string> Seen; |
2291 | 635 | for (const auto &C : Var.Cases) { |
2292 | 635 | if (C.first.empty()) { |
2293 | 1 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2294 | 1 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2295 | 1 | } |
2296 | 634 | if (!isKebabString(C.first)) { |
2297 | 4 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2298 | 4 | spdlog::error( |
2299 | 4 | " DefValType: variant case '{}' is not valid kebab-case"sv, |
2300 | 4 | C.first); |
2301 | 4 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2302 | 4 | } |
2303 | 630 | if (!Seen.insert(toLowerStr(C.first)).second) { |
2304 | 0 | spdlog::error(ErrCode::Value::VariantCaseNameConflicts); |
2305 | 0 | spdlog::error(" DefValType: duplicate variant case '{}'"sv, C.first); |
2306 | 0 | return Unexpect(ErrCode::Value::VariantCaseNameConflicts); |
2307 | 0 | } |
2308 | 630 | if (C.second.has_value()) { |
2309 | 551 | EXPECTED_TRY(validate(*C.second)); |
2310 | 551 | } |
2311 | 630 | } |
2312 | 83.6k | } else if (DVT.isTupleTy()) { |
2313 | 867 | if (DVT.getTuple().Types.empty()) { |
2314 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2315 | 0 | spdlog::error(" DefValType: tuple must have at least one element"sv); |
2316 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2317 | 0 | } |
2318 | 978 | for (const auto &T : DVT.getTuple().Types) { |
2319 | 978 | EXPECTED_TRY(validate(T)); |
2320 | 978 | } |
2321 | 82.7k | } else if (DVT.isListTy()) { |
2322 | 2.08k | EXPECTED_TRY(validate(DVT.getList().ValTy)); |
2323 | 80.6k | } else if (DVT.isOptionTy()) { |
2324 | 617 | EXPECTED_TRY(validate(DVT.getOption().ValTy)); |
2325 | 80.0k | } else if (DVT.isResultTy()) { |
2326 | 2.74k | const auto &R = DVT.getResult(); |
2327 | 2.74k | if (R.ValTy.has_value()) { |
2328 | 1.01k | EXPECTED_TRY(validate(*R.ValTy)); |
2329 | 1.01k | } |
2330 | 2.74k | if (R.ErrTy.has_value()) { |
2331 | 1.72k | EXPECTED_TRY(validate(*R.ErrTy)); |
2332 | 1.72k | } |
2333 | 77.3k | } else if (DVT.isFlagsTy()) { |
2334 | 114 | const auto &Flags = DVT.getFlags(); |
2335 | 114 | if (Flags.Labels.empty()) { |
2336 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2337 | 0 | spdlog::error(" DefValType: flags must have at least one label"sv); |
2338 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2339 | 0 | } |
2340 | 114 | if (Flags.Labels.size() > 32) { |
2341 | 0 | spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags); |
2342 | 0 | return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags); |
2343 | 0 | } |
2344 | 114 | std::unordered_set<std::string> Seen; |
2345 | 118 | for (const auto &L : Flags.Labels) { |
2346 | 118 | if (L.empty()) { |
2347 | 3 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2348 | 3 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2349 | 3 | } |
2350 | 115 | if (!isKebabString(L)) { |
2351 | 14 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2352 | 14 | spdlog::error( |
2353 | 14 | " DefValType: flags label '{}' is not valid kebab-case"sv, L); |
2354 | 14 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2355 | 14 | } |
2356 | 101 | if (!Seen.insert(toLowerStr(L)).second) { |
2357 | 2 | spdlog::error(ErrCode::Value::FlagNameConflicts); |
2358 | 2 | spdlog::error(" DefValType: duplicate flags label '{}'"sv, L); |
2359 | 2 | return Unexpect(ErrCode::Value::FlagNameConflicts); |
2360 | 2 | } |
2361 | 101 | } |
2362 | 77.1k | } else if (DVT.isEnumTy()) { |
2363 | 994 | const auto &Enm = DVT.getEnum(); |
2364 | 994 | if (Enm.Labels.empty()) { |
2365 | 3 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2366 | 3 | spdlog::error(" DefValType: enum must have at least one label"sv); |
2367 | 3 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2368 | 3 | } |
2369 | 991 | std::unordered_set<std::string> Seen; |
2370 | 997 | for (const auto &L : Enm.Labels) { |
2371 | 997 | if (L.empty()) { |
2372 | 3 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2373 | 3 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2374 | 3 | } |
2375 | 994 | if (!isKebabString(L)) { |
2376 | 6 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2377 | 6 | spdlog::error( |
2378 | 6 | " DefValType: enum label '{}' is not valid kebab-case"sv, L); |
2379 | 6 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2380 | 6 | } |
2381 | 988 | if (!Seen.insert(toLowerStr(L)).second) { |
2382 | 1 | spdlog::error(ErrCode::Value::EnumTagNameConflicts); |
2383 | 1 | spdlog::error(" DefValType: duplicate enum label '{}'"sv, L); |
2384 | 1 | return Unexpect(ErrCode::Value::EnumTagNameConflicts); |
2385 | 1 | } |
2386 | 988 | } |
2387 | 76.1k | } else if (DVT.isStreamTy()) { |
2388 | 848 | if (DVT.getStream().ValTy.has_value()) { |
2389 | 422 | EXPECTED_TRY(validate(*DVT.getStream().ValTy)); |
2390 | 422 | } |
2391 | 75.3k | } else if (DVT.isFutureTy()) { |
2392 | 593 | if (DVT.getFuture().ValTy.has_value()) { |
2393 | 241 | EXPECTED_TRY(validate(*DVT.getFuture().ValTy)); |
2394 | 241 | } |
2395 | 593 | } |
2396 | 86.2k | return {}; |
2397 | 86.5k | } |
2398 | | |
2399 | 11.7k | Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept { |
2400 | | // Validate param names: kebab-case + unique |
2401 | 11.7k | std::unordered_set<std::string_view> ParamNames; |
2402 | 11.7k | for (const auto &P : FT.getParamList()) { |
2403 | 4.30k | if (!P.getLabel().empty()) { |
2404 | 2.17k | if (!isKebabString(P.getLabel())) { |
2405 | 12 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2406 | 12 | spdlog::error( |
2407 | 12 | " FuncType: parameter name '{}' is not valid kebab-case"sv, |
2408 | 12 | P.getLabel()); |
2409 | 12 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2410 | 12 | } |
2411 | 2.16k | if (!ParamNames.insert(P.getLabel()).second) { |
2412 | 1 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
2413 | 1 | spdlog::error(" FuncType: duplicate parameter name '{}'"sv, |
2414 | 1 | P.getLabel()); |
2415 | 1 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
2416 | 1 | } |
2417 | 2.16k | } |
2418 | 4.28k | EXPECTED_TRY(validate(P.getValType())); |
2419 | 4.28k | } |
2420 | | // Reject transitive use of borrow in results |
2421 | 11.6k | for (const auto &R : FT.getResultList()) { |
2422 | 10.8k | EXPECTED_TRY(validate(R.getValType())); |
2423 | 10.8k | if (containsBorrow(R.getValType())) { |
2424 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2425 | 0 | spdlog::error( |
2426 | 0 | " FuncType: borrow type not allowed in function results"sv); |
2427 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2428 | 0 | } |
2429 | 10.8k | } |
2430 | 11.6k | return {}; |
2431 | 11.6k | } |
2432 | | |
2433 | | Expect<void> |
2434 | 535k | Validator::validate(const AST::Component::InstanceType &IT) noexcept { |
2435 | | // Instance types are validated with an initially-empty index space. |
2436 | 535k | CompCtx.enterTypeDefinition(); |
2437 | 535k | for (const auto &Decl : IT.getDecl()) { |
2438 | 484 | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
2439 | 484 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
2440 | 484 | return E; |
2441 | 484 | })); |
2442 | 484 | } |
2443 | 535k | CompCtx.exitComponent(); |
2444 | 535k | return {}; |
2445 | 535k | } |
2446 | | |
2447 | | Expect<void> |
2448 | 1.12M | Validator::validate(const AST::Component::ComponentType &CT) noexcept { |
2449 | | // Component types are validated with an initially-empty index space. |
2450 | 1.12M | CompCtx.enterTypeDefinition(); |
2451 | 1.12M | for (const auto &Decl : CT.getDecl()) { |
2452 | 1.48k | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
2453 | 1.48k | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
2454 | 1.48k | return E; |
2455 | 1.48k | })); |
2456 | 1.48k | } |
2457 | 1.12M | CompCtx.exitComponent(); |
2458 | 1.12M | return {}; |
2459 | 1.12M | } |
2460 | | |
2461 | | Expect<void> |
2462 | 21.0k | Validator::validate(const AST::Component::ResourceType &RT) noexcept { |
2463 | | // Resource types are not allowed inside componenttype/instancetype scopes. |
2464 | 21.0k | if (CompCtx.isTypeDefinitionScope()) { |
2465 | 1 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2466 | 1 | spdlog::error(" ResourceType: resource types cannot be defined inside " |
2467 | 1 | "componenttype or instancetype"sv); |
2468 | 1 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2469 | 1 | } |
2470 | 21.0k | if (RT.getDestructor().has_value()) { |
2471 | 1.12k | uint32_t DtorIdx = *RT.getDestructor(); |
2472 | 1.12k | if (DtorIdx >= CompCtx.getCoreSortIndexSize( |
2473 | 1.12k | AST::Component::Sort::CoreSortType::Func)) { |
2474 | 9 | spdlog::error(ErrCode::Value::InvalidIndex); |
2475 | 9 | spdlog::error( |
2476 | 9 | " ResourceType: destructor core func index {} out of bounds"sv, |
2477 | 9 | DtorIdx); |
2478 | 9 | return Unexpect(ErrCode::Value::InvalidIndex); |
2479 | 9 | } |
2480 | | // Verify the destructor's signature is `[i32] -> []`. The signature is |
2481 | | // only available for core funcs the validator has populated (canon |
2482 | | // resource.* synthesise it; aliased-from-core-module funcs are tracked |
2483 | | // as a TODO that needs CoreInstance export-type plumbing). Skip the |
2484 | | // check when the SubType pointer is null — the bounds check above |
2485 | | // already caught the obvious "no such func" mistake. |
2486 | | // TODO: also accept `[i64] -> []` when the resource rep is i64 |
2487 | | // (memory64 proposal); needs ResourceType to carry rep size. |
2488 | 1.12k | const AST::SubType *DtorST = CompCtx.getCoreFunc(DtorIdx); |
2489 | 1.12k | if (DtorST != nullptr) { |
2490 | 966 | const auto &DtorType = DtorST->getCompositeType(); |
2491 | 966 | const auto &ExpType = CoreFuncType_I32_Void.getCompositeType(); |
2492 | 966 | if (!DtorType.isFunc() || |
2493 | 966 | DtorType.getFuncType() != ExpType.getFuncType()) { |
2494 | 1 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2495 | 1 | spdlog::error( |
2496 | 1 | " ResourceType: destructor core func {} must have signature [i32] -> []"sv, |
2497 | 1 | DtorIdx); |
2498 | 1 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2499 | 1 | } |
2500 | 966 | } |
2501 | 1.12k | } |
2502 | 21.0k | return {}; |
2503 | 21.0k | } |
2504 | | |
2505 | 23.9k | bool Validator::containsBorrow(const ComponentValType &VT) const noexcept { |
2506 | 23.9k | if (VT.getCode() == ComponentTypeCode::Borrow) { |
2507 | 0 | return true; |
2508 | 0 | } |
2509 | 23.9k | if (VT.getCode() != ComponentTypeCode::TypeIndex) { |
2510 | 6.36k | return false; |
2511 | 6.36k | } |
2512 | 17.6k | uint32_t Idx = VT.getTypeIndex(); |
2513 | 17.6k | const auto *DT = CompCtx.getDefType(Idx); |
2514 | 17.6k | if (DT == nullptr || !DT->isDefValType()) { |
2515 | 2.72k | return false; |
2516 | 2.72k | } |
2517 | 14.9k | return containsBorrow(DT->getDefValType()); |
2518 | 17.6k | } |
2519 | | |
2520 | | bool Validator::containsBorrow( |
2521 | 14.9k | const AST::Component::DefValType &DVT) const noexcept { |
2522 | 14.9k | if (DVT.isBorrowTy()) { |
2523 | 0 | return true; |
2524 | 0 | } |
2525 | 14.9k | if (DVT.isRecordTy()) { |
2526 | 2.90k | for (const auto &F : DVT.getRecord().LabelTypes) { |
2527 | 2.90k | if (containsBorrow(F.getValType())) { |
2528 | 0 | return true; |
2529 | 0 | } |
2530 | 2.90k | } |
2531 | 2.90k | return false; |
2532 | 2.90k | } |
2533 | 11.9k | if (DVT.isVariantTy()) { |
2534 | 2.04k | for (const auto &C : DVT.getVariant().Cases) { |
2535 | 2.04k | if (C.second.has_value() && containsBorrow(*C.second)) { |
2536 | 0 | return true; |
2537 | 0 | } |
2538 | 2.04k | } |
2539 | 2.04k | return false; |
2540 | 2.04k | } |
2541 | 9.95k | if (DVT.isListTy()) { |
2542 | 1.30k | return containsBorrow(DVT.getList().ValTy); |
2543 | 1.30k | } |
2544 | 8.64k | if (DVT.isTupleTy()) { |
2545 | 1.15k | for (const auto &T : DVT.getTuple().Types) { |
2546 | 1.15k | if (containsBorrow(T)) { |
2547 | 0 | return true; |
2548 | 0 | } |
2549 | 1.15k | } |
2550 | 1.05k | return false; |
2551 | 1.05k | } |
2552 | 7.59k | if (DVT.isOptionTy()) { |
2553 | 68 | return containsBorrow(DVT.getOption().ValTy); |
2554 | 68 | } |
2555 | 7.52k | if (DVT.isResultTy()) { |
2556 | 5.47k | const auto &R = DVT.getResult(); |
2557 | 5.47k | return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) || |
2558 | 5.47k | (R.ErrTy.has_value() && containsBorrow(*R.ErrTy)); |
2559 | 5.47k | } |
2560 | 2.04k | if (DVT.isStreamTy()) { |
2561 | 334 | return DVT.getStream().ValTy.has_value() && |
2562 | 194 | containsBorrow(*DVT.getStream().ValTy); |
2563 | 334 | } |
2564 | 1.71k | if (DVT.isFutureTy()) { |
2565 | 138 | return DVT.getFuture().ValTy.has_value() && |
2566 | 66 | containsBorrow(*DVT.getFuture().ValTy); |
2567 | 138 | } |
2568 | 1.57k | return false; // PrimValType, OwnTy, FlagsTy, EnumTy |
2569 | 1.71k | } |
2570 | | |
2571 | | } // namespace Validator |
2572 | | } // namespace WasmEdge |