/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 | 0 | std::string toLowerStr(std::string_view SV) { |
20 | 0 | std::string Result(SV); |
21 | 0 | std::transform( |
22 | 0 | Result.begin(), Result.end(), Result.begin(), |
23 | 0 | [](unsigned char C) { return static_cast<char>(std::tolower(C)); }); |
24 | 0 | return Result; |
25 | 0 | } |
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 | 0 | descTypeToSortType(AST::Component::ExternDesc::DescType DT) noexcept { |
33 | 0 | switch (DT) { |
34 | 0 | case AST::Component::ExternDesc::DescType::CoreType: |
35 | 0 | return std::nullopt; |
36 | 0 | case AST::Component::ExternDesc::DescType::FuncType: |
37 | 0 | return AST::Component::Sort::SortType::Func; |
38 | 0 | case AST::Component::ExternDesc::DescType::ValueBound: |
39 | 0 | return AST::Component::Sort::SortType::Value; |
40 | 0 | case AST::Component::ExternDesc::DescType::TypeBound: |
41 | 0 | return AST::Component::Sort::SortType::Type; |
42 | 0 | case AST::Component::ExternDesc::DescType::ComponentType: |
43 | 0 | return AST::Component::Sort::SortType::Component; |
44 | 0 | case AST::Component::ExternDesc::DescType::InstanceType: |
45 | 0 | return AST::Component::Sort::SortType::Instance; |
46 | 0 | default: |
47 | 0 | assumingUnreachable(); |
48 | 0 | } |
49 | 0 | } |
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 | 0 | AST::Component::ExternDesc::DescType DT) noexcept { |
58 | 0 | auto Mapped = descTypeToSortType(DT); |
59 | 0 | if (S.isCore()) { |
60 | 0 | return !Mapped.has_value() && |
61 | 0 | S.getCoreSortType() == AST::Component::Sort::CoreSortType::Module; |
62 | 0 | } |
63 | 0 | return Mapped.has_value() && S.getSortType() == *Mapped; |
64 | 0 | } |
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 | 0 | Expect<ComponentName> validateExportName(std::string_view Name) noexcept { |
143 | 0 | 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 | 0 | EXPECTED_TRY(ComponentName CName, ComponentName::parse(Name)); |
149 | 0 | switch (CName.getKind()) { |
150 | 0 | case ComponentNameKind::Label: |
151 | 0 | case ComponentNameKind::Constructor: |
152 | 0 | case ComponentNameKind::Method: |
153 | 0 | case ComponentNameKind::Static: |
154 | 0 | case ComponentNameKind::InterfaceType: |
155 | 0 | return CName; |
156 | 0 | default: |
157 | 0 | spdlog::error(ErrCode::Value::InvalidExportName); |
158 | 0 | spdlog::error(" Export name '{}' kind is not valid for exports"sv, Name); |
159 | 0 | return Unexpect(ErrCode::Value::InvalidExportName); |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | } // namespace |
164 | | |
165 | | void Validator::populateInstanceFromType( |
166 | 0 | uint32_t InstIdx, const AST::Component::InstanceType &IT) noexcept { |
167 | 0 | for (const auto &Decl : IT.getDecl()) { |
168 | 0 | if (!Decl.isExportDecl()) { |
169 | 0 | continue; |
170 | 0 | } |
171 | 0 | const auto &Exp = Decl.getExport(); |
172 | 0 | const auto &ED = Exp.getExternDesc(); |
173 | 0 | auto ST = descTypeToSortType(ED.getDescType()); |
174 | 0 | 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 | 0 | const AST::Component::InstanceType *NestedIT = nullptr; |
184 | 0 | if (ED.getDescType() == |
185 | 0 | 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 | 0 | std::optional<uint64_t> ResourceId; |
196 | 0 | if (ED.getDescType() == AST::Component::ExternDesc::DescType::TypeBound && |
197 | 0 | !ED.isEqType()) { |
198 | 0 | ResourceId = CompCtx.allocateFreshResourceId(); |
199 | 0 | } |
200 | 0 | CompCtx.addInstanceExport(InstIdx, Exp.getName(), *ST, NestedIT, |
201 | 0 | /*NestedInstIdx=*/std::nullopt, ResourceId); |
202 | 0 | } |
203 | 0 | } |
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 | 0 | Validator::validate(const AST::Component::Component &Comp) noexcept { |
306 | 0 | spdlog::warn("Component Model Validation is in active development."sv); |
307 | 0 | CompCtx.reset(); |
308 | 0 | return validateComponent(Comp).and_then([&]() { |
309 | 0 | const_cast<AST::Component::Component &>(Comp).setIsValidated(); |
310 | 0 | return Expect<void>{}; |
311 | 0 | }); |
312 | 0 | } |
313 | | |
314 | | Expect<void> |
315 | 0 | 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 | 0 | auto ReportError = [](auto E) { |
322 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Component)); |
323 | 0 | return E; |
324 | 0 | }; |
325 | |
|
326 | 0 | CompCtx.enterComponent(&Comp); |
327 | 0 | for (const auto &Sec : Comp.getSections()) { |
328 | 0 | auto Func = [&](auto &&S) -> Expect<void> { |
329 | 0 | using T = std::decay_t<decltype(S)>; |
330 | 0 | if constexpr (std::is_same_v<T, AST::CustomSection>) { |
331 | | // Always pass validation. |
332 | 0 | } else { |
333 | 0 | EXPECTED_TRY(validate(S).map_error(ReportError)); |
334 | 0 | } |
335 | 0 | return {}; |
336 | 0 | }; Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 Unexecuted instantiation: 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 |
337 | 0 | EXPECTED_TRY(std::visit(Func, Sec)); |
338 | 0 | } |
339 | 0 | CompCtx.exitComponent(); |
340 | 0 | return {}; |
341 | 0 | } |
342 | | |
343 | | Expect<void> |
344 | 0 | Validator::validate(const AST::Component::CoreModuleSection &ModSec) noexcept { |
345 | 0 | EXPECTED_TRY(validate(ModSec.getContent()).map_error([](auto E) { |
346 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreMod)); |
347 | 0 | return E; |
348 | 0 | })); |
349 | 0 | const_cast<AST::Module &>(ModSec.getContent()).setIsValidated(); |
350 | 0 | CompCtx.addCoreModule(ModSec.getContent()); |
351 | 0 | return {}; |
352 | 0 | } |
353 | | |
354 | | Expect<void> Validator::validate( |
355 | 0 | const AST::Component::CoreInstanceSection &InstSec) noexcept { |
356 | 0 | for (const auto &Inst : InstSec.getContent()) { |
357 | 0 | EXPECTED_TRY(validate(Inst).map_error([](auto E) { |
358 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreInstance)); |
359 | 0 | return E; |
360 | 0 | })); |
361 | 0 | } |
362 | 0 | return {}; |
363 | 0 | } |
364 | | |
365 | | Expect<void> |
366 | 0 | Validator::validate(const AST::Component::CoreTypeSection &TypeSec) noexcept { |
367 | 0 | for (const auto &Type : TypeSec.getContent()) { |
368 | 0 | EXPECTED_TRY(validate(Type).map_error([](auto E) { |
369 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_CoreType)); |
370 | 0 | return E; |
371 | 0 | })); |
372 | 0 | } |
373 | 0 | return {}; |
374 | 0 | } |
375 | | |
376 | | Expect<void> |
377 | 0 | Validator::validate(const AST::Component::ComponentSection &CompSec) noexcept { |
378 | 0 | EXPECTED_TRY(validateComponent(CompSec.getContent()).map_error([](auto E) { |
379 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Component)); |
380 | 0 | return E; |
381 | 0 | })); |
382 | 0 | CompCtx.addComponent(CompSec.getContent()); |
383 | 0 | return {}; |
384 | 0 | } |
385 | | |
386 | | Expect<void> |
387 | 0 | Validator::validate(const AST::Component::InstanceSection &InstSec) noexcept { |
388 | 0 | for (const auto &Inst : InstSec.getContent()) { |
389 | 0 | EXPECTED_TRY(validate(Inst).map_error([](auto E) { |
390 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Instance)); |
391 | 0 | return E; |
392 | 0 | })); |
393 | 0 | } |
394 | 0 | return {}; |
395 | 0 | } |
396 | | |
397 | | Expect<void> |
398 | 0 | Validator::validate(const AST::Component::AliasSection &AliasSec) noexcept { |
399 | 0 | for (const auto &Alias : AliasSec.getContent()) { |
400 | 0 | EXPECTED_TRY(validate(Alias).map_error([](auto E) { |
401 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Alias)); |
402 | 0 | return E; |
403 | 0 | })); |
404 | 0 | const auto &Sort = Alias.getSort(); |
405 | 0 | const bool IsOuter = |
406 | 0 | Alias.getTargetType() == AST::Component::Alias::TargetType::Outer; |
407 | 0 | if (Sort.isCore()) { |
408 | 0 | uint32_t NewCoreIdx = |
409 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
410 | | // Carry the outer-aliased module's slot so the alias stays enumerable |
411 | | // when instantiated. |
412 | 0 | if (IsOuter && Sort.getCoreSortType() == |
413 | 0 | AST::Component::Sort::CoreSortType::Module) { |
414 | 0 | CompCtx.carryOuterCoreModule(NewCoreIdx, Alias.getOuter().first, |
415 | 0 | Alias.getOuter().second); |
416 | 0 | } |
417 | 0 | } else { |
418 | 0 | uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType()); |
419 | | // Component analogue of the outer core-module carry above. |
420 | 0 | if (IsOuter && |
421 | 0 | Sort.getSortType() == AST::Component::Sort::SortType::Component) { |
422 | 0 | CompCtx.carryOuterComponent(NewIdx, Alias.getOuter().first, |
423 | 0 | Alias.getOuter().second); |
424 | 0 | } |
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 | 0 | if (IsOuter && |
428 | 0 | Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
429 | 0 | CompCtx.carryOuterResource(NewIdx, Alias.getOuter().first, |
430 | 0 | Alias.getOuter().second); |
431 | 0 | } |
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 | 0 | if (Alias.getTargetType() == AST::Component::Alias::TargetType::Export) { |
437 | 0 | const auto SrcInstIdx = Alias.getExport().first; |
438 | 0 | const auto &SrcName = Alias.getExport().second; |
439 | 0 | const auto &SrcExports = CompCtx.getInstance(SrcInstIdx).Exports; |
440 | 0 | auto It = SrcExports.find(std::string(SrcName)); |
441 | 0 | if (It != SrcExports.end()) { |
442 | 0 | if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
443 | 0 | if (It->second.IT != nullptr) { |
444 | 0 | populateInstanceFromType(NewIdx, *It->second.IT); |
445 | 0 | } else if (It->second.NestedInstIdx.has_value()) { |
446 | 0 | const auto &NestedExports = |
447 | 0 | CompCtx.getInstance(*It->second.NestedInstIdx).Exports; |
448 | 0 | for (const auto &[Name, IE] : NestedExports) { |
449 | 0 | CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT, |
450 | 0 | IE.NestedInstIdx, IE.ResourceId); |
451 | 0 | } |
452 | 0 | } |
453 | 0 | } 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 | 0 | } |
461 | 0 | } |
462 | 0 | } |
463 | 0 | } |
464 | 0 | return {}; |
465 | 0 | } |
466 | | |
467 | | Expect<void> |
468 | 0 | Validator::validate(const AST::Component::TypeSection &TypeSec) noexcept { |
469 | 0 | for (const auto &Type : TypeSec.getContent()) { |
470 | 0 | EXPECTED_TRY(validate(Type).map_error([](auto E) { |
471 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Type)); |
472 | 0 | return E; |
473 | 0 | })); |
474 | 0 | } |
475 | 0 | return {}; |
476 | 0 | } |
477 | | |
478 | | Expect<void> |
479 | 0 | Validator::validate(const AST::Component::CanonSection &CanonSec) noexcept { |
480 | 0 | for (const auto &C : CanonSec.getContent()) { |
481 | 0 | EXPECTED_TRY(validate(C).map_error([](auto E) { |
482 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Canon)); |
483 | 0 | return E; |
484 | 0 | })); |
485 | 0 | } |
486 | 0 | return {}; |
487 | 0 | } |
488 | | |
489 | | Expect<void> |
490 | 0 | 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 | 0 | const auto &Start = StartSec.getContent(); |
503 | | |
504 | | // 1. Function index bounds. |
505 | 0 | const uint32_t FuncIdx = Start.getFunctionIndex(); |
506 | 0 | const uint32_t FuncSpaceSize = |
507 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func); |
508 | 0 | if (FuncIdx >= FuncSpaceSize) { |
509 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
510 | 0 | spdlog::error( |
511 | 0 | " Start: function index {} exceeds func index space size {}"sv, |
512 | 0 | FuncIdx, FuncSpaceSize); |
513 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
514 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
515 | 0 | } |
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 | 0 | const AST::Component::FuncType *FT = CompCtx.getFunc(FuncIdx); |
521 | 0 | if (FT != nullptr) { |
522 | 0 | const auto Args = Start.getArguments(); |
523 | 0 | const auto &ParamList = FT->getParamList(); |
524 | 0 | if (Args.size() != ParamList.size()) { |
525 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
526 | 0 | spdlog::error( |
527 | 0 | " Start: argument count {} does not match func {} param arity {}"sv, |
528 | 0 | Args.size(), FuncIdx, ParamList.size()); |
529 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
530 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
531 | 0 | } |
532 | 0 | const uint32_t ResultArity = |
533 | 0 | static_cast<uint32_t>(FT->getResultList().size()); |
534 | 0 | if (Start.getResult() != ResultArity) { |
535 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
536 | 0 | spdlog::error( |
537 | 0 | " Start: declared result count {} does not match func {} result arity {}"sv, |
538 | 0 | Start.getResult(), FuncIdx, ResultArity); |
539 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
540 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
541 | 0 | } |
542 | 0 | } |
543 | | |
544 | | // 3. Argument indices must be in value index space bounds. Per-arg |
545 | | // consume-once tracking is deferred (GAP-EX-4). |
546 | 0 | const uint32_t ValueSpaceSize = |
547 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Value); |
548 | 0 | for (const uint32_t ArgIdx : Start.getArguments()) { |
549 | 0 | if (ArgIdx >= ValueSpaceSize) { |
550 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
551 | 0 | spdlog::error( |
552 | 0 | " Start: argument value index {} exceeds value index space size {}"sv, |
553 | 0 | ArgIdx, ValueSpaceSize); |
554 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Start)); |
555 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | | // 4. Append result values to the value index space. |
560 | 0 | for (uint32_t I = 0; I < Start.getResult(); ++I) { |
561 | 0 | CompCtx.addValue(); |
562 | 0 | } |
563 | 0 | return {}; |
564 | 0 | } |
565 | | |
566 | | Expect<void> |
567 | 0 | Validator::validate(const AST::Component::ImportSection &ImpSec) noexcept { |
568 | 0 | for (const auto &Imp : ImpSec.getContent()) { |
569 | 0 | EXPECTED_TRY(validate(Imp).map_error([](auto E) { |
570 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Import)); |
571 | 0 | return E; |
572 | 0 | })); |
573 | 0 | } |
574 | 0 | return {}; |
575 | 0 | } |
576 | | |
577 | | Expect<void> |
578 | 0 | Validator::validate(const AST::Component::ExportSection &ExpSec) noexcept { |
579 | 0 | for (const auto &Exp : ExpSec.getContent()) { |
580 | 0 | EXPECTED_TRY(validate(Exp).map_error([](auto E) { |
581 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Sec_Export)); |
582 | 0 | return E; |
583 | 0 | })); |
584 | 0 | } |
585 | 0 | return {}; |
586 | 0 | } |
587 | | |
588 | | Expect<void> |
589 | 0 | Validator::validate(const AST::Component::CoreInstance &Inst) noexcept { |
590 | 0 | if (Inst.isInstantiateModule()) { |
591 | | // Instantiate module case. |
592 | | |
593 | | // Check the module index bound first. |
594 | 0 | const uint32_t ModIdx = Inst.getModuleIndex(); |
595 | 0 | if (ModIdx >= CompCtx.getCoreSortIndexSize( |
596 | 0 | AST::Component::Sort::CoreSortType::Module)) { |
597 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
598 | 0 | spdlog::error( |
599 | 0 | " CoreInstance: Module index {} exceeds available core modules {}"sv, |
600 | 0 | ModIdx, |
601 | 0 | CompCtx.getCoreSortIndexSize( |
602 | 0 | AST::Component::Sort::CoreSortType::Module)); |
603 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
604 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
605 | 0 | } |
606 | | // Reject duplicate argument names on an instantiate expression. The |
607 | | // spec requires argument names to be strongly-unique per instantiation. |
608 | 0 | { |
609 | 0 | std::unordered_set<std::string_view> SeenArgs; |
610 | 0 | 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 | 0 | } |
620 | | |
621 | | // Imports + exports come from the raw Module (inline) or the |
622 | | // CoreModuleType (imported / aliased) — GAP-CI-1. |
623 | 0 | const auto &CoreModSlot = CompCtx.getCoreModule(ModIdx); |
624 | 0 | const auto *Mod = CoreModSlot.Body; |
625 | 0 | const auto *ModTy = CoreModSlot.Type; |
626 | | |
627 | | // Required arg module-names (one per distinct CoreImportDecl module). |
628 | 0 | std::vector<std::string_view> RequiredArgNames; |
629 | 0 | if (Mod != nullptr) { |
630 | 0 | for (const auto &Import : Mod->getImportSection().getContent()) { |
631 | 0 | RequiredArgNames.push_back(Import.getModuleName()); |
632 | 0 | } |
633 | 0 | } 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 | 0 | auto Args = Inst.getInstantiateArgs(); |
642 | 0 | for (const auto ImportName : RequiredArgNames) { |
643 | 0 | const auto ArgIt = |
644 | 0 | std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) { |
645 | 0 | return Arg.getName() == ImportName; |
646 | 0 | }); |
647 | 0 | if (ArgIt == Args.end()) { |
648 | 0 | spdlog::error(ErrCode::Value::MissingArgument); |
649 | 0 | spdlog::error( |
650 | 0 | " CoreInstance: Module index {} missing argument for import '{}'"sv, |
651 | 0 | Inst.getModuleIndex(), ImportName); |
652 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
653 | 0 | return Unexpect(ErrCode::Value::MissingArgument); |
654 | 0 | } |
655 | 0 | } |
656 | | |
657 | | // Allocate the core:instance and bind exports to it. |
658 | 0 | uint32_t InstanceIdx = CompCtx.addCoreInstance(); |
659 | 0 | if (Mod != nullptr) { |
660 | 0 | for (const auto &ExportDesc : Mod->getExportSection().getContent()) { |
661 | 0 | CompCtx.addCoreInstanceExport(InstanceIdx, ExportDesc.getExternalName(), |
662 | 0 | ExportDesc.getExternalType()); |
663 | 0 | } |
664 | 0 | } 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 | 0 | } else if (Inst.isInlineExport()) { |
689 | | // Inline export case. |
690 | | // Allocate the core instance first, then register each inline export. |
691 | 0 | 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 | 0 | std::unordered_set<std::string_view> SeenExports; |
696 | 0 | for (const auto &Export : Inst.getInlineExports()) { |
697 | 0 | if (!SeenExports.insert(Export.getName()).second) { |
698 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
699 | 0 | spdlog::error(" CoreInstance: Duplicate inline-export name '{}'"sv, |
700 | 0 | Export.getName()); |
701 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
702 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
703 | 0 | } |
704 | 0 | const auto &Sort = Export.getSortIdx().getSort(); |
705 | 0 | uint32_t Idx = Export.getSortIdx().getIdx(); |
706 | 0 | assuming(Sort.isCore()); |
707 | 0 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
708 | | // The error message differs of the tag core sort. |
709 | 0 | ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex; |
710 | 0 | if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) { |
711 | 0 | ErrValue = ErrCode::Value::UnknownCoreTag; |
712 | 0 | } |
713 | 0 | spdlog::error(ErrValue); |
714 | 0 | spdlog::error( |
715 | 0 | " CoreInstance: Inline export '{}' refers to invalid index {}"sv, |
716 | 0 | Export.getName(), Idx); |
717 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
718 | 0 | return Unexpect(ErrValue); |
719 | 0 | } |
720 | | // Map CoreSortType to ExternalType for the instance export map. |
721 | 0 | ExternalType ET; |
722 | 0 | switch (Sort.getCoreSortType()) { |
723 | 0 | case AST::Component::Sort::CoreSortType::Func: |
724 | 0 | ET = ExternalType::Function; |
725 | 0 | 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 | 0 | default: |
739 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
740 | 0 | spdlog::error( |
741 | 0 | " CoreInstance: Inline export '{}' has unsupported core sort"sv, |
742 | 0 | Export.getName()); |
743 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreInstance)); |
744 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
745 | 0 | } |
746 | 0 | CompCtx.addCoreInstanceExport(InstanceIdx, Export.getName(), ET); |
747 | 0 | } |
748 | 0 | } else { |
749 | 0 | assumingUnreachable(); |
750 | 0 | } |
751 | 0 | return {}; |
752 | 0 | } |
753 | | |
754 | | Expect<void> |
755 | 0 | Validator::validate(const AST::Component::Instance &Inst) noexcept { |
756 | 0 | if (Inst.isInstantiateModule()) { |
757 | | // Instantiate module case. |
758 | | |
759 | | // Check the component index bound first. |
760 | 0 | const uint32_t CompIdx = Inst.getComponentIndex(); |
761 | 0 | if (CompIdx >= |
762 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)) { |
763 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
764 | 0 | spdlog::error( |
765 | 0 | " Instance: Component index {} exceeds available components {}"sv, |
766 | 0 | CompIdx, |
767 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Component)); |
768 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
769 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
770 | 0 | } |
771 | | // Reject duplicate argument names on an instantiate expression. The |
772 | | // spec requires argument names to be strongly-unique per instantiation. |
773 | 0 | { |
774 | 0 | std::unordered_set<std::string_view> SeenArgs; |
775 | 0 | for (const auto &Arg : Inst.getInstantiateArgs()) { |
776 | 0 | if (!SeenArgs.insert(Arg.getName()).second) { |
777 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
778 | 0 | spdlog::error(" Instance: Duplicate argument name '{}'"sv, |
779 | 0 | Arg.getName()); |
780 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
781 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
782 | 0 | } |
783 | 0 | } |
784 | 0 | } |
785 | | |
786 | | // Source: raw Component (inline) or ComponentType (imported / aliased). |
787 | 0 | const auto &CompSlot = CompCtx.getComponent(CompIdx); |
788 | 0 | const auto *Comp = CompSlot.Body; |
789 | 0 | const auto *CompTy = CompSlot.Type; |
790 | | |
791 | | // Verify each component import is satisfied by some instantiate arg. |
792 | 0 | auto Args = Inst.getInstantiateArgs(); |
793 | 0 | auto checkImport = |
794 | 0 | [&](std::string_view ImportName, |
795 | 0 | const AST::Component::ExternDesc &ImportDesc) -> Expect<void> { |
796 | 0 | const auto ArgIt = |
797 | 0 | std::find_if(Args.begin(), Args.end(), [&](const auto &Arg) { |
798 | 0 | return Arg.getName() == ImportName; |
799 | 0 | }); |
800 | 0 | if (ArgIt == Args.end()) { |
801 | 0 | spdlog::error(ErrCode::Value::MissingArgument); |
802 | 0 | spdlog::error( |
803 | 0 | " Instance: Component index {} missing argument for import '{}'"sv, |
804 | 0 | Inst.getComponentIndex(), ImportName); |
805 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
806 | 0 | return Unexpect(ErrCode::Value::MissingArgument); |
807 | 0 | } |
808 | 0 | const auto &Sort = ArgIt->getIndex().getSort(); |
809 | 0 | const uint32_t Idx = ArgIt->getIndex().getIdx(); |
810 | | // Only `core module` is admissible as a core-side import externdesc. |
811 | 0 | if (Sort.isCore() && Sort.getCoreSortType() != |
812 | 0 | AST::Component::Sort::CoreSortType::Module) { |
813 | 0 | spdlog::error(ErrCode::Value::ArgTypeMismatch); |
814 | 0 | spdlog::error(" Instance: Argument '{}' uses a core sort other than " |
815 | 0 | "`core module`, which no import externdesc can accept"sv, |
816 | 0 | ImportName); |
817 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
818 | 0 | return Unexpect(ErrCode::Value::ArgTypeMismatch); |
819 | 0 | } |
820 | 0 | if (!sortMatchesDescType(Sort, ImportDesc.getDescType())) { |
821 | 0 | spdlog::error(ErrCode::Value::ArgTypeMismatch); |
822 | 0 | spdlog::error(" Instance: Argument '{}' sort mismatch for import"sv, |
823 | 0 | ImportName); |
824 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
825 | 0 | return Unexpect(ErrCode::Value::ArgTypeMismatch); |
826 | 0 | } |
827 | 0 | 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 | 0 | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
839 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
840 | 0 | spdlog::error( |
841 | 0 | " Instance: Argument '{}' refers to invalid index {}"sv, |
842 | 0 | ImportName, Idx); |
843 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
844 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
845 | 0 | } |
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 | 0 | if (Comp != nullptr && |
850 | 0 | ImportDesc.getDescType() == |
851 | 0 | 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 | 0 | return {}; |
867 | 0 | }; |
868 | 0 | if (Comp != nullptr) { |
869 | 0 | for (const auto &Sec : Comp->getSections()) { |
870 | 0 | if (const auto *IS = std::get_if<AST::Component::ImportSection>(&Sec)) { |
871 | 0 | for (const auto &Imp : IS->getContent()) { |
872 | 0 | EXPECTED_TRY(checkImport(Imp.getName(), Imp.getDesc())); |
873 | 0 | } |
874 | 0 | } |
875 | 0 | } |
876 | 0 | } else if (CompTy != nullptr) { |
877 | 0 | for (const auto &CD : CompTy->getDecl()) { |
878 | 0 | if (CD.isImportDecl()) { |
879 | 0 | const auto &ID = CD.getImport(); |
880 | 0 | EXPECTED_TRY(checkImport(ID.getName(), ID.getExternDesc())); |
881 | 0 | } |
882 | 0 | } |
883 | 0 | } |
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 | 0 | uint32_t InstanceIdx = CompCtx.addInstance(); |
890 | 0 | if (Comp != nullptr) { |
891 | 0 | for (const auto &Sec : Comp->getSections()) { |
892 | 0 | const auto *ES = std::get_if<AST::Component::ExportSection>(&Sec); |
893 | 0 | if (ES == nullptr) { |
894 | 0 | continue; |
895 | 0 | } |
896 | 0 | 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 | 0 | } |
913 | 0 | } else if (CompTy != nullptr) { |
914 | 0 | for (const auto &CD : CompTy->getDecl()) { |
915 | 0 | if (!CD.isInstanceDecl()) { |
916 | 0 | continue; |
917 | 0 | } |
918 | 0 | const auto &ID = CD.getInstance(); |
919 | 0 | if (!ID.isExportDecl()) { |
920 | 0 | continue; |
921 | 0 | } |
922 | 0 | const auto &ED = ID.getExport(); |
923 | 0 | const auto OptST = descTypeToSortType(ED.getExternDesc().getDescType()); |
924 | 0 | if (!OptST.has_value()) { |
925 | 0 | continue; // `(core module)` export — not a component-side entry. |
926 | 0 | } |
927 | 0 | CompCtx.addInstanceExport(InstanceIdx, ED.getName(), *OptST); |
928 | 0 | } |
929 | 0 | } |
930 | 0 | } else if (Inst.isInlineExport()) { |
931 | | // Allocate the instance first so exports can be registered on it. |
932 | 0 | 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 | 0 | std::unordered_set<std::string_view> SeenExports; |
939 | 0 | for (const auto &Export : Inst.getInlineExports()) { |
940 | 0 | if (!SeenExports.insert(Export.getName()).second) { |
941 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
942 | 0 | spdlog::error(" Instance: Duplicate inline-export name '{}'"sv, |
943 | 0 | Export.getName()); |
944 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
945 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
946 | 0 | } |
947 | 0 | const auto &Sort = Export.getSortIdx().getSort(); |
948 | 0 | uint32_t Idx = Export.getSortIdx().getIdx(); |
949 | 0 | if (Sort.isCore()) { |
950 | 0 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
951 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
952 | 0 | spdlog::error( |
953 | 0 | " Instance: Inline export '{}' refers to invalid index {}"sv, |
954 | 0 | Export.getName(), Idx); |
955 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
956 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
957 | 0 | } |
958 | 0 | continue; |
959 | 0 | } |
960 | 0 | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
961 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
962 | 0 | spdlog::error( |
963 | 0 | " Instance: Inline export '{}' refers to invalid index {}"sv, |
964 | 0 | Export.getName(), Idx); |
965 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Instance)); |
966 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
967 | 0 | } |
968 | 0 | if (Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
969 | 0 | auto SubstitutedIdx = |
970 | 0 | CompCtx.getSubstitutedType(std::string(Export.getName())); |
971 | 0 | 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 | 0 | } |
979 | 0 | std::optional<uint32_t> NestedIdx; |
980 | 0 | const AST::Component::InstanceType *PropagatedIT = nullptr; |
981 | 0 | if (Sort.getSortType() == AST::Component::Sort::SortType::Instance) { |
982 | 0 | 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 | 0 | PropagatedIT = CompCtx.getInstance(Idx).Type; |
988 | 0 | } |
989 | 0 | CompCtx.addInstanceExport(InstanceIdx, Export.getName(), |
990 | 0 | Sort.getSortType(), PropagatedIT, NestedIdx); |
991 | 0 | } |
992 | 0 | } else { |
993 | 0 | assumingUnreachable(); |
994 | 0 | } |
995 | 0 | return {}; |
996 | 0 | } |
997 | | |
998 | 0 | Expect<void> Validator::validate(const AST::Component::CoreAlias &A) noexcept { |
999 | | // CoreAlias is always an outer alias. |
1000 | 0 | uint32_t Ct = A.getComponentJump(); |
1001 | 0 | uint32_t Idx = A.getIndex(); |
1002 | |
|
1003 | 0 | uint32_t OutLinkCompCnt = 0; |
1004 | 0 | const auto *TargetCtx = &CompCtx.getCurrentContext(); |
1005 | 0 | while (Ct > OutLinkCompCnt && TargetCtx != nullptr) { |
1006 | 0 | TargetCtx = TargetCtx->Parent; |
1007 | 0 | OutLinkCompCnt++; |
1008 | 0 | } |
1009 | 0 | if (TargetCtx == nullptr) { |
1010 | 0 | 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 | 0 | spdlog::error( |
1014 | 0 | " CoreAlias: outer count {} exceeds enclosing component count {}"sv, |
1015 | 0 | Ct, OutLinkCompCnt - 1); |
1016 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1017 | 0 | } |
1018 | | |
1019 | 0 | const auto &Sort = A.getSort(); |
1020 | 0 | if (Sort.isCore()) { |
1021 | 0 | if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) { |
1022 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1023 | 0 | spdlog::error(" CoreAlias: outer index {} out of bounds"sv, Idx); |
1024 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1025 | 0 | } |
1026 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
1027 | 0 | } |
1028 | 0 | return {}; |
1029 | 0 | } |
1030 | | |
1031 | 0 | Expect<void> Validator::validate(const AST::Component::Alias &Alias) noexcept { |
1032 | 0 | const auto &Sort = Alias.getSort(); |
1033 | 0 | switch (Alias.getTargetType()) { |
1034 | 0 | case AST::Component::Alias::TargetType::Export: { |
1035 | 0 | const auto Idx = Alias.getExport().first; |
1036 | 0 | const auto &Name = Alias.getExport().second; |
1037 | |
|
1038 | 0 | if (Sort.isCore()) { |
1039 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1040 | 0 | spdlog::error(" Alias export: Mapping an export '{}' to core:sort"sv, |
1041 | 0 | Name); |
1042 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1043 | 0 | } |
1044 | | |
1045 | 0 | if (Idx >= |
1046 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)) { |
1047 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1048 | 0 | spdlog::error( |
1049 | 0 | " Alias export: Export index {} exceeds available component instance index {}"sv, |
1050 | 0 | Idx, |
1051 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Instance)); |
1052 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1053 | 0 | } |
1054 | | |
1055 | 0 | const auto &InstExports = CompCtx.getInstance(Idx).Exports; |
1056 | 0 | auto It = InstExports.find(std::string(Name)); |
1057 | 0 | if (It == InstExports.cend()) { |
1058 | 0 | spdlog::error(ErrCode::Value::ExportNotFound); |
1059 | 0 | spdlog::error( |
1060 | 0 | " Alias export: No matching export '{}' found in component instance index {}"sv, |
1061 | 0 | Name, Idx); |
1062 | 0 | return Unexpect(ErrCode::Value::ExportNotFound); |
1063 | 0 | } |
1064 | | |
1065 | 0 | if (It->second.ST != Sort.getSortType()) { |
1066 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1067 | 0 | spdlog::error(" Alias export: Type mapping mismatch for export '{}'"sv, |
1068 | 0 | Name); |
1069 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1070 | 0 | } |
1071 | 0 | return {}; |
1072 | 0 | } |
1073 | 0 | case AST::Component::Alias::TargetType::CoreExport: { |
1074 | 0 | const auto Idx = Alias.getExport().first; |
1075 | 0 | const auto &Name = Alias.getExport().second; |
1076 | |
|
1077 | 0 | if (!Sort.isCore()) { |
1078 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1079 | 0 | spdlog::error(" Alias core:export: Mapping a export '{}' to sort"sv, |
1080 | 0 | Name); |
1081 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1082 | 0 | } |
1083 | | |
1084 | 0 | if (Idx >= CompCtx.getCoreSortIndexSize( |
1085 | 0 | AST::Component::Sort::CoreSortType::Instance)) { |
1086 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1087 | 0 | spdlog::error( |
1088 | 0 | " Alias core:export: Export index {} exceeds available core instance index {}"sv, |
1089 | 0 | Idx, |
1090 | 0 | CompCtx.getCoreSortIndexSize( |
1091 | 0 | AST::Component::Sort::CoreSortType::Instance) - |
1092 | 0 | 1); |
1093 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1094 | 0 | } |
1095 | | |
1096 | 0 | const auto &CoreExports = CompCtx.getCoreInstance(Idx); |
1097 | 0 | auto It = CoreExports.find(std::string(Name)); |
1098 | 0 | if (It == CoreExports.end()) { |
1099 | 0 | spdlog::error(ErrCode::Value::ExportNotFound); |
1100 | 0 | spdlog::error( |
1101 | 0 | " Alias core:export: No matching export '{}' found in core instance index {}"sv, |
1102 | 0 | Name, Idx); |
1103 | 0 | return Unexpect(ErrCode::Value::ExportNotFound); |
1104 | 0 | } |
1105 | | |
1106 | 0 | const auto ExternTy = It->second; |
1107 | 0 | AST::Component::Sort::CoreSortType ST; |
1108 | 0 | switch (ExternTy) { |
1109 | 0 | case ExternalType::Function: |
1110 | 0 | ST = AST::Component::Sort::CoreSortType::Func; |
1111 | 0 | 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 | 0 | } |
1131 | 0 | if (ST != Sort.getCoreSortType()) { |
1132 | | // The error message differs of the tag core sort. |
1133 | 0 | ErrCode::Value ErrValue = ErrCode::Value::InvalidIndex; |
1134 | 0 | if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Tag) { |
1135 | 0 | ErrValue = ErrCode::Value::UnknownCoreTag; |
1136 | 0 | } |
1137 | 0 | spdlog::error(ErrValue); |
1138 | 0 | spdlog::error( |
1139 | 0 | " Alias core:export: Type mapping mismatch for export '{}'"sv, |
1140 | 0 | Name); |
1141 | 0 | return Unexpect(ErrValue); |
1142 | 0 | } |
1143 | 0 | return {}; |
1144 | 0 | } |
1145 | 0 | case AST::Component::Alias::TargetType::Outer: { |
1146 | 0 | const auto Ct = Alias.getOuter().first; |
1147 | 0 | const auto Idx = Alias.getOuter().second; |
1148 | |
|
1149 | 0 | uint32_t OutLinkCompCnt = 0; |
1150 | 0 | const auto *TargetCtx = &CompCtx.getCurrentContext(); |
1151 | 0 | while (Ct > OutLinkCompCnt && TargetCtx != nullptr) { |
1152 | 0 | TargetCtx = TargetCtx->Parent; |
1153 | 0 | OutLinkCompCnt++; |
1154 | 0 | } |
1155 | 0 | if (TargetCtx == nullptr) { |
1156 | 0 | 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 | 0 | spdlog::error( |
1161 | 0 | " Alias outer: Component out-link count {} is exceeding the enclosing component count {}"sv, |
1162 | 0 | Ct, OutLinkCompCnt - 1); |
1163 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1164 | 0 | } |
1165 | | |
1166 | 0 | if (Sort.isCore()) { |
1167 | 0 | if (Sort.getCoreSortType() != |
1168 | 0 | AST::Component::Sort::CoreSortType::Module && |
1169 | 0 | Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Type) { |
1170 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1171 | 0 | spdlog::error( |
1172 | 0 | " Alias outer: Invalid core:sort for outer alias. Only type, module, or component are allowed."sv); |
1173 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1174 | 0 | } |
1175 | 0 | if (Idx >= TargetCtx->getCoreSortIndexSize(Sort.getCoreSortType())) { |
1176 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1177 | 0 | spdlog::error( |
1178 | 0 | " Alias outer: core:sort index {} invalid in component context"sv, |
1179 | 0 | Idx); |
1180 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1181 | 0 | } |
1182 | 0 | } else { |
1183 | 0 | if (Sort.getSortType() != AST::Component::Sort::SortType::Type && |
1184 | 0 | Sort.getSortType() != AST::Component::Sort::SortType::Component) { |
1185 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1186 | 0 | spdlog::error( |
1187 | 0 | " Alias outer: Invalid sort for outer alias. Only type, module, or component are allowed."sv); |
1188 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1189 | 0 | } |
1190 | 0 | if (Idx >= TargetCtx->getSortIndexSize(Sort.getSortType())) { |
1191 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1192 | 0 | spdlog::error( |
1193 | 0 | " Alias outer: sort index {} invalid in component context"sv, |
1194 | 0 | Idx); |
1195 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1196 | 0 | } |
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 | 0 | } |
1209 | 0 | return {}; |
1210 | 0 | } |
1211 | 0 | default: |
1212 | 0 | assumingUnreachable(); |
1213 | 0 | } |
1214 | 0 | } |
1215 | | |
1216 | | Expect<void> |
1217 | 0 | Validator::validate(const AST::Component::CoreDefType &DType) noexcept { |
1218 | 0 | if (DType.isRecType()) { |
1219 | | // Each sub-type in the rec group gets its own entry in core:type. |
1220 | 0 | for (const auto &ST : DType.getSubTypes()) { |
1221 | 0 | CompCtx.addCoreType(&ST); |
1222 | 0 | } |
1223 | 0 | } else if (DType.isModuleType()) { |
1224 | | // Module types are validated with an initially-empty type index space. |
1225 | 0 | CompCtx.enterTypeDefinition(); |
1226 | 0 | for (const auto &Decl : DType.getModuleType()) { |
1227 | 0 | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
1228 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_CoreDefType)); |
1229 | 0 | return E; |
1230 | 0 | })); |
1231 | 0 | } |
1232 | 0 | 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 | 0 | uint32_t NewTypeIdx = CompCtx.addCoreType(); |
1236 | 0 | CompCtx.setCoreModuleType(NewTypeIdx, &DType); |
1237 | 0 | } else { |
1238 | 0 | assumingUnreachable(); |
1239 | 0 | } |
1240 | 0 | return {}; |
1241 | 0 | } |
1242 | | |
1243 | | Expect<void> |
1244 | 0 | Validator::validate(const AST::Component::DefType &DType) noexcept { |
1245 | 0 | auto ReportError = [](auto E) { |
1246 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
1247 | 0 | return E; |
1248 | 0 | }; |
1249 | |
|
1250 | 0 | if (DType.isDefValType()) { |
1251 | 0 | EXPECTED_TRY(validate(DType.getDefValType()).map_error(ReportError)); |
1252 | 0 | } else if (DType.isFuncType()) { |
1253 | 0 | EXPECTED_TRY(validate(DType.getFuncType()).map_error(ReportError)); |
1254 | 0 | } else if (DType.isComponentType()) { |
1255 | 0 | EXPECTED_TRY(validate(DType.getComponentType()).map_error(ReportError)); |
1256 | 0 | } else if (DType.isInstanceType()) { |
1257 | 0 | EXPECTED_TRY(validate(DType.getInstanceType()).map_error(ReportError)); |
1258 | 0 | } else if (DType.isResourceType()) { |
1259 | 0 | EXPECTED_TRY(validate(DType.getResourceType()).map_error(ReportError)); |
1260 | 0 | } else { |
1261 | 0 | assumingUnreachable(); |
1262 | 0 | } |
1263 | | // addType records body/id/locality for resource DefTypes in one step. |
1264 | 0 | CompCtx.addType(&DType); |
1265 | 0 | return {}; |
1266 | 0 | } |
1267 | | |
1268 | | Expect<void> |
1269 | 0 | Validator::validate(const AST::Component::Canonical &Canon) noexcept { |
1270 | 0 | switch (Canon.getOpCode()) { |
1271 | 0 | case ComponentCanonOpCode::Lift: |
1272 | 0 | return validateCanonLift(Canon); |
1273 | 0 | case ComponentCanonOpCode::Lower: |
1274 | 0 | return validateCanonLower(Canon); |
1275 | 0 | case ComponentCanonOpCode::Resource__new: |
1276 | 0 | return validateCanonResourceNew(Canon); |
1277 | 0 | case ComponentCanonOpCode::Resource__rep: |
1278 | 0 | return validateCanonResourceRep(Canon); |
1279 | 0 | case ComponentCanonOpCode::Resource__drop: |
1280 | 0 | case ComponentCanonOpCode::Resource__drop_async: |
1281 | 0 | return validateCanonResourceDrop(Canon); |
1282 | 0 | default: |
1283 | 0 | spdlog::error(ErrCode::Value::ComponentNotImplValidator); |
1284 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1285 | 0 | return Unexpect(ErrCode::Value::ComponentNotImplValidator); |
1286 | 0 | } |
1287 | 0 | } |
1288 | | |
1289 | | Expect<void> Validator::validateCanonOptions( |
1290 | | ComponentCanonOpCode Code, |
1291 | 0 | Span<const AST::Component::CanonOpt> Opts) noexcept { |
1292 | 0 | using OptCode = ComponentCanonOptCode; |
1293 | 0 | 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 | 0 | 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 | 0 | bool HasEncoding = false; |
1306 | 0 | bool HasMemory = false; |
1307 | 0 | bool HasRealloc = false; |
1308 | 0 | bool HasPostReturn = false; |
1309 | 0 | bool HasAsync = false; |
1310 | 0 | bool HasCallback = false; |
1311 | 0 | bool HasAlwaysTaskReturn = false; |
1312 | 0 | uint32_t ReallocIdx = 0; |
1313 | 0 | uint32_t CallbackIdx = 0; |
1314 | 0 | uint32_t PostReturnIdx = 0; |
1315 | 0 | uint32_t MemoryIdx = 0; |
1316 | |
|
1317 | 0 | auto RejectDup = [&](const char *Name) -> Expect<void> { |
1318 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1319 | 0 | spdlog::error(" canonical option '{}' appears more than once"sv, Name); |
1320 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1321 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1322 | 0 | }; |
1323 | 0 | auto RejectSite = [&](const char *Name) -> Expect<void> { |
1324 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1325 | 0 | spdlog::error( |
1326 | 0 | " canonical option '{}' is not allowed in this canon built-in"sv, |
1327 | 0 | Name); |
1328 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1329 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1330 | 0 | }; |
1331 | |
|
1332 | 0 | for (const auto &Opt : Opts) { |
1333 | 0 | switch (Opt.getCode()) { |
1334 | 0 | case OptCode::Encode_UTF8: |
1335 | 0 | case OptCode::Encode_UTF16: |
1336 | 0 | case OptCode::Encode_Latin1: |
1337 | 0 | if (HasEncoding) { |
1338 | 0 | return RejectDup("string-encoding"); |
1339 | 0 | } |
1340 | 0 | HasEncoding = true; |
1341 | 0 | break; |
1342 | 0 | case OptCode::Memory: |
1343 | 0 | if (HasMemory) { |
1344 | 0 | return RejectDup("memory"); |
1345 | 0 | } |
1346 | 0 | HasMemory = true; |
1347 | 0 | MemoryIdx = Opt.getIndex(); |
1348 | 0 | break; |
1349 | 0 | case OptCode::Realloc: |
1350 | 0 | if (HasRealloc) { |
1351 | 0 | return RejectDup("realloc"); |
1352 | 0 | } |
1353 | 0 | HasRealloc = true; |
1354 | 0 | ReallocIdx = Opt.getIndex(); |
1355 | 0 | break; |
1356 | 0 | case OptCode::PostReturn: |
1357 | 0 | if (Code != CanonOp::Lift) { |
1358 | 0 | return RejectSite("post-return"); |
1359 | 0 | } |
1360 | 0 | if (HasPostReturn) { |
1361 | 0 | return RejectDup("post-return"); |
1362 | 0 | } |
1363 | 0 | HasPostReturn = true; |
1364 | 0 | PostReturnIdx = Opt.getIndex(); |
1365 | 0 | break; |
1366 | 0 | case OptCode::Async: |
1367 | 0 | if (HasAsync) { |
1368 | 0 | return RejectDup("async"); |
1369 | 0 | } |
1370 | 0 | HasAsync = true; |
1371 | 0 | break; |
1372 | 0 | case OptCode::Callback: |
1373 | 0 | if (Code != CanonOp::Lift) { |
1374 | 0 | return RejectSite("callback"); |
1375 | 0 | } |
1376 | 0 | if (HasCallback) { |
1377 | 0 | return RejectDup("callback"); |
1378 | 0 | } |
1379 | 0 | HasCallback = true; |
1380 | 0 | CallbackIdx = Opt.getIndex(); |
1381 | 0 | break; |
1382 | 0 | case OptCode::AlwaysTaskReturn: |
1383 | 0 | if (Code != CanonOp::Lift) { |
1384 | 0 | return RejectSite("always-task-return"); |
1385 | 0 | } |
1386 | 0 | if (HasAlwaysTaskReturn) { |
1387 | 0 | return RejectDup("always-task-return"); |
1388 | 0 | } |
1389 | 0 | HasAlwaysTaskReturn = true; |
1390 | 0 | 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 | 0 | } |
1398 | 0 | } |
1399 | | |
1400 | | // Structural rules. |
1401 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | if (HasRealloc && !HasMemory) { |
1422 | 0 | spdlog::error(ErrCode::Value::InvalidCanonOption); |
1423 | 0 | spdlog::error( |
1424 | 0 | " canonical option 'realloc' requires 'memory' to also be specified"sv); |
1425 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1426 | 0 | return Unexpect(ErrCode::Value::InvalidCanonOption); |
1427 | 0 | } |
1428 | | |
1429 | | // Index bounds checks. Core func signature body checks (realloc/callback) |
1430 | | // deferred as GAP-C-5b once getCoreFunc signatures are populated. |
1431 | 0 | if (HasMemory && |
1432 | 0 | MemoryIdx >= CompCtx.getCoreSortIndexSize( |
1433 | 0 | AST::Component::Sort::CoreSortType::Memory)) { |
1434 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1435 | 0 | spdlog::error( |
1436 | 0 | " canonical option 'memory': core memory index {} out of bounds"sv, |
1437 | 0 | MemoryIdx); |
1438 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1439 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1440 | 0 | } |
1441 | 0 | const uint32_t CoreFuncSpaceSize = |
1442 | 0 | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func); |
1443 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | return {}; |
1468 | 0 | } |
1469 | | |
1470 | | Expect<void> |
1471 | 0 | Validator::validateCanonLift(const AST::Component::Canonical &Canon) noexcept { |
1472 | 0 | const uint32_t CoreFuncIdx = Canon.getIndex(); |
1473 | 0 | const uint32_t CoreFuncSpaceSize = |
1474 | 0 | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Func); |
1475 | | // 1. Core func index bounds. |
1476 | 0 | if (CoreFuncIdx >= CoreFuncSpaceSize) { |
1477 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1478 | 0 | spdlog::error( |
1479 | 0 | " canon lift: core func index {} exceeds core func index space size {}"sv, |
1480 | 0 | CoreFuncIdx, CoreFuncSpaceSize); |
1481 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1482 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1483 | 0 | } |
1484 | 0 | const uint32_t TypeIdx = Canon.getTargetIndex(); |
1485 | 0 | const uint32_t TypeSpaceSize = |
1486 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1487 | | // 2. Target type index bounds. |
1488 | 0 | if (TypeIdx >= TypeSpaceSize) { |
1489 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1490 | 0 | spdlog::error( |
1491 | 0 | " canon lift: type index {} exceeds type index space size {}"sv, |
1492 | 0 | TypeIdx, TypeSpaceSize); |
1493 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1494 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1495 | 0 | } |
1496 | | // 3. Target type must be a component FuncType. |
1497 | 0 | const auto *DT = CompCtx.getDefType(TypeIdx); |
1498 | 0 | 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 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1502 | 0 | spdlog::error( |
1503 | 0 | " canon lift: target type index {} is an unresolved type slot"sv, |
1504 | 0 | TypeIdx); |
1505 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1506 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1507 | 0 | } |
1508 | 0 | if (!DT->isFuncType()) { |
1509 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1510 | 0 | spdlog::error( |
1511 | 0 | " canon lift: target type index {} does not reference a component func type"sv, |
1512 | 0 | TypeIdx); |
1513 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1514 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1515 | 0 | } |
1516 | | // 4. Validate canonical options (Lift site allows all). |
1517 | 0 | 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 | 0 | CompCtx.addFunc(&DT->getFuncType()); |
1521 | 0 | return {}; |
1522 | 0 | } |
1523 | | |
1524 | | Expect<void> |
1525 | 0 | Validator::validateCanonLower(const AST::Component::Canonical &Canon) noexcept { |
1526 | 0 | const uint32_t FuncIdx = Canon.getIndex(); |
1527 | 0 | const uint32_t FuncSpaceSize = |
1528 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Func); |
1529 | | // 1. Component func index bounds. |
1530 | 0 | if (FuncIdx >= FuncSpaceSize) { |
1531 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1532 | 0 | spdlog::error( |
1533 | 0 | " canon lower: component func index {} exceeds func index space size {}"sv, |
1534 | 0 | FuncIdx, FuncSpaceSize); |
1535 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1536 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1537 | 0 | } |
1538 | | // 2. Validate canonical options (per-site rules for Lower). |
1539 | 0 | 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 | 0 | CompCtx.addCoreFunc(); |
1543 | 0 | return {}; |
1544 | 0 | } |
1545 | | |
1546 | | Expect<void> Validator::validateCanonResourceNew( |
1547 | 0 | const AST::Component::Canonical &Canon) noexcept { |
1548 | 0 | const uint32_t Idx = Canon.getIndex(); |
1549 | 0 | const uint32_t TypeSpaceSize = |
1550 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1551 | | // 1. Type index bounds. |
1552 | 0 | if (Idx >= TypeSpaceSize) { |
1553 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1554 | 0 | spdlog::error( |
1555 | 0 | " canon resource.new: type index {} exceeds type index space size {}"sv, |
1556 | 0 | Idx, TypeSpaceSize); |
1557 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1558 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1559 | 0 | } |
1560 | | // 2. Type must be a locally-defined resource. |
1561 | 0 | const auto *RInfo = CompCtx.getResource(Idx); |
1562 | 0 | if (RInfo == nullptr) { |
1563 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1564 | 0 | spdlog::error( |
1565 | 0 | " canon resource.new: type index {} does not reference a resource"sv, |
1566 | 0 | Idx); |
1567 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1568 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1569 | 0 | } |
1570 | 0 | if (!RInfo->LocallyDefined) { |
1571 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1572 | 0 | spdlog::error( |
1573 | 0 | " canon resource.new: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv, |
1574 | 0 | Idx); |
1575 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1576 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1577 | 0 | } |
1578 | | // 4. Validate canonical options. |
1579 | 0 | 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 | 0 | CompCtx.addCoreFunc(&CoreFuncType_I32_I32); |
1583 | 0 | return {}; |
1584 | 0 | } |
1585 | | |
1586 | | Expect<void> Validator::validateCanonResourceRep( |
1587 | 0 | const AST::Component::Canonical &Canon) noexcept { |
1588 | 0 | const uint32_t Idx = Canon.getIndex(); |
1589 | 0 | const uint32_t TypeSpaceSize = |
1590 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1591 | | // 1. Type index bounds. |
1592 | 0 | if (Idx >= TypeSpaceSize) { |
1593 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1594 | 0 | spdlog::error( |
1595 | 0 | " canon resource.rep: type index {} exceeds type index space size {}"sv, |
1596 | 0 | Idx, TypeSpaceSize); |
1597 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1598 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1599 | 0 | } |
1600 | | // 2. Type must be a locally-defined resource. |
1601 | 0 | const auto *RInfo = CompCtx.getResource(Idx); |
1602 | 0 | if (RInfo == nullptr) { |
1603 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1604 | 0 | spdlog::error( |
1605 | 0 | " canon resource.rep: type index {} does not reference a resource"sv, |
1606 | 0 | Idx); |
1607 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1608 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1609 | 0 | } |
1610 | 0 | if (!RInfo->LocallyDefined) { |
1611 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1612 | 0 | spdlog::error( |
1613 | 0 | " canon resource.rep: type index {} is not locally defined (imported or outer-aliased resources are not allowed)"sv, |
1614 | 0 | Idx); |
1615 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1616 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1617 | 0 | } |
1618 | | // 4. Validate canonical options. |
1619 | 0 | 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 | 0 | CompCtx.addCoreFunc(&CoreFuncType_I32_I32); |
1623 | 0 | return {}; |
1624 | 0 | } |
1625 | | |
1626 | | Expect<void> Validator::validateCanonResourceDrop( |
1627 | 0 | const AST::Component::Canonical &Canon) noexcept { |
1628 | 0 | const uint32_t Idx = Canon.getIndex(); |
1629 | 0 | const uint32_t TypeSpaceSize = |
1630 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1631 | | // 1. Type index bounds. |
1632 | 0 | if (Idx >= TypeSpaceSize) { |
1633 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1634 | 0 | spdlog::error( |
1635 | 0 | " canon resource.drop: type index {} exceeds type index space size {}"sv, |
1636 | 0 | Idx, TypeSpaceSize); |
1637 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1638 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1639 | 0 | } |
1640 | | // 2. Type must be a resource type. |
1641 | 0 | if (CompCtx.getResource(Idx) == nullptr) { |
1642 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1643 | 0 | spdlog::error( |
1644 | 0 | " canon resource.drop: type index {} does not reference a resource"sv, |
1645 | 0 | Idx); |
1646 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Canonical)); |
1647 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1648 | 0 | } |
1649 | | // 3. resource.drop accepts both local and imported resources — no locality |
1650 | | // check. |
1651 | | // 4. Validate canonical options. |
1652 | 0 | 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 | 0 | CompCtx.addCoreFunc(&CoreFuncType_I32_Void); |
1657 | 0 | return {}; |
1658 | 0 | } |
1659 | | |
1660 | 0 | 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 | 0 | const uint32_t TypeSpaceBefore = |
1675 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1676 | |
|
1677 | 0 | EXPECTED_TRY(validate(Im.getDesc()).map_error([](auto E) { |
1678 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1679 | 0 | return E; |
1680 | 0 | })); |
1681 | | |
1682 | 0 | EXPECTED_TRY(ComponentName CName, |
1683 | 0 | ComponentName::parse(Im.getName()).map_error([](auto E) { |
1684 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1685 | 0 | return E; |
1686 | 0 | })); |
1687 | | |
1688 | | // Annotated plainnames ([constructor], [method], [static]) can only appear |
1689 | | // on func imports. |
1690 | 0 | switch (CName.getKind()) { |
1691 | 0 | case ComponentNameKind::Constructor: |
1692 | 0 | case ComponentNameKind::Method: |
1693 | 0 | case ComponentNameKind::Static: |
1694 | 0 | if (Im.getDesc().getDescType() != |
1695 | 0 | AST::Component::ExternDesc::DescType::FuncType) { |
1696 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
1697 | 0 | spdlog::error(" Import: annotated name requires func type"sv); |
1698 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1699 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
1700 | 0 | } |
1701 | 0 | break; |
1702 | 0 | default: |
1703 | 0 | break; |
1704 | 0 | } |
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 | 0 | std::string_view ResourceLabel; |
1715 | 0 | 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 | 0 | default: |
1726 | 0 | break; |
1727 | 0 | } |
1728 | 0 | 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 | 0 | if (!CompCtx.addImportedName(CName)) { |
1738 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
1739 | 0 | spdlog::error(" Import: Duplicate import name"sv); |
1740 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Import)); |
1741 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
1742 | 0 | } |
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 | 0 | if (Im.getDesc().getDescType() == |
1747 | 0 | AST::Component::ExternDesc::DescType::TypeBound && |
1748 | 0 | CName.getKind() == ComponentNameKind::Label) { |
1749 | 0 | CompCtx.addResourceLabel(Im.getName(), TypeSpaceBefore); |
1750 | 0 | } |
1751 | |
|
1752 | 0 | return {}; |
1753 | 0 | } |
1754 | | |
1755 | 0 | 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 | 0 | const auto &Sort = Ex.getSortIndex().getSort(); |
1771 | 0 | uint32_t Idx = Ex.getSortIndex().getIdx(); |
1772 | 0 | 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 | 0 | if (Sort.getCoreSortType() != AST::Component::Sort::CoreSortType::Module) { |
1776 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1777 | 0 | spdlog::error( |
1778 | 0 | " Export: core sort other than `core module` is not allowed"sv); |
1779 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1780 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1781 | 0 | } |
1782 | 0 | if (Idx >= CompCtx.getCoreSortIndexSize(Sort.getCoreSortType())) { |
1783 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
1784 | 0 | spdlog::error(" Export: sort index {} out of bounds"sv, Idx); |
1785 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1786 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
1787 | 0 | } |
1788 | 0 | } else { |
1789 | 0 | if (Idx >= CompCtx.getSortIndexSize(Sort.getSortType())) { |
1790 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
1791 | 0 | spdlog::error(" Export: sort index {} out of bounds"sv, Idx); |
1792 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1793 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
1794 | 0 | } |
1795 | 0 | } |
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 | 0 | if (Ex.getDesc().has_value() && |
1803 | 0 | !sortMatchesDescType(Sort, Ex.getDesc()->getDescType())) { |
1804 | 0 | spdlog::error(ErrCode::Value::ExportAscriptionIncompatible); |
1805 | 0 | spdlog::error( |
1806 | 0 | " Export: ascribed externdesc kind does not match sortidx sort"sv); |
1807 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1808 | 0 | return Unexpect(ErrCode::Value::ExportAscriptionIncompatible); |
1809 | 0 | } |
1810 | | |
1811 | | // Validate name grammar, then enforce strong-uniqueness across exports. |
1812 | 0 | EXPECTED_TRY(ComponentName CName, |
1813 | 0 | validateExportName(Ex.getName()).map_error([](auto E) { |
1814 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1815 | 0 | return E; |
1816 | 0 | })); |
1817 | 0 | if (!CompCtx.addExportedName(CName)) { |
1818 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
1819 | 0 | spdlog::error(" Export: Duplicate export name '{}'"sv, Ex.getName()); |
1820 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Export)); |
1821 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
1822 | 0 | } |
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 | 0 | if (Sort.isCore()) { |
1829 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
1830 | 0 | } else { |
1831 | 0 | const AST::Component::InstanceType *IT = nullptr; |
1832 | 0 | const bool IsInst = |
1833 | 0 | Sort.getSortType() == AST::Component::Sort::SortType::Instance; |
1834 | 0 | const bool HasInstAscription = |
1835 | 0 | IsInst && Ex.getDesc().has_value() && |
1836 | 0 | Ex.getDesc()->getDescType() == |
1837 | 0 | AST::Component::ExternDesc::DescType::InstanceType; |
1838 | 0 | 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 | 0 | uint32_t NewIdx = CompCtx.incSortIndexSize(Sort.getSortType()); |
1853 | 0 | if (IsInst) { |
1854 | 0 | if (IT != nullptr) { |
1855 | 0 | populateInstanceFromType(NewIdx, *IT); |
1856 | 0 | } 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 | 0 | const auto &SrcExports = CompCtx.getInstance(Idx).Exports; |
1863 | 0 | for (const auto &[Name, IE] : SrcExports) { |
1864 | 0 | CompCtx.addInstanceExport(NewIdx, Name, IE.ST, IE.IT, |
1865 | 0 | IE.NestedInstIdx); |
1866 | 0 | } |
1867 | 0 | } |
1868 | 0 | } |
1869 | 0 | } |
1870 | 0 | return {}; |
1871 | 0 | } |
1872 | | |
1873 | | Expect<void> |
1874 | 0 | 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 | 0 | switch (Desc.getDescType()) { |
1892 | 0 | case AST::Component::ExternDesc::DescType::CoreType: { |
1893 | 0 | const uint32_t RefIdx = Desc.getTypeIndex(); |
1894 | 0 | const uint32_t CoreTypeSize = |
1895 | 0 | CompCtx.getCoreSortIndexSize(AST::Component::Sort::CoreSortType::Type); |
1896 | 0 | if (RefIdx >= CoreTypeSize) { |
1897 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1898 | 0 | spdlog::error( |
1899 | 0 | " ExternDesc: core type index {} exceeds core:type index space size {}"sv, |
1900 | 0 | RefIdx, CoreTypeSize); |
1901 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1902 | 0 | } |
1903 | 0 | break; |
1904 | 0 | } |
1905 | 0 | case AST::Component::ExternDesc::DescType::FuncType: |
1906 | 0 | case AST::Component::ExternDesc::DescType::ComponentType: |
1907 | 0 | case AST::Component::ExternDesc::DescType::InstanceType: { |
1908 | 0 | const uint32_t RefIdx = Desc.getTypeIndex(); |
1909 | 0 | const uint32_t TypeSize = |
1910 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type); |
1911 | 0 | if (RefIdx >= TypeSize) { |
1912 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1913 | 0 | spdlog::error( |
1914 | 0 | " ExternDesc: referenced type index {} exceeds type index space size {}"sv, |
1915 | 0 | RefIdx, TypeSize); |
1916 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1917 | 0 | } |
1918 | 0 | break; |
1919 | 0 | } |
1920 | 0 | default: |
1921 | 0 | break; |
1922 | 0 | } |
1923 | | |
1924 | 0 | switch (Desc.getDescType()) { |
1925 | 0 | 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 | 0 | const auto *CT = CompCtx.getCoreModuleType(Desc.getTypeIndex()); |
1929 | 0 | CompCtx.addCoreModule(CT); |
1930 | 0 | break; |
1931 | 0 | } |
1932 | 0 | case AST::Component::ExternDesc::DescType::FuncType: |
1933 | 0 | CompCtx.addFunc(); |
1934 | 0 | break; |
1935 | 0 | case AST::Component::ExternDesc::DescType::ValueBound: |
1936 | 0 | CompCtx.addValue(); |
1937 | 0 | break; |
1938 | 0 | case AST::Component::ExternDesc::DescType::TypeBound: |
1939 | 0 | if (Desc.isEqType()) { |
1940 | | // (type (eq i)) — alias type i |
1941 | 0 | uint32_t RefIdx = Desc.getTypeIndex(); |
1942 | 0 | if (RefIdx >= |
1943 | 0 | CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
1944 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1945 | 0 | spdlog::error(" ExternDesc: eq type bound index {} out of bounds"sv, |
1946 | 0 | RefIdx); |
1947 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
1948 | 0 | } |
1949 | | // (eq i): inherits the source resource's id; body lives on the |
1950 | | // shared registry entry. |
1951 | 0 | uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false); |
1952 | 0 | if (const auto *SrcInfo = CompCtx.getResource(RefIdx)) { |
1953 | 0 | CompCtx.addResource(NewIdx, {SrcInfo->Id, /*LocallyDefined=*/false}); |
1954 | 0 | } |
1955 | 0 | } else { |
1956 | | // (sub resource): abstract import — fresh id with no body. |
1957 | 0 | uint32_t NewIdx = CompCtx.addType(nullptr, /*IsLocal=*/false); |
1958 | 0 | CompCtx.addResource(NewIdx, {CompCtx.allocateFreshResourceId(), |
1959 | 0 | /*LocallyDefined=*/false}); |
1960 | 0 | } |
1961 | 0 | break; |
1962 | 0 | 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 | 0 | const auto *CT = CompCtx.getComponentType(Desc.getTypeIndex()); |
1966 | 0 | CompCtx.addComponent(CT); |
1967 | 0 | break; |
1968 | 0 | } |
1969 | 0 | 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 | 0 | const auto *IT = CompCtx.getInstanceType(Desc.getTypeIndex()); |
1973 | 0 | uint32_t InstIdx = CompCtx.addInstance(IT); |
1974 | 0 | if (IT != nullptr) { |
1975 | 0 | populateInstanceFromType(InstIdx, *IT); |
1976 | 0 | } |
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 | 0 | break; |
1982 | 0 | } |
1983 | 0 | default: |
1984 | 0 | assumingUnreachable(); |
1985 | 0 | } |
1986 | | |
1987 | 0 | return {}; |
1988 | 0 | } |
1989 | | |
1990 | | Expect<void> |
1991 | 0 | Validator::validate(const AST::Component::CoreImportDesc &Desc) noexcept { |
1992 | 0 | if (Desc.isFunc()) { |
1993 | 0 | uint32_t TypeIdx = Desc.getTypeIndex(); |
1994 | 0 | if (TypeIdx >= CompCtx.getCoreSortIndexSize( |
1995 | 0 | AST::Component::Sort::CoreSortType::Type)) { |
1996 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
1997 | 0 | spdlog::error(" CoreImportDesc: func type index {} out of bounds"sv, |
1998 | 0 | TypeIdx); |
1999 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
2000 | 0 | } |
2001 | 0 | CompCtx.addCoreFunc(); |
2002 | 0 | } else if (Desc.isTable()) { |
2003 | 0 | CompCtx.addCoreTable(); |
2004 | 0 | } else if (Desc.isMemory()) { |
2005 | 0 | CompCtx.addCoreMemory(); |
2006 | 0 | } else if (Desc.isGlobal()) { |
2007 | 0 | CompCtx.addCoreGlobal(); |
2008 | 0 | } else if (Desc.isTag()) { |
2009 | 0 | CompCtx.addCoreTag(); |
2010 | 0 | } else { |
2011 | 0 | assumingUnreachable(); |
2012 | 0 | } |
2013 | 0 | return {}; |
2014 | 0 | } |
2015 | | |
2016 | | Expect<void> |
2017 | 0 | Validator::validate(const AST::Component::CoreImportDecl &Decl) noexcept { |
2018 | 0 | return validate(Decl.getImportDesc()); |
2019 | 0 | } |
2020 | | |
2021 | | Expect<void> |
2022 | 0 | Validator::validate(const AST::Component::CoreExportDecl &Decl) noexcept { |
2023 | 0 | return validate(Decl.getImportDesc()); |
2024 | 0 | } |
2025 | | |
2026 | | Expect<void> |
2027 | 0 | Validator::validate(const AST::Component::CoreModuleDecl &Decl) noexcept { |
2028 | 0 | auto ReportError = [](auto E) { |
2029 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_CoreModule)); |
2030 | 0 | return E; |
2031 | 0 | }; |
2032 | |
|
2033 | 0 | if (Decl.isImport()) { |
2034 | 0 | EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError)); |
2035 | 0 | } else if (Decl.isType()) { |
2036 | 0 | EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError)); |
2037 | 0 | } else if (Decl.isAlias()) { |
2038 | 0 | EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError)); |
2039 | 0 | } else if (Decl.isExport()) { |
2040 | 0 | EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError)); |
2041 | 0 | } else { |
2042 | 0 | assumingUnreachable(); |
2043 | 0 | } |
2044 | 0 | return {}; |
2045 | 0 | } |
2046 | | |
2047 | | Expect<void> |
2048 | 0 | Validator::validate(const AST::Component::ImportDecl &Decl) noexcept { |
2049 | 0 | auto ReportError = [](auto E) { |
2050 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Import)); |
2051 | 0 | return E; |
2052 | 0 | }; |
2053 | | |
2054 | | // Validate the extern descriptor (also increments sort index spaces). |
2055 | 0 | EXPECTED_TRY(validate(Decl.getExternDesc()).map_error(ReportError)); |
2056 | | |
2057 | | // Parse and validate the import name. |
2058 | 0 | EXPECTED_TRY(ComponentName CName, |
2059 | 0 | ComponentName::parse(Decl.getName()).map_error(ReportError)); |
2060 | | |
2061 | | // Annotated plainnames can only appear on func imports. |
2062 | 0 | 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 | 0 | default: |
2075 | 0 | break; |
2076 | 0 | } |
2077 | | |
2078 | | // Check import name uniqueness. |
2079 | 0 | 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 | 0 | return {}; |
2087 | 0 | } |
2088 | | |
2089 | | Expect<void> |
2090 | 0 | 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 | 0 | EXPECTED_TRY(ComponentName CName, |
2095 | 0 | validateExportName(Decl.getName()).map_error([](auto E) { |
2096 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export)); |
2097 | 0 | return E; |
2098 | 0 | })); |
2099 | 0 | 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 | 0 | EXPECTED_TRY(validate(Decl.getExternDesc()).map_error([](auto E) { |
2117 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Export)); |
2118 | 0 | return E; |
2119 | 0 | })); |
2120 | | |
2121 | 0 | return {}; |
2122 | 0 | } |
2123 | | |
2124 | | Expect<void> |
2125 | 0 | Validator::validate(const AST::Component::InstanceDecl &Decl) noexcept { |
2126 | 0 | auto ReportError = [](auto E) { |
2127 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Instance)); |
2128 | 0 | return E; |
2129 | 0 | }; |
2130 | |
|
2131 | 0 | if (Decl.isCoreType()) { |
2132 | 0 | EXPECTED_TRY(validate(*Decl.getCoreType()).map_error(ReportError)); |
2133 | 0 | } else if (Decl.isType()) { |
2134 | 0 | EXPECTED_TRY(validate(*Decl.getType()).map_error(ReportError)); |
2135 | 0 | } else if (Decl.isAlias()) { |
2136 | 0 | EXPECTED_TRY(validate(Decl.getAlias()).map_error(ReportError)); |
2137 | 0 | const auto &A = Decl.getAlias(); |
2138 | 0 | const auto &Sort = A.getSort(); |
2139 | 0 | if (Sort.isCore()) { |
2140 | 0 | CompCtx.incCoreSortIndexSize(Sort.getCoreSortType()); |
2141 | 0 | } else { |
2142 | 0 | 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 | 0 | if (A.getTargetType() == AST::Component::Alias::TargetType::Outer && |
2146 | 0 | Sort.getSortType() == AST::Component::Sort::SortType::Type) { |
2147 | 0 | CompCtx.carryOuterResource(NewInstIdx, A.getOuter().first, |
2148 | 0 | A.getOuter().second); |
2149 | 0 | } |
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 | 0 | 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 | 0 | } |
2180 | 0 | } else if (Decl.isExportDecl()) { |
2181 | 0 | EXPECTED_TRY(validate(Decl.getExport()).map_error(ReportError)); |
2182 | 0 | } else { |
2183 | 0 | assumingUnreachable(); |
2184 | 0 | } |
2185 | 0 | return {}; |
2186 | 0 | } |
2187 | | |
2188 | | Expect<void> |
2189 | 0 | Validator::validate(const AST::Component::ComponentDecl &Decl) noexcept { |
2190 | 0 | auto ReportError = [](auto E) { |
2191 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_Decl_Component)); |
2192 | 0 | return E; |
2193 | 0 | }; |
2194 | |
|
2195 | 0 | if (Decl.isImportDecl()) { |
2196 | 0 | EXPECTED_TRY(validate(Decl.getImport()).map_error(ReportError)); |
2197 | 0 | } else if (Decl.isInstanceDecl()) { |
2198 | 0 | EXPECTED_TRY(validate(Decl.getInstance()).map_error(ReportError)); |
2199 | 0 | } else { |
2200 | 0 | assumingUnreachable(); |
2201 | 0 | } |
2202 | 0 | return {}; |
2203 | 0 | } |
2204 | | |
2205 | 0 | Expect<void> Validator::validate(const ComponentValType &VT) noexcept { |
2206 | 0 | if (VT.getCode() == ComponentTypeCode::TypeIndex) { |
2207 | 0 | uint32_t Idx = VT.getTypeIndex(); |
2208 | 0 | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2209 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2210 | 0 | spdlog::error(" ComponentValType: type index {} out of bounds"sv, Idx); |
2211 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2212 | 0 | } |
2213 | 0 | const auto *DT = CompCtx.getDefType(Idx); |
2214 | 0 | if (DT != nullptr && !DT->isDefValType() && !DT->isResourceType()) { |
2215 | 0 | spdlog::error(ErrCode::Value::NotADefinedType); |
2216 | 0 | spdlog::error( |
2217 | 0 | " ComponentValType: type index {} is not a defined value type"sv, |
2218 | 0 | Idx); |
2219 | 0 | return Unexpect(ErrCode::Value::NotADefinedType); |
2220 | 0 | } |
2221 | 0 | } |
2222 | 0 | return {}; |
2223 | 0 | } |
2224 | | |
2225 | | Expect<void> |
2226 | 0 | Validator::validate(const AST::Component::DefValType &DVT) noexcept { |
2227 | 0 | if (DVT.isOwnTy()) { |
2228 | 0 | uint32_t Idx = DVT.getOwn().Idx; |
2229 | 0 | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2230 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2231 | 0 | spdlog::error(" DefValType: own type index {} out of bounds"sv, Idx); |
2232 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2233 | 0 | } |
2234 | 0 | if (CompCtx.getResource(Idx) == nullptr) { |
2235 | 0 | spdlog::error(ErrCode::Value::NotADefinedType); |
2236 | 0 | spdlog::error( |
2237 | 0 | " DefValType: own type index {} does not refer to a resource type"sv, |
2238 | 0 | Idx); |
2239 | 0 | return Unexpect(ErrCode::Value::NotADefinedType); |
2240 | 0 | } |
2241 | 0 | } else if (DVT.isBorrowTy()) { |
2242 | 0 | uint32_t Idx = DVT.getBorrow().Idx; |
2243 | 0 | if (Idx >= CompCtx.getSortIndexSize(AST::Component::Sort::SortType::Type)) { |
2244 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
2245 | 0 | spdlog::error(" DefValType: borrow type index {} out of bounds"sv, |
2246 | 0 | Idx); |
2247 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
2248 | 0 | } |
2249 | 0 | if (CompCtx.getResource(Idx) == nullptr) { |
2250 | 0 | spdlog::error(ErrCode::Value::NotADefinedType); |
2251 | 0 | spdlog::error( |
2252 | 0 | " DefValType: borrow type index {} does not refer to a resource type"sv, |
2253 | 0 | Idx); |
2254 | 0 | return Unexpect(ErrCode::Value::NotADefinedType); |
2255 | 0 | } |
2256 | 0 | } else if (DVT.isRecordTy()) { |
2257 | 0 | const auto &Rec = DVT.getRecord(); |
2258 | 0 | 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 | 0 | std::unordered_set<std::string> Seen; |
2264 | 0 | for (const auto < : Rec.LabelTypes) { |
2265 | 0 | if (LT.getLabel().empty()) { |
2266 | 0 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2267 | 0 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2268 | 0 | } |
2269 | 0 | if (!isKebabString(LT.getLabel())) { |
2270 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2271 | 0 | spdlog::error( |
2272 | 0 | " DefValType: record field '{}' is not valid kebab-case"sv, |
2273 | 0 | LT.getLabel()); |
2274 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2275 | 0 | } |
2276 | 0 | if (!Seen.insert(toLowerStr(LT.getLabel())).second) { |
2277 | 0 | spdlog::error(ErrCode::Value::RecordFieldNameConflicts); |
2278 | 0 | spdlog::error(" DefValType: duplicate record field '{}'"sv, |
2279 | 0 | LT.getLabel()); |
2280 | 0 | return Unexpect(ErrCode::Value::RecordFieldNameConflicts); |
2281 | 0 | } |
2282 | 0 | EXPECTED_TRY(validate(LT.getValType())); |
2283 | 0 | } |
2284 | 0 | } else if (DVT.isVariantTy()) { |
2285 | 0 | const auto &Var = DVT.getVariant(); |
2286 | 0 | if (Var.Cases.empty()) { |
2287 | 0 | spdlog::error(ErrCode::Value::VariantMustHaveCase); |
2288 | 0 | return Unexpect(ErrCode::Value::VariantMustHaveCase); |
2289 | 0 | } |
2290 | 0 | std::unordered_set<std::string> Seen; |
2291 | 0 | for (const auto &C : Var.Cases) { |
2292 | 0 | if (C.first.empty()) { |
2293 | 0 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2294 | 0 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2295 | 0 | } |
2296 | 0 | if (!isKebabString(C.first)) { |
2297 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2298 | 0 | spdlog::error( |
2299 | 0 | " DefValType: variant case '{}' is not valid kebab-case"sv, |
2300 | 0 | C.first); |
2301 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2302 | 0 | } |
2303 | 0 | 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 | 0 | if (C.second.has_value()) { |
2309 | 0 | EXPECTED_TRY(validate(*C.second)); |
2310 | 0 | } |
2311 | 0 | } |
2312 | 0 | } else if (DVT.isTupleTy()) { |
2313 | 0 | 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 | 0 | for (const auto &T : DVT.getTuple().Types) { |
2319 | 0 | EXPECTED_TRY(validate(T)); |
2320 | 0 | } |
2321 | 0 | } else if (DVT.isListTy()) { |
2322 | 0 | EXPECTED_TRY(validate(DVT.getList().ValTy)); |
2323 | 0 | } else if (DVT.isOptionTy()) { |
2324 | 0 | EXPECTED_TRY(validate(DVT.getOption().ValTy)); |
2325 | 0 | } else if (DVT.isResultTy()) { |
2326 | 0 | const auto &R = DVT.getResult(); |
2327 | 0 | if (R.ValTy.has_value()) { |
2328 | 0 | EXPECTED_TRY(validate(*R.ValTy)); |
2329 | 0 | } |
2330 | 0 | if (R.ErrTy.has_value()) { |
2331 | 0 | EXPECTED_TRY(validate(*R.ErrTy)); |
2332 | 0 | } |
2333 | 0 | } else if (DVT.isFlagsTy()) { |
2334 | 0 | const auto &Flags = DVT.getFlags(); |
2335 | 0 | 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 | 0 | if (Flags.Labels.size() > 32) { |
2341 | 0 | spdlog::error(ErrCode::Value::CannotHaveMoreThan32Flags); |
2342 | 0 | return Unexpect(ErrCode::Value::CannotHaveMoreThan32Flags); |
2343 | 0 | } |
2344 | 0 | std::unordered_set<std::string> Seen; |
2345 | 0 | for (const auto &L : Flags.Labels) { |
2346 | 0 | if (L.empty()) { |
2347 | 0 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2348 | 0 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2349 | 0 | } |
2350 | 0 | if (!isKebabString(L)) { |
2351 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2352 | 0 | spdlog::error( |
2353 | 0 | " DefValType: flags label '{}' is not valid kebab-case"sv, L); |
2354 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2355 | 0 | } |
2356 | 0 | if (!Seen.insert(toLowerStr(L)).second) { |
2357 | 0 | spdlog::error(ErrCode::Value::FlagNameConflicts); |
2358 | 0 | spdlog::error(" DefValType: duplicate flags label '{}'"sv, L); |
2359 | 0 | return Unexpect(ErrCode::Value::FlagNameConflicts); |
2360 | 0 | } |
2361 | 0 | } |
2362 | 0 | } else if (DVT.isEnumTy()) { |
2363 | 0 | const auto &Enm = DVT.getEnum(); |
2364 | 0 | if (Enm.Labels.empty()) { |
2365 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2366 | 0 | spdlog::error(" DefValType: enum must have at least one label"sv); |
2367 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2368 | 0 | } |
2369 | 0 | std::unordered_set<std::string> Seen; |
2370 | 0 | for (const auto &L : Enm.Labels) { |
2371 | 0 | if (L.empty()) { |
2372 | 0 | spdlog::error(ErrCode::Value::NameCannotBeEmpty); |
2373 | 0 | return Unexpect(ErrCode::Value::NameCannotBeEmpty); |
2374 | 0 | } |
2375 | 0 | if (!isKebabString(L)) { |
2376 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2377 | 0 | spdlog::error( |
2378 | 0 | " DefValType: enum label '{}' is not valid kebab-case"sv, L); |
2379 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2380 | 0 | } |
2381 | 0 | if (!Seen.insert(toLowerStr(L)).second) { |
2382 | 0 | spdlog::error(ErrCode::Value::EnumTagNameConflicts); |
2383 | 0 | spdlog::error(" DefValType: duplicate enum label '{}'"sv, L); |
2384 | 0 | return Unexpect(ErrCode::Value::EnumTagNameConflicts); |
2385 | 0 | } |
2386 | 0 | } |
2387 | 0 | } else if (DVT.isStreamTy()) { |
2388 | 0 | if (DVT.getStream().ValTy.has_value()) { |
2389 | 0 | EXPECTED_TRY(validate(*DVT.getStream().ValTy)); |
2390 | 0 | } |
2391 | 0 | } else if (DVT.isFutureTy()) { |
2392 | 0 | if (DVT.getFuture().ValTy.has_value()) { |
2393 | 0 | EXPECTED_TRY(validate(*DVT.getFuture().ValTy)); |
2394 | 0 | } |
2395 | 0 | } |
2396 | 0 | return {}; |
2397 | 0 | } |
2398 | | |
2399 | 0 | Expect<void> Validator::validate(const AST::Component::FuncType &FT) noexcept { |
2400 | | // Validate param names: kebab-case + unique |
2401 | 0 | std::unordered_set<std::string_view> ParamNames; |
2402 | 0 | for (const auto &P : FT.getParamList()) { |
2403 | 0 | if (!P.getLabel().empty()) { |
2404 | 0 | if (!isKebabString(P.getLabel())) { |
2405 | 0 | spdlog::error(ErrCode::Value::ComponentInvalidName); |
2406 | 0 | spdlog::error( |
2407 | 0 | " FuncType: parameter name '{}' is not valid kebab-case"sv, |
2408 | 0 | P.getLabel()); |
2409 | 0 | return Unexpect(ErrCode::Value::ComponentInvalidName); |
2410 | 0 | } |
2411 | 0 | if (!ParamNames.insert(P.getLabel()).second) { |
2412 | 0 | spdlog::error(ErrCode::Value::ComponentDuplicateName); |
2413 | 0 | spdlog::error(" FuncType: duplicate parameter name '{}'"sv, |
2414 | 0 | P.getLabel()); |
2415 | 0 | return Unexpect(ErrCode::Value::ComponentDuplicateName); |
2416 | 0 | } |
2417 | 0 | } |
2418 | 0 | EXPECTED_TRY(validate(P.getValType())); |
2419 | 0 | } |
2420 | | // Reject transitive use of borrow in results |
2421 | 0 | for (const auto &R : FT.getResultList()) { |
2422 | 0 | EXPECTED_TRY(validate(R.getValType())); |
2423 | 0 | 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 | 0 | } |
2430 | 0 | return {}; |
2431 | 0 | } |
2432 | | |
2433 | | Expect<void> |
2434 | 0 | Validator::validate(const AST::Component::InstanceType &IT) noexcept { |
2435 | | // Instance types are validated with an initially-empty index space. |
2436 | 0 | CompCtx.enterTypeDefinition(); |
2437 | 0 | for (const auto &Decl : IT.getDecl()) { |
2438 | 0 | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
2439 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
2440 | 0 | return E; |
2441 | 0 | })); |
2442 | 0 | } |
2443 | 0 | CompCtx.exitComponent(); |
2444 | 0 | return {}; |
2445 | 0 | } |
2446 | | |
2447 | | Expect<void> |
2448 | 0 | Validator::validate(const AST::Component::ComponentType &CT) noexcept { |
2449 | | // Component types are validated with an initially-empty index space. |
2450 | 0 | CompCtx.enterTypeDefinition(); |
2451 | 0 | for (const auto &Decl : CT.getDecl()) { |
2452 | 0 | EXPECTED_TRY(validate(Decl).map_error([](auto E) { |
2453 | 0 | spdlog::error(ErrInfo::InfoAST(ASTNodeAttr::Comp_DefType)); |
2454 | 0 | return E; |
2455 | 0 | })); |
2456 | 0 | } |
2457 | 0 | CompCtx.exitComponent(); |
2458 | 0 | return {}; |
2459 | 0 | } |
2460 | | |
2461 | | Expect<void> |
2462 | 0 | Validator::validate(const AST::Component::ResourceType &RT) noexcept { |
2463 | | // Resource types are not allowed inside componenttype/instancetype scopes. |
2464 | 0 | if (CompCtx.isTypeDefinitionScope()) { |
2465 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2466 | 0 | spdlog::error(" ResourceType: resource types cannot be defined inside " |
2467 | 0 | "componenttype or instancetype"sv); |
2468 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2469 | 0 | } |
2470 | 0 | if (RT.getDestructor().has_value()) { |
2471 | 0 | uint32_t DtorIdx = *RT.getDestructor(); |
2472 | 0 | if (DtorIdx >= CompCtx.getCoreSortIndexSize( |
2473 | 0 | AST::Component::Sort::CoreSortType::Func)) { |
2474 | 0 | spdlog::error(ErrCode::Value::InvalidIndex); |
2475 | 0 | spdlog::error( |
2476 | 0 | " ResourceType: destructor core func index {} out of bounds"sv, |
2477 | 0 | DtorIdx); |
2478 | 0 | return Unexpect(ErrCode::Value::InvalidIndex); |
2479 | 0 | } |
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 | 0 | const AST::SubType *DtorST = CompCtx.getCoreFunc(DtorIdx); |
2489 | 0 | if (DtorST != nullptr) { |
2490 | 0 | const auto &DtorType = DtorST->getCompositeType(); |
2491 | 0 | const auto &ExpType = CoreFuncType_I32_Void.getCompositeType(); |
2492 | 0 | if (!DtorType.isFunc() || |
2493 | 0 | DtorType.getFuncType() != ExpType.getFuncType()) { |
2494 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
2495 | 0 | spdlog::error( |
2496 | 0 | " ResourceType: destructor core func {} must have signature [i32] -> []"sv, |
2497 | 0 | DtorIdx); |
2498 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
2499 | 0 | } |
2500 | 0 | } |
2501 | 0 | } |
2502 | 0 | return {}; |
2503 | 0 | } |
2504 | | |
2505 | 0 | bool Validator::containsBorrow(const ComponentValType &VT) const noexcept { |
2506 | 0 | if (VT.getCode() == ComponentTypeCode::Borrow) { |
2507 | 0 | return true; |
2508 | 0 | } |
2509 | 0 | if (VT.getCode() != ComponentTypeCode::TypeIndex) { |
2510 | 0 | return false; |
2511 | 0 | } |
2512 | 0 | uint32_t Idx = VT.getTypeIndex(); |
2513 | 0 | const auto *DT = CompCtx.getDefType(Idx); |
2514 | 0 | if (DT == nullptr || !DT->isDefValType()) { |
2515 | 0 | return false; |
2516 | 0 | } |
2517 | 0 | return containsBorrow(DT->getDefValType()); |
2518 | 0 | } |
2519 | | |
2520 | | bool Validator::containsBorrow( |
2521 | 0 | const AST::Component::DefValType &DVT) const noexcept { |
2522 | 0 | if (DVT.isBorrowTy()) { |
2523 | 0 | return true; |
2524 | 0 | } |
2525 | 0 | if (DVT.isRecordTy()) { |
2526 | 0 | for (const auto &F : DVT.getRecord().LabelTypes) { |
2527 | 0 | if (containsBorrow(F.getValType())) { |
2528 | 0 | return true; |
2529 | 0 | } |
2530 | 0 | } |
2531 | 0 | return false; |
2532 | 0 | } |
2533 | 0 | if (DVT.isVariantTy()) { |
2534 | 0 | for (const auto &C : DVT.getVariant().Cases) { |
2535 | 0 | if (C.second.has_value() && containsBorrow(*C.second)) { |
2536 | 0 | return true; |
2537 | 0 | } |
2538 | 0 | } |
2539 | 0 | return false; |
2540 | 0 | } |
2541 | 0 | if (DVT.isListTy()) { |
2542 | 0 | return containsBorrow(DVT.getList().ValTy); |
2543 | 0 | } |
2544 | 0 | if (DVT.isTupleTy()) { |
2545 | 0 | for (const auto &T : DVT.getTuple().Types) { |
2546 | 0 | if (containsBorrow(T)) { |
2547 | 0 | return true; |
2548 | 0 | } |
2549 | 0 | } |
2550 | 0 | return false; |
2551 | 0 | } |
2552 | 0 | if (DVT.isOptionTy()) { |
2553 | 0 | return containsBorrow(DVT.getOption().ValTy); |
2554 | 0 | } |
2555 | 0 | if (DVT.isResultTy()) { |
2556 | 0 | const auto &R = DVT.getResult(); |
2557 | 0 | return (R.ValTy.has_value() && containsBorrow(*R.ValTy)) || |
2558 | 0 | (R.ErrTy.has_value() && containsBorrow(*R.ErrTy)); |
2559 | 0 | } |
2560 | 0 | if (DVT.isStreamTy()) { |
2561 | 0 | return DVT.getStream().ValTy.has_value() && |
2562 | 0 | containsBorrow(*DVT.getStream().ValTy); |
2563 | 0 | } |
2564 | 0 | if (DVT.isFutureTy()) { |
2565 | 0 | return DVT.getFuture().ValTy.has_value() && |
2566 | 0 | containsBorrow(*DVT.getFuture().ValTy); |
2567 | 0 | } |
2568 | 0 | return false; // PrimValType, OwnTy, FlagsTy, EnumTy |
2569 | 0 | } |
2570 | | |
2571 | | } // namespace Validator |
2572 | | } // namespace WasmEdge |