/src/WasmEdge/lib/validator/component_context.cpp
Line | Count | Source |
1 | | // SPDX-License-Identifier: Apache-2.0 |
2 | | // SPDX-FileCopyrightText: Copyright The WasmEdge Authors |
3 | | |
4 | | //===-- component_context.cpp - Component checker -------------------------===// |
5 | | // |
6 | | // The bodies of Context. |
7 | | // |
8 | | //===----------------------------------------------------------------------===// |
9 | | #include "validator/component_context.h" |
10 | | |
11 | | #include "common/component_valtype.h" |
12 | | #include "common/errinfo.h" |
13 | | #include "common/spdlog.h" |
14 | | |
15 | | #include <algorithm> |
16 | | #include <cctype> |
17 | | #include <string> |
18 | | #include <unordered_map> |
19 | | #include <unordered_set> |
20 | | #include <utility> |
21 | | #include <vector> |
22 | | |
23 | | namespace WasmEdge { |
24 | | namespace Validator { |
25 | | namespace Component { |
26 | | |
27 | | using namespace std::literals; |
28 | | // --------------------------------------------------------------------------- |
29 | | // Index-space registration and resolution. |
30 | | // --------------------------------------------------------------------------- |
31 | | |
32 | 10.1k | void Context::addExtern(const ExternInfo &Info) noexcept { |
33 | 10.1k | auto &S = getTop(); |
34 | 10.1k | switch (Info.Kind) { |
35 | 19 | case ExternKind::CoreType: |
36 | 19 | S.addCoreModule(Info.CoreMod); |
37 | 19 | break; |
38 | 351 | case ExternKind::FuncType: |
39 | 351 | S.addFunc(Info.Func); |
40 | 351 | break; |
41 | 380 | case ExternKind::ValueBound: |
42 | 380 | S.addValue(Info.Value); |
43 | 380 | break; |
44 | 8.40k | case ExternKind::TypeBound: |
45 | 8.40k | S.addType(Info.Type); |
46 | 8.40k | break; |
47 | 204 | case ExternKind::InstanceType: |
48 | 204 | S.addInstance(Info.Shape); |
49 | 204 | break; |
50 | 747 | case ExternKind::ComponentType: |
51 | 747 | S.addComponent(Info.Shape); |
52 | 747 | break; |
53 | 10.1k | } |
54 | 10.1k | } |
55 | | |
56 | | Expect<ExternInfo> |
57 | 10.5k | Context::getExtern(const AST::Component::SortIndex &SI) noexcept { |
58 | 10.5k | ExternInfo Info; |
59 | 10.5k | const auto &S = getTop(); |
60 | 10.5k | const auto &Sort = SI.getSort(); |
61 | 10.5k | const uint32_t Idx = SI.getIdx(); |
62 | 10.5k | if (Sort.isCore()) { |
63 | 9 | if (Sort.getCoreSortType() == AST::Component::Sort::CoreSortType::Module) { |
64 | 1 | const auto *Mod = S.getCoreModule(Idx); |
65 | 1 | if (Mod == nullptr) { |
66 | 1 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
67 | 1 | spdlog::error(" Core module index {} out of bounds (size {})."sv, |
68 | 1 | Idx, S.CoreModules.size()); |
69 | 1 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
70 | 1 | } |
71 | 0 | Info.Kind = ExternKind::CoreType; |
72 | 0 | Info.CoreMod = Mod; |
73 | 0 | return Info; |
74 | 1 | } |
75 | 8 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
76 | 8 | spdlog::error( |
77 | 8 | " Core sorts other than module cannot be used at component level."sv); |
78 | 8 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
79 | 9 | } |
80 | 10.5k | switch (Sort.getSortType()) { |
81 | 222 | case AST::Component::Sort::SortType::Func: { |
82 | 222 | const auto *F = S.getFunc(Idx); |
83 | 222 | if (F == nullptr) { |
84 | 27 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
85 | 27 | spdlog::error(" Function index {} out of bounds (size {})."sv, Idx, |
86 | 27 | S.Funcs.size()); |
87 | 27 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
88 | 27 | } |
89 | 195 | Info.Kind = ExternKind::FuncType; |
90 | 195 | Info.Func = *F; |
91 | 195 | return Info; |
92 | 222 | } |
93 | 151 | case AST::Component::Sort::SortType::Value: { |
94 | 151 | if (Idx >= S.Values.size()) { |
95 | 39 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
96 | 39 | spdlog::error(" Value index {} out of bounds (size {})."sv, Idx, |
97 | 39 | S.Values.size()); |
98 | 39 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
99 | 39 | } |
100 | 112 | Info.Kind = ExternKind::ValueBound; |
101 | 112 | Info.Value = S.Values[Idx].Type; |
102 | 112 | return Info; |
103 | 151 | } |
104 | 2.25k | case AST::Component::Sort::SortType::Type: { |
105 | 2.25k | const auto *E = S.getType(Idx); |
106 | 2.25k | if (E == nullptr) { |
107 | 53 | spdlog::error(ErrCode::Value::ComponentTypeIndexOutOfBounds); |
108 | 53 | spdlog::error(" Type index {} out of bounds (size {})."sv, Idx, |
109 | 53 | S.Types.size()); |
110 | 53 | return Unexpect(ErrCode::Value::ComponentTypeIndexOutOfBounds); |
111 | 53 | } |
112 | 2.20k | Info.Kind = ExternKind::TypeBound; |
113 | 2.20k | Info.Type = *E; |
114 | 2.20k | return Info; |
115 | 2.25k | } |
116 | 7.42k | case AST::Component::Sort::SortType::Component: { |
117 | 7.42k | const auto *C = S.getComponent(Idx); |
118 | 7.42k | if (C == nullptr) { |
119 | 32 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
120 | 32 | spdlog::error(" Component index {} out of bounds (size {})."sv, Idx, |
121 | 32 | S.Components.size()); |
122 | 32 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
123 | 32 | } |
124 | 7.38k | Info.Kind = ExternKind::ComponentType; |
125 | 7.38k | Info.Shape = C; |
126 | 7.38k | return Info; |
127 | 7.42k | } |
128 | 526 | case AST::Component::Sort::SortType::Instance: { |
129 | 526 | const auto *I = S.getInstance(Idx); |
130 | 526 | if (I == nullptr) { |
131 | 25 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
132 | 25 | spdlog::error(" Instance index {} out of bounds (size {})."sv, Idx, |
133 | 25 | S.Instances.size()); |
134 | 25 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
135 | 25 | } |
136 | 501 | Info.Kind = ExternKind::InstanceType; |
137 | 501 | Info.Shape = I; |
138 | 501 | return Info; |
139 | 526 | } |
140 | 0 | default: |
141 | 0 | spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds); |
142 | 0 | return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds); |
143 | 10.5k | } |
144 | 10.5k | } |
145 | | |
146 | | // valtype ::= i:<typeidx> | pvt:<primvaltype>; the index must be a defvaltype. |
147 | 4.24M | Expect<void> Context::validate(const ComponentValType &VT) const noexcept { |
148 | 4.24M | if (VT.isPrimValType()) { |
149 | 3.17M | return {}; |
150 | 3.17M | } |
151 | 1.06M | const auto *Entry = getTop().getType(VT.getTypeIndex()); |
152 | 1.06M | if (Entry == nullptr) { |
153 | 109 | spdlog::error(ErrCode::Value::ComponentTypeIndexOutOfBounds); |
154 | 109 | spdlog::error(" Value type index {} out of bounds (size {})."sv, |
155 | 109 | VT.getTypeIndex(), getTop().Types.size()); |
156 | 109 | return Unexpect(ErrCode::Value::ComponentTypeIndexOutOfBounds); |
157 | 109 | } |
158 | 1.06M | if (Entry->getDefValType() == nullptr) { |
159 | 25 | spdlog::error(ErrCode::Value::NotADefinedType); |
160 | 25 | spdlog::error(" Value type index {} does not refer to a value type."sv, |
161 | 25 | VT.getTypeIndex()); |
162 | 25 | return Unexpect(ErrCode::Value::NotADefinedType); |
163 | 25 | } |
164 | 1.06M | return {}; |
165 | 1.06M | } |
166 | | |
167 | | // --------------------------------------------------------------------------- |
168 | | // Naming checks and the named-types rule; the only writer of NameSide. |
169 | | // --------------------------------------------------------------------------- |
170 | | |
171 | | Expect<ExternName> Context::parseExternName(std::string_view Name, |
172 | 10.9k | bool IsImport) const noexcept { |
173 | 10.9k | const auto Position = IsImport ? "Import"sv : "Export"sv; |
174 | 10.9k | ExternName CN; |
175 | 10.9k | EXPECTED_TRY(CN.parse(Name)); |
176 | 10.6k | if (CN.getKind() == ExternName::Kind::Invalid) { |
177 | 0 | spdlog::error(ErrCode::Value::InvalidExternName); |
178 | 0 | spdlog::error(" {} name '{}' is not a valid extern name."sv, Position, |
179 | 0 | Name); |
180 | 0 | return Unexpect(ErrCode::Value::InvalidExternName); |
181 | 0 | } |
182 | 10.6k | return CN; |
183 | 10.6k | } |
184 | | |
185 | | Expect<void> Context::addUniqueName(std::vector<NameRecord> &Names, |
186 | | const NameRecord &N, |
187 | 10.6k | bool IsImport) const noexcept { |
188 | | // Declarator clashes carry their own code; exact duplicates never do. |
189 | 10.6k | const auto SideCode = IsImport ? ErrCode::Value::ComponentImportNameConflict |
190 | 10.6k | : ErrCode::Value::ComponentExportNameConflict; |
191 | 10.6k | const auto ConflictCode = |
192 | 10.6k | getTop().Kind == ScopeKind::Component |
193 | 10.6k | ? SideCode |
194 | 10.6k | : (IsImport ? ErrCode::Value::ComponentDeclImportNameConflict |
195 | 267 | : ErrCode::Value::ComponentDeclExportNameConflict); |
196 | 10.6k | auto ReportClash = [&](ErrCode::Value Code) noexcept { |
197 | 51 | spdlog::error(Code); |
198 | 51 | spdlog::error(" {} name '{}' is not strongly-unique."sv, |
199 | 51 | IsImport ? "Import"sv : "Export"sv, N.Original); |
200 | 51 | return Unexpect(Code); |
201 | 51 | }; |
202 | 17.5k | for (const auto &E : Names) { |
203 | 17.5k | if (E.Original == N.Original) { |
204 | 48 | return ReportClash(SideCode); |
205 | 48 | } |
206 | 17.4k | if (E.Canonical == N.Canonical) { |
207 | 3 | return ReportClash(ConflictCode); |
208 | 3 | } |
209 | 17.4k | } |
210 | 10.6k | Names.push_back(N); |
211 | 10.6k | return {}; |
212 | 10.6k | } |
213 | | |
214 | | Expect<void> Context::checkNameAttributes(const ExternName &CN, |
215 | | Span<const std::string> Impls, |
216 | | Span<const std::string> VSuffixes, |
217 | 10.6k | bool IsInstance) const noexcept { |
218 | 10.6k | for (const auto &V : VSuffixes) { |
219 | 13 | EXPECTED_TRY(CN.checkVersionSuffix(V)); |
220 | 13 | } |
221 | 10.6k | if (Impls.empty()) { |
222 | 10.6k | return {}; |
223 | 10.6k | } |
224 | 2 | for (const auto &I : Impls) { |
225 | 2 | auto Parsed = parseExternName(I, true); |
226 | 2 | if (!Parsed.has_value()) { |
227 | 1 | spdlog::error(ErrCode::Value::ComponentImplementsName); |
228 | 1 | spdlog::error(" `implements` value `{}` is not a valid name"sv, I); |
229 | 1 | return Unexpect(ErrCode::Value::ComponentImplementsName); |
230 | 1 | } |
231 | 1 | if (Parsed->getKind() != ExternName::Kind::InterfaceType) { |
232 | 1 | spdlog::error(ErrCode::Value::ComponentImplementsInterface); |
233 | 1 | spdlog::error(" `implements` value `{}` must be an interface"sv, I); |
234 | 1 | return Unexpect(ErrCode::Value::ComponentImplementsInterface); |
235 | 1 | } |
236 | 1 | } |
237 | 0 | if (CN.getKind() != ExternName::Kind::Label && |
238 | 0 | CN.getKind() != ExternName::Kind::Constructor && |
239 | 0 | CN.getKind() != ExternName::Kind::Method && |
240 | 0 | CN.getKind() != ExternName::Kind::Static) { |
241 | 0 | spdlog::error(ErrCode::Value::ComponentImplementsPlain); |
242 | 0 | spdlog::error(" name `{}` is not valid with `implements`"sv, |
243 | 0 | CN.getOriginalName()); |
244 | 0 | return Unexpect(ErrCode::Value::ComponentImplementsPlain); |
245 | 0 | } |
246 | 0 | if (!IsInstance) { |
247 | 0 | spdlog::error(ErrCode::Value::ComponentImplementsInstance); |
248 | 0 | spdlog::error(" only instances can have an `implements`"sv); |
249 | 0 | return Unexpect(ErrCode::Value::ComponentImplementsInstance); |
250 | 0 | } |
251 | 0 | return {}; |
252 | 0 | } |
253 | | |
254 | | Expect<void> Context::checkAnnotatedName(const ExternName &Name, |
255 | | const ExternInfo &Info, |
256 | 10.5k | bool IsImport) noexcept { |
257 | 10.5k | const auto Kind = Name.getKind(); |
258 | 10.5k | if (Kind != ExternName::Kind::Constructor && |
259 | 10.5k | Kind != ExternName::Kind::Method && Kind != ExternName::Kind::Static) { |
260 | 10.5k | return {}; |
261 | 10.5k | } |
262 | 19 | if (Info.Kind != ExternKind::FuncType || Info.Func.FT == nullptr) { |
263 | 17 | spdlog::error(ErrCode::Value::ComponentIsNotFunc); |
264 | 17 | spdlog::error( |
265 | 17 | " Annotated name '{}' is only allowed on function imports/exports."sv, |
266 | 17 | Name.getOriginalName()); |
267 | 17 | return Unexpect(ErrCode::Value::ComponentIsNotFunc); |
268 | 17 | } |
269 | 2 | const std::string_view ResourceLabel = Name.getDetail().Resource; |
270 | 2 | auto &S = getTop(); |
271 | 2 | const auto &Labels = S.getNameSide(IsImport).ResourceLabels; |
272 | 2 | const auto &Names = S.getNameSide(IsImport).ResourceNames; |
273 | 2 | const auto &FT = *Info.Func.FT; |
274 | | // The signature's resource must be named here as the annotation's label. |
275 | 2 | auto CheckTarget = [&](uint32_t Target) noexcept -> Expect<void> { |
276 | 0 | auto NameIt = Names.find(Target); |
277 | 0 | if (NameIt == Names.end()) { |
278 | 0 | spdlog::error(ErrCode::Value::ComponentResourceNotNamed); |
279 | 0 | spdlog::error(" Resource used in '{}' has no name in this " |
280 | 0 | "context."sv, |
281 | 0 | Name.getOriginalName()); |
282 | 0 | return Unexpect(ErrCode::Value::ComponentResourceNotNamed); |
283 | 0 | } |
284 | 0 | if (NameIt->second != ResourceLabel) { |
285 | 0 | spdlog::error(ErrCode::Value::AnnotatedFuncResourceName); |
286 | 0 | spdlog::error(" '{}' does not match resource '{}'."sv, |
287 | 0 | Name.getOriginalName(), NameIt->second); |
288 | 0 | return Unexpect(ErrCode::Value::AnnotatedFuncResourceName); |
289 | 0 | } |
290 | 0 | return {}; |
291 | 0 | }; |
292 | | |
293 | | // Resolve a valtype to the resource behind its own or borrow handle. |
294 | 2 | auto HandleOf = [this](const QualValType &Q, |
295 | 2 | bool WantOwn) noexcept -> std::optional<uint32_t> { |
296 | 1 | TypeEntry Storage; |
297 | 1 | const auto *Entry = getTypeEntry(Q, Storage); |
298 | 1 | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
299 | 1 | if (Def == nullptr) { |
300 | 1 | return std::nullopt; |
301 | 1 | } |
302 | 0 | const auto &DVT = *Def; |
303 | 0 | uint32_t HandleIdx = 0; |
304 | 0 | if (WantOwn && DVT.isOwnTy()) { |
305 | 0 | HandleIdx = DVT.getOwn().Idx; |
306 | 0 | } else if (!WantOwn && DVT.isBorrowTy()) { |
307 | 0 | HandleIdx = DVT.getBorrow().Idx; |
308 | 0 | } else { |
309 | 0 | return std::nullopt; |
310 | 0 | } |
311 | 0 | const auto *Res = Entry->Home->getType(HandleIdx); |
312 | 0 | if (Res == nullptr || !Res->isResource()) { |
313 | 0 | return std::nullopt; |
314 | 0 | } |
315 | 0 | return applyRemap(Entry->Remap, *Res->ResourceId); |
316 | 0 | }; |
317 | | |
318 | 2 | if (Kind == ExternName::Kind::Constructor) { |
319 | | // Signature first: exactly one result of (own T) or (result (own T) e?). |
320 | 2 | if (!FT.getResult().has_value()) { |
321 | 1 | spdlog::error(ErrCode::Value::AnnotatedCtorReturnOne); |
322 | 1 | spdlog::error(" Constructor '{}' should return one value."sv, |
323 | 1 | Name.getOriginalName()); |
324 | 1 | return Unexpect(ErrCode::Value::AnnotatedCtorReturnOne); |
325 | 1 | } |
326 | 1 | QualValType Q{*FT.getResult(), Info.Func.Home, Info.Func.Remap}; |
327 | 1 | auto Target = HandleOf(Q, true); |
328 | 1 | if (!Target.has_value()) { |
329 | | // Unwrap (result (own T) e?). |
330 | 1 | TypeEntry Storage; |
331 | 1 | const auto *Entry = getTypeEntry(Q, Storage); |
332 | 1 | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
333 | 1 | if (Def != nullptr && Def->isResultTy()) { |
334 | 0 | const auto &Res = Def->getResult(); |
335 | 0 | if (Res.ValTy.has_value()) { |
336 | 0 | Target = HandleOf({*Res.ValTy, Entry->Home, Entry->Remap}, true); |
337 | 0 | } |
338 | 0 | } |
339 | 1 | } |
340 | 1 | if (!Target.has_value()) { |
341 | 1 | spdlog::error(ErrCode::Value::AnnotatedCtorReturn); |
342 | 1 | spdlog::error(" Constructor '{}' must return (own {})."sv, |
343 | 1 | Name.getOriginalName(), ResourceLabel); |
344 | 1 | return Unexpect(ErrCode::Value::AnnotatedCtorReturn); |
345 | 1 | } |
346 | 0 | EXPECTED_TRY(CheckTarget(*Target)); |
347 | 0 | } else if (Kind == ExternName::Kind::Method) { |
348 | 0 | if (FT.getParamList().empty()) { |
349 | 0 | spdlog::error(ErrCode::Value::AnnotatedMethodArgs); |
350 | 0 | spdlog::error(" Method '{}' should have at least one argument."sv, |
351 | 0 | Name.getOriginalName()); |
352 | 0 | return Unexpect(ErrCode::Value::AnnotatedMethodArgs); |
353 | 0 | } |
354 | 0 | const auto Params = FT.getParamList(); |
355 | 0 | const auto &Self = Params[0]; |
356 | 0 | if (Self.getLabel() != "self"sv) { |
357 | 0 | spdlog::error(ErrCode::Value::AnnotatedMethodSelf); |
358 | 0 | spdlog::error( |
359 | 0 | " Method '{}' should have a first argument called `self`."sv, |
360 | 0 | Name.getOriginalName()); |
361 | 0 | return Unexpect(ErrCode::Value::AnnotatedMethodSelf); |
362 | 0 | } |
363 | 0 | auto Target = |
364 | 0 | HandleOf({Self.getValType(), Info.Func.Home, Info.Func.Remap}, false); |
365 | 0 | if (!Target.has_value()) { |
366 | 0 | spdlog::error(ErrCode::Value::AnnotatedMethodBorrow); |
367 | 0 | spdlog::error( |
368 | 0 | " Method '{}' should take a first argument of (borrow {})."sv, |
369 | 0 | Name.getOriginalName(), ResourceLabel); |
370 | 0 | return Unexpect(ErrCode::Value::AnnotatedMethodBorrow); |
371 | 0 | } |
372 | 0 | EXPECTED_TRY(CheckTarget(*Target)); |
373 | 0 | } else { |
374 | 0 | const bool LabelKnown = Labels.count(std::string(ResourceLabel)) != 0; |
375 | 0 | if (!LabelKnown) { |
376 | 0 | spdlog::error(ErrCode::Value::AnnotatedStaticUnknown); |
377 | 0 | spdlog::error( |
378 | 0 | " Static '{}' resource name is not known in this context."sv, |
379 | 0 | Name.getOriginalName()); |
380 | 0 | return Unexpect(ErrCode::Value::AnnotatedStaticUnknown); |
381 | 0 | } |
382 | 0 | } |
383 | 0 | return {}; |
384 | 2 | } |
385 | | |
386 | | void Context::addResourceLabel(const ExternName &Name, const ExternInfo &Info, |
387 | 10.0k | bool IsImport) noexcept { |
388 | 10.0k | if (Name.getKind() == ExternName::Kind::Label && |
389 | 9.87k | Info.Kind == ExternKind::TypeBound && Info.Type.isResource()) { |
390 | 6.05k | auto &S = getTop(); |
391 | 6.05k | auto &Labels = S.getNameSide(IsImport).ResourceLabels; |
392 | 6.05k | auto &Names = S.getNameSide(IsImport).ResourceNames; |
393 | 6.05k | Labels.emplace(std::string(Name.getOriginalName()), *Info.Type.ResourceId); |
394 | 6.05k | Names.emplace(*Info.Type.ResourceId, std::string(Name.getOriginalName())); |
395 | 6.05k | } |
396 | 10.0k | } |
397 | | |
398 | | Expect<void> Context::checkNamedTypesRule(const ExternInfo &Info, |
399 | 10.1k | bool IsImport) noexcept { |
400 | | // Instance-type declarations do not enforce the named-types rule. |
401 | 10.1k | if (getTop().Kind == ScopeKind::InstanceType) { |
402 | 133 | return {}; |
403 | 133 | } |
404 | 9.97k | if (introduceExternTypes(Info, IsImport)) { |
405 | 9.93k | return {}; |
406 | 9.93k | } |
407 | 40 | ErrCode::Value Code; |
408 | 40 | switch (Info.Kind) { |
409 | 5 | case ExternKind::FuncType: |
410 | 5 | Code = IsImport ? ErrCode::Value::ComponentFuncNotValidImport |
411 | 5 | : ErrCode::Value::ComponentFuncNotValidExport; |
412 | 5 | break; |
413 | 0 | case ExternKind::InstanceType: |
414 | 0 | Code = IsImport ? ErrCode::Value::ComponentInstanceNotValidImport |
415 | 0 | : ErrCode::Value::ComponentInstanceNotValidExport; |
416 | 0 | break; |
417 | 35 | default: |
418 | 35 | Code = IsImport ? ErrCode::Value::ComponentTypeNotValidImport |
419 | 35 | : ErrCode::Value::ComponentTypeNotValidExport; |
420 | 35 | break; |
421 | 40 | } |
422 | 40 | spdlog::error(Code); |
423 | 40 | spdlog::error( |
424 | 40 | " A referenced type or resource was not introduced by a preceding " |
425 | 40 | "{}."sv, |
426 | 40 | IsImport ? "import"sv : "import or export"sv); |
427 | 40 | return Unexpect(Code); |
428 | 40 | } |
429 | | |
430 | | // --------------------------------------------------------------------------- |
431 | | // Declaring an import or an export; name errors precede ascription errors. |
432 | | // --------------------------------------------------------------------------- |
433 | | |
434 | | Expect<ExternInfo> |
435 | | Context::addImport(std::string_view Name, const ExternInfo &Resolved, |
436 | | Span<const std::string> Impls, |
437 | 7.77k | Span<const std::string> VSuffixes) noexcept { |
438 | | // Each instance import mints fresh identities for its declared resources. |
439 | 7.77k | ExternInfo Info = Resolved; |
440 | 7.77k | if (Info.Kind == ExternKind::InstanceType) { |
441 | 13 | Info.Shape = copyDeclaredResources(Info.Shape, true); |
442 | 13 | } |
443 | 7.77k | EXPECTED_TRY(ExternName CN, parseExternName(Name, true)); |
444 | 7.61k | EXPECTED_TRY(checkNameAttributes(CN, Impls, VSuffixes, |
445 | 7.59k | Info.Kind == ExternKind::InstanceType)); |
446 | 7.59k | EXPECTED_TRY(addUniqueName(getTop().ImportSide.Names, NameRecord(CN), true)); |
447 | 7.56k | addExtern(Info); |
448 | 7.56k | EXPECTED_TRY(checkNamedTypesRule(Info, true)); |
449 | 7.55k | EXPECTED_TRY(checkAnnotatedName(CN, Info, true)); |
450 | 7.54k | addResourceLabel(CN, Info, true); |
451 | 7.54k | return Info; |
452 | 7.55k | } |
453 | | |
454 | | Expect<ExternName> |
455 | | Context::registerExportName(std::string_view Name, bool IsInstance, |
456 | | Span<const std::string> Impls, |
457 | 2.56k | Span<const std::string> VSuffixes) noexcept { |
458 | 2.56k | EXPECTED_TRY(ExternName CN, parseExternName(Name, false)); |
459 | 2.41k | EXPECTED_TRY(checkNameAttributes(CN, Impls, VSuffixes, IsInstance)); |
460 | 2.41k | EXPECTED_TRY(addUniqueName(getTop().ExportSide.Names, NameRecord(CN), false)); |
461 | 2.39k | return CN; |
462 | 2.41k | } |
463 | | |
464 | | Expect<ExternInfo> |
465 | | Context::addExport(const ExternName &CN, const ExternInfo &Inferred, |
466 | 2.38k | const std::optional<ExternInfo> &Ascribed) noexcept { |
467 | 2.38k | ExternInfo Result = Inferred; |
468 | 2.38k | if (Ascribed.has_value()) { |
469 | 80 | ResourceMap Subst; |
470 | 80 | if (!Matcher::matchExtern(*this, Inferred, *Ascribed, Subst)) { |
471 | 7 | spdlog::error(ErrCode::Value::ExportAscriptionIncompatible); |
472 | 7 | spdlog::error( |
473 | 7 | " Ascribed type of export '{}' is not compatible with the " |
474 | 7 | "exported definition."sv, |
475 | 7 | CN.getOriginalName()); |
476 | 7 | return Unexpect(ErrCode::Value::ExportAscriptionIncompatible); |
477 | 7 | } |
478 | 73 | Result = *Ascribed; |
479 | 73 | } |
480 | | // An export of a type re-introduces it under a fresh naming identity. |
481 | 2.38k | if (Result.Kind == ExternKind::TypeBound) { |
482 | 1.54k | Result.Type.NameId = nextNameId(); |
483 | 1.54k | } |
484 | 2.38k | if (Result.Kind == ExternKind::InstanceType) { |
485 | 194 | Result.Shape = copyDeclaredResources(Result.Shape, false); |
486 | 194 | } |
487 | 2.38k | addExtern(Result); |
488 | 2.38k | EXPECTED_TRY(checkNamedTypesRule(Result, false)); |
489 | 2.35k | EXPECTED_TRY(checkAnnotatedName(CN, Result, false)); |
490 | 2.34k | addResourceLabel(CN, Result, false); |
491 | 2.34k | return Result; |
492 | 2.35k | } |
493 | | |
494 | | // --------------------------------------------------------------------------- |
495 | | // Canonical options. They resolve core index spaces. |
496 | | // --------------------------------------------------------------------------- |
497 | | |
498 | | ValType Context::getCanonPtrType( |
499 | 34.8k | const AST::Component::Canonical &Canon) const noexcept { |
500 | 34.8k | for (const auto &Opt : Canon.getOptions()) { |
501 | 15.4k | if (Opt.getCode() == ComponentCanonOptCode::Memory) { |
502 | 39 | const uint32_t Idx = Opt.getIndex(); |
503 | 39 | if (Idx < getTop().CoreMemories.size()) { |
504 | 0 | const auto *Mem = getTop().CoreMemories[Idx]; |
505 | 0 | return ValType(Mem != nullptr && Mem->getLimit().is64() |
506 | 0 | ? TypeCode::I64 |
507 | 0 | : TypeCode::I32); |
508 | 0 | } |
509 | 39 | } |
510 | 15.4k | } |
511 | 34.8k | return ValType(TypeCode::I32); |
512 | 34.8k | } |
513 | | |
514 | | Expect<void> Context::checkOptions(const AST::Component::Canonical &Canon, |
515 | 17.4k | bool IsLift) const noexcept { |
516 | 17.4k | const auto &S = getTop(); |
517 | 17.4k | const ValType I32V{TypeCode::I32}; |
518 | 17.4k | bool SeenEncoding = false, SeenMemory = false, SeenRealloc = false, |
519 | 17.4k | SeenPostReturn = false, SeenAsync = false, SeenCallback = false; |
520 | | // The pointer width of realloc follows the selected memory. |
521 | 17.4k | const ValType Ptr = getCanonPtrType(Canon); |
522 | 17.4k | for (const auto &Opt : Canon.getOptions()) { |
523 | 7.75k | switch (Opt.getCode()) { |
524 | 4.06k | case ComponentCanonOptCode::Encode_UTF8: |
525 | 5.82k | case ComponentCanonOptCode::Encode_UTF16: |
526 | 6.07k | case ComponentCanonOptCode::Encode_Latin1: |
527 | 6.07k | if (SeenEncoding) { |
528 | 8 | spdlog::error(ErrCode::Value::CanonEncodingConflict); |
529 | 8 | spdlog::error(" Duplicate string-encoding canonical option."sv); |
530 | 8 | return Unexpect(ErrCode::Value::CanonEncodingConflict); |
531 | 8 | } |
532 | 6.06k | SeenEncoding = true; |
533 | 6.06k | break; |
534 | 21 | case ComponentCanonOptCode::Memory: { |
535 | 21 | if (SeenMemory) { |
536 | 0 | spdlog::error(ErrCode::Value::CanonMemoryDuplicated); |
537 | 0 | spdlog::error(" Duplicate memory canonical option."sv); |
538 | 0 | return Unexpect(ErrCode::Value::CanonMemoryDuplicated); |
539 | 0 | } |
540 | 21 | SeenMemory = true; |
541 | 21 | const uint32_t Idx = Opt.getIndex(); |
542 | 21 | if (Idx >= S.CoreMemories.size()) { |
543 | 21 | spdlog::error(ErrCode::Value::ComponentMemoryIndexOutOfBounds); |
544 | 21 | spdlog::error(" Canonical option memory index {} out of bounds."sv, |
545 | 21 | Idx); |
546 | 21 | return Unexpect(ErrCode::Value::ComponentMemoryIndexOutOfBounds); |
547 | 21 | } |
548 | 0 | break; |
549 | 21 | } |
550 | 2 | case ComponentCanonOptCode::Realloc: { |
551 | 2 | if (SeenRealloc) { |
552 | 0 | spdlog::error(ErrCode::Value::CanonReallocDuplicated); |
553 | 0 | spdlog::error(" Duplicate realloc canonical option."sv); |
554 | 0 | return Unexpect(ErrCode::Value::CanonReallocDuplicated); |
555 | 0 | } |
556 | 2 | SeenRealloc = true; |
557 | 2 | const auto *Func = S.getCoreFunc(Opt.getIndex()); |
558 | 2 | if (Func == nullptr) { |
559 | 1 | spdlog::error(ErrCode::Value::InvalidIndex); |
560 | 1 | spdlog::error( |
561 | 1 | " Canonical option realloc function index {} out of bounds."sv, |
562 | 1 | Opt.getIndex()); |
563 | 1 | return Unexpect(ErrCode::Value::InvalidIndex); |
564 | 1 | } |
565 | | // realloc has type [ptr ptr ptr ptr] -> [ptr] for the selected memory. |
566 | 1 | const std::vector<ValType> ReallocParams(4, Ptr); |
567 | 1 | const std::vector<ValType> ReallocResults(1, Ptr); |
568 | 1 | const auto &CT = Func->getCompositeType(); |
569 | 1 | if (!CT.isFunc() || CT.getFuncType().getParamTypes() != ReallocParams || |
570 | 1 | CT.getFuncType().getReturnTypes() != ReallocResults) { |
571 | 1 | spdlog::error(ErrCode::Value::CanonReallocSignature); |
572 | 1 | spdlog::error( |
573 | 1 | " realloc must have type [ptr ptr ptr ptr] -> [ptr]."sv); |
574 | 1 | return Unexpect(ErrCode::Value::CanonReallocSignature); |
575 | 1 | } |
576 | 0 | break; |
577 | 1 | } |
578 | 1.07k | case ComponentCanonOptCode::PostReturn: |
579 | 1.07k | if (!IsLift) { |
580 | 1 | spdlog::error(ErrCode::Value::CanonPostReturnOnLower); |
581 | 1 | spdlog::error(" post-return cannot be specified for lowerings."sv); |
582 | 1 | return Unexpect(ErrCode::Value::CanonPostReturnOnLower); |
583 | 1 | } |
584 | 1.07k | if (SeenPostReturn) { |
585 | 1 | spdlog::error(ErrCode::Value::CanonPostReturnDuplicated); |
586 | 1 | spdlog::error(" post-return is specified more than once."sv); |
587 | 1 | return Unexpect(ErrCode::Value::CanonPostReturnDuplicated); |
588 | 1 | } |
589 | 1.07k | SeenPostReturn = true; |
590 | | // The signature is checked by the caller, once the flat type is known. |
591 | 1.07k | break; |
592 | 567 | case ComponentCanonOptCode::Async: |
593 | 567 | if (SeenAsync) { |
594 | 1 | spdlog::error(ErrCode::Value::CanonAsyncDuplicated); |
595 | 1 | spdlog::error(" async is specified more than once."sv); |
596 | 1 | return Unexpect(ErrCode::Value::CanonAsyncDuplicated); |
597 | 1 | } |
598 | 566 | SeenAsync = true; |
599 | 566 | break; |
600 | 11 | case ComponentCanonOptCode::Callback: { |
601 | | // `callback` may only appear on `canon lift`, and only with `async`. |
602 | 11 | if (!IsLift) { |
603 | 1 | spdlog::error(ErrCode::Value::CanonCallbackOnLower); |
604 | 1 | spdlog::error(" callback cannot be specified for lowerings."sv); |
605 | 1 | return Unexpect(ErrCode::Value::CanonCallbackOnLower); |
606 | 1 | } |
607 | 10 | if (SeenCallback) { |
608 | 0 | spdlog::error(ErrCode::Value::CanonCallbackDuplicated); |
609 | 0 | spdlog::error(" callback is specified more than once."sv); |
610 | 0 | return Unexpect(ErrCode::Value::CanonCallbackDuplicated); |
611 | 0 | } |
612 | 10 | SeenCallback = true; |
613 | 10 | const auto *Func = S.getCoreFunc(Opt.getIndex()); |
614 | 10 | if (Func == nullptr) { |
615 | 9 | spdlog::error(ErrCode::Value::InvalidIndex); |
616 | 9 | spdlog::error(" Canonical option callback function index {} out " |
617 | 9 | "of bounds."sv, |
618 | 9 | Opt.getIndex()); |
619 | 9 | return Unexpect(ErrCode::Value::InvalidIndex); |
620 | 9 | } |
621 | | // callback has type [i32 i32 i32] -> [i32]. |
622 | 1 | const std::vector<ValType> CallbackParams(3, I32V); |
623 | 1 | const std::vector<ValType> CallbackResults(1, I32V); |
624 | 1 | const auto &CT = Func->getCompositeType(); |
625 | 1 | if (!CT.isFunc() || CT.getFuncType().getParamTypes() != CallbackParams || |
626 | 1 | CT.getFuncType().getReturnTypes() != CallbackResults) { |
627 | 1 | spdlog::error(ErrCode::Value::CanonCallbackSignature); |
628 | 1 | spdlog::error(" callback must have type [i32 i32 i32] -> [i32]."sv); |
629 | 1 | return Unexpect(ErrCode::Value::CanonCallbackSignature); |
630 | 1 | } |
631 | 0 | break; |
632 | 1 | } |
633 | 0 | default: |
634 | 0 | spdlog::error(ErrCode::Value::UnknownCanonicalOption); |
635 | 0 | return Unexpect(ErrCode::Value::UnknownCanonicalOption); |
636 | 7.75k | } |
637 | 7.75k | } |
638 | 17.4k | if (SeenRealloc && !SeenMemory) { |
639 | 0 | spdlog::error(ErrCode::Value::CanonMemoryRequired); |
640 | 0 | spdlog::error(" realloc requires the memory canonical option."sv); |
641 | 0 | return Unexpect(ErrCode::Value::CanonMemoryRequired); |
642 | 0 | } |
643 | 17.4k | return {}; |
644 | 17.4k | } |
645 | | |
646 | | Expect<void> |
647 | | Context::checkRequiredOptions(const AST::Component::Canonical &Canon, |
648 | | bool NeedMemory, bool NeedRealloc, |
649 | 17.3k | std::string_view What) const noexcept { |
650 | 17.3k | if (NeedMemory && !Canon.hasOption(ComponentCanonOptCode::Memory)) { |
651 | 126 | spdlog::error(ErrCode::Value::CanonMemoryRequired); |
652 | 126 | spdlog::error(" {} requires the memory option."sv, What); |
653 | 126 | return Unexpect(ErrCode::Value::CanonMemoryRequired); |
654 | 126 | } |
655 | 17.2k | if (NeedRealloc && !Canon.hasOption(ComponentCanonOptCode::Realloc)) { |
656 | 0 | spdlog::error(ErrCode::Value::CanonReallocRequired); |
657 | 0 | spdlog::error(" {} requires the realloc option."sv, What); |
658 | 0 | return Unexpect(ErrCode::Value::CanonReallocRequired); |
659 | 0 | } |
660 | 17.2k | return {}; |
661 | 17.2k | } |
662 | | |
663 | | // --------------------------------------------------------------------------- |
664 | | // Instantiation and the substitution applied to the views it creates. |
665 | | // --------------------------------------------------------------------------- |
666 | | |
667 | | Expect<const Shape *> Context::instantiateComponentShape( |
668 | | const Shape &CI, |
669 | | Span<const AST::Component::InstantiateArg<AST::Component::SortIndex>> |
670 | 975k | Args) noexcept { |
671 | | // Resolve the arguments. The names must be unique. |
672 | 975k | std::unordered_map<std::string_view, ExternInfo> ArgMap; |
673 | 975k | for (const auto &Arg : Args) { |
674 | 7.38k | EXPECTED_TRY(auto Info, getExtern(Arg.getIndex())); |
675 | 7.33k | if (!ArgMap.emplace(Arg.getName(), Info).second) { |
676 | 3 | spdlog::error(ErrCode::Value::ComponentDuplicateArg); |
677 | 3 | spdlog::error(" Duplicate instantiation argument '{}'."sv, |
678 | 3 | Arg.getName()); |
679 | 3 | return Unexpect(ErrCode::Value::ComponentDuplicateArg); |
680 | 3 | } |
681 | | // Values are consumed by being passed as arguments. |
682 | 7.33k | if (!Arg.getIndex().getSort().isCore() && |
683 | 7.33k | Arg.getIndex().getSort().getSortType() == |
684 | 7.33k | AST::Component::Sort::SortType::Value) { |
685 | 0 | EXPECTED_TRY(getTop().consumeValue(Arg.getIndex().getIdx())); |
686 | 0 | } |
687 | 7.33k | } |
688 | | // Match every import; Subst accumulates the resource substitution. |
689 | 975k | ResourceMap Subst; |
690 | 975k | for (const auto &[Name, Req] : CI.Imports) { |
691 | 1 | auto It = ArgMap.find(Name); |
692 | 1 | if (It == ArgMap.end()) { |
693 | 1 | spdlog::error(ErrCode::Value::ComponentMissingImport); |
694 | 1 | spdlog::error(" Missing instantiation argument '{}'."sv, Name); |
695 | 1 | return Unexpect(ErrCode::Value::ComponentMissingImport); |
696 | 1 | } |
697 | 0 | if (auto Res = Matcher::matchExtern(*this, It->second, Req, Subst); !Res) { |
698 | 0 | spdlog::error(Res.error()); |
699 | 0 | spdlog::error(" Instantiation argument '{}' has an incompatible " |
700 | 0 | "type."sv, |
701 | 0 | Name); |
702 | 0 | return Unexpect(Res.error()); |
703 | 0 | } |
704 | 0 | } |
705 | | |
706 | | // Combined remap: substituted imports + freshened defined resources. |
707 | 975k | std::unordered_set<uint32_t> Reachable; |
708 | 975k | for (const auto &[Name, E] : CI.Exports) { |
709 | 3.45k | collectResources(E, Reachable); |
710 | 3.45k | } |
711 | 975k | auto *Node = addResourceMap(); |
712 | 975k | Node->Map = Subst.Map; |
713 | 975k | for (const uint32_t Id : Reachable) { |
714 | 0 | if (Node->Map.count(Id) != 0) { |
715 | 0 | continue; |
716 | 0 | } |
717 | 0 | const auto &Entry = getResource(Id); |
718 | 0 | if (!Entry.FromImport && CI.DeclScope != nullptr && |
719 | 0 | isDeclaredIn(Id, *CI.DeclScope)) { |
720 | | // A fresh resource belongs to the created instance: no definition body. |
721 | 0 | Node->Map.emplace(Id, addResource(nullptr, &getTop(), false)); |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | 975k | const auto *Result = copyInstanceExports(CI, Node); |
726 | 975k | ExternInfo Probe; |
727 | 975k | Probe.Kind = ExternKind::InstanceType; |
728 | 975k | Probe.Shape = Result; |
729 | 975k | EXPECTED_TRY(checkTypeLimits(Probe)); |
730 | 975k | return Result; |
731 | 975k | } |
732 | | |
733 | | const Shape *Context::copyDeclaredResources(const Shape *Inst, |
734 | 207 | bool FromImport) noexcept { |
735 | | // Only declaration-built shapes carry their own declared resources. |
736 | 207 | if (Inst == nullptr || Inst->DeclScope == nullptr || |
737 | 207 | Inst->DeclScope->Kind != ScopeKind::InstanceType) { |
738 | 194 | return Inst; |
739 | 194 | } |
740 | 13 | std::unordered_set<uint32_t> Ids; |
741 | 13 | ExternInfo Probe; |
742 | 13 | Probe.Kind = ExternKind::InstanceType; |
743 | 13 | Probe.Shape = Inst; |
744 | 13 | collectResources(Probe, Ids); |
745 | 13 | auto *Node = addResourceMap(); |
746 | 13 | for (const uint32_t Id : Ids) { |
747 | 0 | if (isDeclaredIn(Id, *Inst->DeclScope)) { |
748 | 0 | Node->Map.emplace(Id, addResource(nullptr, &getTop(), FromImport)); |
749 | 0 | } |
750 | 0 | } |
751 | 13 | if (Node->Map.empty()) { |
752 | 13 | return Inst; |
753 | 13 | } |
754 | 0 | std::unordered_map<const Shape *, const Shape *> Copies; |
755 | 0 | return copyShape(Inst, Node, Copies); |
756 | 13 | } |
757 | | |
758 | | const Shape *Context::copyInstanceExports(const Shape &CI, |
759 | 975k | const ResourceMap *Node) noexcept { |
760 | 975k | std::unordered_map<const Shape *, const Shape *> Copies; |
761 | 975k | auto *Result = addShape(); |
762 | 975k | Result->DeclScope = CI.DeclScope; |
763 | 975k | for (const auto &[Name, E] : CI.Exports) { |
764 | 3.45k | Result->Exports.emplace(Name, copyExtern(E, Node, Copies)); |
765 | 3.45k | Result->ExportOrder.emplace_back(Name); |
766 | 3.45k | } |
767 | 975k | return Result; |
768 | 975k | } |
769 | | |
770 | | // --------------------------------------------------------------------------- |
771 | | // Resolution: the shared substrate of every walk below. |
772 | | // --------------------------------------------------------------------------- |
773 | | |
774 | | const TypeEntry *Context::getTypeEntry(const QualValType &Q, |
775 | 10.5M | TypeEntry &Storage) noexcept { |
776 | 10.5M | if (Q.VT.isPrimValType() || Q.Home == nullptr) { |
777 | 7.21M | return nullptr; |
778 | 7.21M | } |
779 | 3.37M | const auto *Entry = Q.Home->getType(Q.VT.getTypeIndex()); |
780 | 3.37M | if (Entry == nullptr) { |
781 | 24 | return nullptr; |
782 | 24 | } |
783 | 3.37M | Storage = *Entry; |
784 | 3.37M | if (Entry->isResource()) { |
785 | 10 | Storage.ResourceId = applyRemap(Q.Remap, *Entry->ResourceId); |
786 | 10 | } |
787 | 3.37M | Storage.Remap = composeRemap(Q.Remap, Entry->Remap); |
788 | 3.37M | return &Storage; |
789 | 3.37M | } |
790 | | |
791 | | // The primitive behind a valtype, through prim-alias indirections. |
792 | | std::optional<PrimValType> |
793 | 47.8k | Context::getPrimValType(const QualValType &Q) noexcept { |
794 | 47.8k | if (Q.VT.isPrimValType()) { |
795 | 35.3k | return Q.VT.getPrimValType(); |
796 | 35.3k | } |
797 | 12.5k | TypeEntry Storage; |
798 | 12.5k | const auto *Entry = getTypeEntry(Q, Storage); |
799 | 12.5k | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
800 | 12.5k | if (Def == nullptr || !Def->isPrimValType()) { |
801 | 12.5k | return std::nullopt; |
802 | 12.5k | } |
803 | 40 | return Def->getPrimValType(); |
804 | 12.5k | } |
805 | | |
806 | | std::optional<uint32_t> Context::getResourceId(const Scope *Home, |
807 | | const ResourceMap *Remap, |
808 | 0 | uint32_t Idx) const noexcept { |
809 | 0 | if (Home == nullptr) { |
810 | 0 | return std::nullopt; |
811 | 0 | } |
812 | 0 | const auto *Entry = Home->getType(Idx); |
813 | 0 | if (Entry == nullptr || !Entry->isResource()) { |
814 | 0 | return std::nullopt; |
815 | 0 | } |
816 | 0 | return applyRemap(Remap, *Entry->ResourceId); |
817 | 0 | } |
818 | | |
819 | | // --------------------------------------------------------------------------- |
820 | | // Resource-id walks. |
821 | | // --------------------------------------------------------------------------- |
822 | | |
823 | 1.88M | bool Context::hasBorrow(const QualValType &Q) noexcept { |
824 | 1.88M | TypeEntry Storage; |
825 | 1.88M | const auto *Entry = getTypeEntry(Q, Storage); |
826 | 1.88M | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
827 | 1.88M | if (Def == nullptr || Def->isPrimValType()) { |
828 | 924k | return false; |
829 | 924k | } |
830 | 955k | const auto &D = *Def; |
831 | 955k | if (D.isBorrowTy()) { |
832 | 2 | return true; |
833 | 2 | } |
834 | | // Stream and future payloads are borrow-free, so the containers suffice. |
835 | 955k | bool Found = false; |
836 | 1.12M | forEachValType(D, [&](const ComponentValType &VT) noexcept { |
837 | 1.12M | Found = Found || hasBorrow({VT, Entry->Home, Entry->Remap}); |
838 | 1.12M | }); |
839 | 955k | return Found; |
840 | 955k | } |
841 | | |
842 | 36.2k | bool Context::hasHandle(const QualValType &Q) noexcept { |
843 | 36.2k | if (Q.VT.isPrimValType()) { |
844 | 20.0k | return Q.VT.getPrimValType() == PrimValType::ErrorContext; |
845 | 20.0k | } |
846 | 16.2k | TypeEntry Storage; |
847 | 16.2k | const auto *Entry = getTypeEntry(Q, Storage); |
848 | 16.2k | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
849 | 16.2k | if (Def == nullptr) { |
850 | 0 | return false; |
851 | 0 | } |
852 | 16.2k | const auto &D = *Def; |
853 | 16.2k | if (D.isPrimValType()) { |
854 | 751 | return D.getPrimValType() == PrimValType::ErrorContext; |
855 | 751 | } |
856 | 15.5k | if (D.isOwnTy() || D.isBorrowTy() || D.isStreamTy() || D.isFutureTy()) { |
857 | 4 | return true; |
858 | 4 | } |
859 | 15.5k | bool Found = false; |
860 | 21.1k | forEachValType(D, [&](const ComponentValType &VT) noexcept { |
861 | 21.1k | Found = Found || hasHandle({VT, Entry->Home, Entry->Remap}); |
862 | 21.1k | }); |
863 | 15.5k | return Found; |
864 | 15.5k | } |
865 | | |
866 | | void Context::collectResources(const ExternInfo &Info, |
867 | 3.47k | std::unordered_set<uint32_t> &Out) noexcept { |
868 | 3.47k | switch (Info.Kind) { |
869 | 0 | case ExternKind::CoreType: |
870 | 0 | return; |
871 | 0 | case ExternKind::FuncType: { |
872 | 0 | if (Info.Func.FT == nullptr) { |
873 | 0 | return; |
874 | 0 | } |
875 | 0 | for (const auto &P : Info.Func.FT->getParamList()) { |
876 | 0 | collectResources({P.getValType(), Info.Func.Home, Info.Func.Remap}, Out); |
877 | 0 | } |
878 | 0 | if (const auto &R = Info.Func.FT->getResult(); R.has_value()) { |
879 | 0 | collectResources({*R, Info.Func.Home, Info.Func.Remap}, Out); |
880 | 0 | } |
881 | 0 | return; |
882 | 0 | } |
883 | 0 | case ExternKind::ValueBound: |
884 | 0 | collectResources(Info.Value, Out); |
885 | 0 | return; |
886 | 0 | case ExternKind::TypeBound: { |
887 | 0 | const auto &E = Info.Type; |
888 | 0 | if (E.isResource()) { |
889 | 0 | Out.insert(*E.ResourceId); |
890 | 0 | return; |
891 | 0 | } |
892 | 0 | if (E.Inst != nullptr) { |
893 | 0 | for (const auto &[Name, Sub] : E.Inst->Exports) { |
894 | 0 | collectResources(Sub, Out); |
895 | 0 | } |
896 | 0 | return; |
897 | 0 | } |
898 | 0 | if (E.Comp != nullptr) { |
899 | 0 | for (const auto &[Name, Sub] : E.Comp->Imports) { |
900 | 0 | collectResources(Sub, Out); |
901 | 0 | } |
902 | 0 | for (const auto &[Name, Sub] : E.Comp->Exports) { |
903 | 0 | collectResources(Sub, Out); |
904 | 0 | } |
905 | 0 | return; |
906 | 0 | } |
907 | 0 | if (E.getDefValType() != nullptr) { |
908 | 0 | collectResources(E, Out); |
909 | 0 | return; |
910 | 0 | } |
911 | 0 | if (const auto *FT = E.getFuncType()) { |
912 | 0 | for (const auto &P : FT->getParamList()) { |
913 | 0 | collectResources({P.getValType(), E.Home, E.Remap}, Out); |
914 | 0 | } |
915 | 0 | if (const auto &R = FT->getResult(); R.has_value()) { |
916 | 0 | collectResources({*R, E.Home, E.Remap}, Out); |
917 | 0 | } |
918 | 0 | } |
919 | 0 | return; |
920 | 0 | } |
921 | 946 | case ExternKind::InstanceType: |
922 | 3.47k | case ExternKind::ComponentType: |
923 | | // Instances carry no imports, so one walk covers both shapes. |
924 | 3.47k | if (Info.Shape != nullptr) { |
925 | 3.47k | for (const auto &[Name, Sub] : Info.Shape->Imports) { |
926 | 0 | collectResources(Sub, Out); |
927 | 0 | } |
928 | 3.47k | for (const auto &[Name, Sub] : Info.Shape->Exports) { |
929 | 0 | collectResources(Sub, Out); |
930 | 0 | } |
931 | 3.47k | } |
932 | 3.47k | return; |
933 | 3.47k | } |
934 | 3.47k | } |
935 | | |
936 | | void Context::collectResources(const QualValType &Q, |
937 | 0 | std::unordered_set<uint32_t> &Out) noexcept { |
938 | 0 | TypeEntry Storage; |
939 | 0 | if (const auto *Entry = getTypeEntry(Q, Storage)) { |
940 | 0 | collectResources(*Entry, Out); |
941 | 0 | } |
942 | 0 | } |
943 | | |
944 | 0 | bool Context::isDeclaredIn(uint32_t Id, const Scope &S) const noexcept { |
945 | 0 | const auto &Entry = getResource(Id); |
946 | 0 | for (const auto *Cur = Entry.Origin; Cur != nullptr; Cur = Cur->Parent) { |
947 | 0 | if (Cur == &S) { |
948 | 0 | return true; |
949 | 0 | } |
950 | 0 | } |
951 | 0 | return false; |
952 | 0 | } |
953 | | |
954 | | // --------------------------------------------------------------------------- |
955 | | // Effective type size and nesting depth. |
956 | | // --------------------------------------------------------------------------- |
957 | | |
958 | 8.93M | uint64_t Context::getTypeSize(const ExternInfo &Info) noexcept { |
959 | 8.93M | switch (Info.Kind) { |
960 | 0 | case ExternKind::CoreType: |
961 | 0 | return Info.CoreMod != nullptr |
962 | 0 | ? 1 + Info.CoreMod->Imports.size() + Info.CoreMod->Exports.size() |
963 | 0 | : 1; |
964 | 776k | case ExternKind::FuncType: { |
965 | 776k | if (Info.Func.FT == nullptr) { |
966 | 0 | return 1; |
967 | 0 | } |
968 | 776k | auto It = TypeSizes.find(Info.Func.FT); |
969 | 776k | if (It != TypeSizes.end()) { |
970 | 125 | return It->second; |
971 | 125 | } |
972 | 776k | uint64_t Size = 1; |
973 | 776k | for (const auto &P : Info.Func.FT->getParamList()) { |
974 | 1.72k | Size += getTypeSize({P.getValType(), Info.Func.Home, Info.Func.Remap}); |
975 | 1.72k | } |
976 | 776k | if (const auto &R = Info.Func.FT->getResult(); R.has_value()) { |
977 | 748k | Size += getTypeSize({*R, Info.Func.Home, Info.Func.Remap}); |
978 | 748k | } |
979 | 776k | TypeSizes.emplace(Info.Func.FT, Size); |
980 | 776k | return Size; |
981 | 776k | } |
982 | 230 | case ExternKind::ValueBound: |
983 | 230 | return 1 + getTypeSize(Info.Value); |
984 | 6.08M | case ExternKind::TypeBound: { |
985 | 6.08M | const auto &E = Info.Type; |
986 | 6.08M | if (E.isResource()) { |
987 | 1.91k | return 1; |
988 | 1.91k | } |
989 | 6.08M | if (E.Inst != nullptr || E.Comp != nullptr) { |
990 | 3.12M | const void *Key = E.Inst != nullptr ? static_cast<const void *>(E.Inst) |
991 | 3.12M | : static_cast<const void *>(E.Comp); |
992 | 3.12M | auto It = TypeSizes.find(Key); |
993 | 3.12M | if (It != TypeSizes.end()) { |
994 | 85 | return It->second; |
995 | 85 | } |
996 | 3.12M | TypeSizes.emplace(Key, 1); |
997 | 3.12M | uint64_t Size = 1; |
998 | 3.12M | if (E.Inst != nullptr) { |
999 | 855k | for (const auto &[Name, Sub] : E.Inst->Exports) { |
1000 | 133 | Size += 1 + getTypeSize(Sub); |
1001 | 133 | } |
1002 | 2.26M | } else { |
1003 | 2.26M | for (const auto &[Name, Sub] : E.Comp->Imports) { |
1004 | 112 | Size += 1 + getTypeSize(Sub); |
1005 | 112 | } |
1006 | 2.26M | for (const auto &[Name, Sub] : E.Comp->Exports) { |
1007 | 22 | Size += 1 + getTypeSize(Sub); |
1008 | 22 | } |
1009 | 2.26M | } |
1010 | 3.12M | TypeSizes[Key] = Size; |
1011 | 3.12M | return Size; |
1012 | 3.12M | } |
1013 | 2.96M | if (E.getDefValType() != nullptr) { |
1014 | 2.18M | return getTypeSize(E); |
1015 | 2.18M | } |
1016 | 776k | if (E.getFuncType() != nullptr) { |
1017 | 776k | ExternInfo FI; |
1018 | 776k | FI.Kind = ExternKind::FuncType; |
1019 | 776k | FI.Func = {E.getFuncType(), E.Home, E.Remap}; |
1020 | 776k | return getTypeSize(FI); |
1021 | 776k | } |
1022 | 0 | return 1; |
1023 | 776k | } |
1024 | 2.07M | case ExternKind::InstanceType: |
1025 | 2.08M | case ExternKind::ComponentType: { |
1026 | | // Instances carry no imports, so one walk covers both shapes. |
1027 | 2.08M | if (Info.Shape == nullptr) { |
1028 | 0 | return 1; |
1029 | 0 | } |
1030 | 2.08M | auto It = TypeSizes.find(Info.Shape); |
1031 | 2.08M | if (It != TypeSizes.end()) { |
1032 | 141 | return It->second; |
1033 | 141 | } |
1034 | 2.08M | TypeSizes.emplace(Info.Shape, 1); |
1035 | 2.08M | uint64_t Size = 1; |
1036 | 2.08M | for (const auto &[Name, Sub] : Info.Shape->Imports) { |
1037 | 0 | Size += 1 + getTypeSize(Sub); |
1038 | 0 | } |
1039 | 2.08M | for (const auto &[Name, Sub] : Info.Shape->Exports) { |
1040 | 3.95k | Size += 1 + getTypeSize(Sub); |
1041 | 3.95k | } |
1042 | 2.08M | TypeSizes[Info.Shape] = Size; |
1043 | 2.08M | return Size; |
1044 | 2.08M | } |
1045 | 8.93M | } |
1046 | 0 | return 1; |
1047 | 8.93M | } |
1048 | | |
1049 | 12.0M | uint64_t Context::getTypeDepth(const ExternInfo &Info) noexcept { |
1050 | 12.0M | uint64_t Max = 0; |
1051 | 12.0M | switch (Info.Kind) { |
1052 | 776k | case ExternKind::FuncType: |
1053 | 776k | if (Info.Func.FT != nullptr) { |
1054 | 776k | for (const auto &P : Info.Func.FT->getParamList()) { |
1055 | 1.72k | Max = std::max(Max, getTypeDepth({P.getValType(), Info.Func.Home, |
1056 | 1.72k | Info.Func.Remap})); |
1057 | 1.72k | } |
1058 | 776k | if (const auto &R = Info.Func.FT->getResult(); R.has_value()) { |
1059 | 749k | Max = |
1060 | 749k | std::max(Max, getTypeDepth({*R, Info.Func.Home, Info.Func.Remap})); |
1061 | 749k | } |
1062 | 776k | } |
1063 | 776k | break; |
1064 | 6.08M | case ExternKind::TypeBound: { |
1065 | 6.08M | const auto &E = Info.Type; |
1066 | 6.08M | if (E.Inst != nullptr || E.Comp != nullptr) { |
1067 | 3.12M | ExternInfo Probe; |
1068 | 3.12M | Probe.Kind = E.Inst != nullptr ? ExternKind::InstanceType |
1069 | 3.12M | : ExternKind::ComponentType; |
1070 | 3.12M | Probe.Shape = E.Inst != nullptr ? E.Inst : E.Comp; |
1071 | 3.12M | Max = getTypeDepth(Probe); |
1072 | 3.12M | } else if (E.getDefValType() != nullptr) { |
1073 | 2.18M | Max = getTypeDepth(E); |
1074 | 2.18M | } else if (E.getFuncType() != nullptr) { |
1075 | 776k | ExternInfo Probe; |
1076 | 776k | Probe.Kind = ExternKind::FuncType; |
1077 | 776k | Probe.Func = {E.getFuncType(), E.Home, E.Remap}; |
1078 | 776k | Max = getTypeDepth(Probe); |
1079 | 776k | } |
1080 | 6.08M | break; |
1081 | 0 | } |
1082 | 230 | case ExternKind::ValueBound: |
1083 | 230 | Max = getTypeDepth(Info.Value); |
1084 | 230 | break; |
1085 | 2.93M | case ExternKind::InstanceType: |
1086 | 5.20M | case ExternKind::ComponentType: { |
1087 | | // A shape is one nesting level; one walk covers both shapes. |
1088 | 5.20M | if (Info.Shape == nullptr) { |
1089 | 0 | return 1; |
1090 | 0 | } |
1091 | 5.20M | auto It = TypeDepths.find(Info.Shape); |
1092 | 5.20M | if (It != TypeDepths.end()) { |
1093 | 226 | return It->second; |
1094 | 226 | } |
1095 | 5.20M | TypeDepths.emplace(Info.Shape, 1); // Break cycles defensively. |
1096 | 5.20M | for (const auto &[Name, E] : Info.Shape->Imports) { |
1097 | 112 | Max = std::max(Max, getTypeDepth(E)); |
1098 | 112 | } |
1099 | 5.20M | for (const auto &[Name, E] : Info.Shape->Exports) { |
1100 | 4.10k | Max = std::max(Max, getTypeDepth(E)); |
1101 | 4.10k | } |
1102 | 5.20M | TypeDepths[Info.Shape] = Max + 1; |
1103 | 5.20M | return Max + 1; |
1104 | 5.20M | } |
1105 | 0 | case ExternKind::CoreType: |
1106 | | // Core types carry no component value types. |
1107 | 0 | break; |
1108 | 12.0M | } |
1109 | 6.85M | return Max; |
1110 | 12.0M | } |
1111 | | |
1112 | 8.15M | Expect<void> Context::checkTypeLimits(const ExternInfo &Info) noexcept { |
1113 | 8.15M | const uint64_t Size = getTypeSize(Info); |
1114 | 8.15M | if (Size >= MaxTypeSize) { |
1115 | 0 | spdlog::error(ErrCode::Value::ComponentTypeSizeLimit); |
1116 | 0 | spdlog::error(" Effective type size {} exceeds the limit of {}."sv, Size, |
1117 | 0 | MaxTypeSize); |
1118 | 0 | return Unexpect(ErrCode::Value::ComponentTypeSizeLimit); |
1119 | 0 | } |
1120 | 8.15M | const uint64_t Depth = getTypeDepth(Info); |
1121 | 8.15M | if (Depth > MaxTypeDepth) { |
1122 | 0 | spdlog::error(ErrCode::Value::ComponentTypeNestingDepth); |
1123 | 0 | spdlog::error(" Value type nesting depth {} exceeds the limit."sv, |
1124 | 0 | Depth); |
1125 | 0 | return Unexpect(ErrCode::Value::ComponentTypeNestingDepth); |
1126 | 0 | } |
1127 | 8.15M | return {}; |
1128 | 8.15M | } |
1129 | | |
1130 | | // --------------------------------------------------------------------------- |
1131 | | // Canonical ABI layout (spec `elem_size` and `alignment`, 64-bit pointers). |
1132 | | // --------------------------------------------------------------------------- |
1133 | | |
1134 | | // Element size and alignment of a value type, as the Canonical ABI lays it |
1135 | | // out in a 64-bit memory. |
1136 | | std::pair<uint64_t, uint64_t> |
1137 | 674k | Context::getElemLayout(const QualValType &Q) noexcept { |
1138 | 674k | if (Q.VT.isPrimValType()) { |
1139 | 399k | switch (Q.VT.getPrimValType()) { |
1140 | 347k | case PrimValType::Bool: |
1141 | 349k | case PrimValType::S8: |
1142 | 352k | case PrimValType::U8: |
1143 | 352k | return {1, 1}; |
1144 | 957 | case PrimValType::S16: |
1145 | 12.1k | case PrimValType::U16: |
1146 | 12.1k | return {2, 2}; |
1147 | 395 | case PrimValType::S64: |
1148 | 1.11k | case PrimValType::U64: |
1149 | 2.04k | case PrimValType::F64: |
1150 | 2.04k | return {8, 8}; |
1151 | 739 | case PrimValType::String: |
1152 | 739 | return {16, 8}; |
1153 | 31.5k | default: |
1154 | 31.5k | return {4, 4}; |
1155 | 399k | } |
1156 | 399k | } |
1157 | 275k | TypeEntry Storage; |
1158 | 275k | const auto *Entry = getTypeEntry(Q, Storage); |
1159 | 275k | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
1160 | 275k | if (Def == nullptr) { |
1161 | 0 | return {4, 4}; |
1162 | 0 | } |
1163 | 275k | return getElemLayout(*Def, Entry->Home, Entry->Remap); |
1164 | 275k | } |
1165 | | |
1166 | | std::pair<uint64_t, uint64_t> |
1167 | | Context::getElemLayout(const AST::Component::DefValType &D, const Scope *Home, |
1168 | 2.45M | const ResourceMap *Remap) noexcept { |
1169 | 2.45M | if (D.isPrimValType()) { |
1170 | 32.0k | return getElemLayout({ComponentValType(D.getPrimValType()), Home, Remap}); |
1171 | 32.0k | } |
1172 | 2.42M | auto It = ElemLayouts.find(&D); |
1173 | 2.42M | if (It != ElemLayouts.end()) { |
1174 | 273k | return It->second; |
1175 | 273k | } |
1176 | 2.15M | auto AlignTo = [](uint64_t Ptr, uint64_t Align) noexcept { |
1177 | 1.25M | return (Ptr + Align - 1) / Align * Align; |
1178 | 1.25M | }; |
1179 | 2.15M | auto Layout = [&](const ComponentValType &VT) noexcept { |
1180 | 642k | return getElemLayout({VT, Home, Remap}); |
1181 | 642k | }; |
1182 | | // A record lays its fields out in order. |
1183 | 2.15M | auto RecordLayout = [&](const auto &Fields) noexcept { |
1184 | 5.52k | uint64_t Size = 0; |
1185 | 5.52k | uint64_t Align = 1; |
1186 | 10.5k | for (const auto &VT : Fields) { |
1187 | 10.5k | const auto [FSize, FAlign] = Layout(VT); |
1188 | 10.5k | Size = AlignTo(Size, FAlign) + FSize; |
1189 | 10.5k | Align = std::max(Align, FAlign); |
1190 | 10.5k | } |
1191 | 5.52k | return std::make_pair(AlignTo(Size, Align), Align); |
1192 | 5.52k | }; |
1193 | | // A variant is a discriminant followed by the largest payload. |
1194 | 2.15M | auto VariantLayout = [&](size_t NumCases, const auto &Payloads) noexcept { |
1195 | 620k | const uint64_t Disc = NumCases <= (UINT64_C(1) << 8) ? 1 |
1196 | 620k | : NumCases <= (UINT64_C(1) << 16) ? 2 |
1197 | 0 | : 4; |
1198 | 620k | uint64_t CaseSize = 0; |
1199 | 620k | uint64_t Align = Disc; |
1200 | 620k | for (const auto &VT : Payloads) { |
1201 | 613k | const auto [PSize, PAlign] = Layout(VT); |
1202 | 613k | CaseSize = std::max(CaseSize, PSize); |
1203 | 613k | Align = std::max(Align, PAlign); |
1204 | 613k | } |
1205 | 620k | return std::make_pair(AlignTo(AlignTo(Disc, Align) + CaseSize, Align), |
1206 | 620k | Align); |
1207 | 620k | }; |
1208 | 2.15M | std::pair<uint64_t, uint64_t> Res{4, 4}; |
1209 | 2.15M | if (D.isRecordTy()) { |
1210 | 594 | std::vector<ComponentValType> Fields; |
1211 | 637 | for (const auto < : D.getRecord().LabelTypes) { |
1212 | 637 | Fields.push_back(LT.getValType()); |
1213 | 637 | } |
1214 | 594 | Res = RecordLayout(Fields); |
1215 | 2.15M | } else if (D.isTupleTy()) { |
1216 | 4.93k | Res = RecordLayout(D.getTuple().Types); |
1217 | 2.14M | } else if (D.isVariantTy()) { |
1218 | 7.25k | std::vector<ComponentValType> Payloads; |
1219 | 7.25k | for (const auto &[Label, Ty] : D.getVariant().Cases) { |
1220 | 7.25k | if (Ty.has_value()) { |
1221 | 7.15k | Payloads.push_back(*Ty); |
1222 | 7.15k | } |
1223 | 7.25k | } |
1224 | 7.25k | Res = VariantLayout(D.getVariant().Cases.size(), Payloads); |
1225 | 2.14M | } else if (D.isEnumTy()) { |
1226 | 6.74k | Res = VariantLayout(D.getEnum().Labels.size(), |
1227 | 6.74k | std::vector<ComponentValType>{}); |
1228 | 2.13M | } else if (D.isOptionTy()) { |
1229 | 3.65k | Res = VariantLayout(2, std::vector{D.getOption().ValTy}); |
1230 | 2.13M | } else if (D.isResultTy()) { |
1231 | 602k | std::vector<ComponentValType> Payloads; |
1232 | 602k | if (D.getResult().ValTy.has_value()) { |
1233 | 219k | Payloads.push_back(*D.getResult().ValTy); |
1234 | 219k | } |
1235 | 602k | if (D.getResult().ErrTy.has_value()) { |
1236 | 383k | Payloads.push_back(*D.getResult().ErrTy); |
1237 | 383k | } |
1238 | 602k | Res = VariantLayout(2, Payloads); |
1239 | 1.52M | } else if (D.isFlagsTy()) { |
1240 | 3.27k | const size_t N = D.getFlags().Labels.size(); |
1241 | 3.27k | const uint64_t Size = N <= 8 ? 1 : N <= 16 ? 2 : 4; |
1242 | 3.27k | Res = {Size, Size}; |
1243 | 1.52M | } else if (D.isListTy()) { |
1244 | 32.2k | const auto &List = D.getList(); |
1245 | 32.2k | if (List.Len.has_value()) { |
1246 | 18.4k | const auto [ESize, EAlign] = Layout(List.ValTy); |
1247 | 18.4k | Res = {*List.Len * ESize, EAlign}; |
1248 | 18.4k | } else { |
1249 | 13.7k | Res = {16, 8}; |
1250 | 13.7k | } |
1251 | 1.49M | } else if (D.isMapTy()) { |
1252 | 1.39M | Res = {16, 8}; |
1253 | 1.39M | } |
1254 | 2.15M | ElemLayouts.emplace(&D, Res); |
1255 | 2.15M | return Res; |
1256 | 2.42M | } |
1257 | | |
1258 | | // --------------------------------------------------------------------------- |
1259 | | // Canonical ABI flattening (spec `flatten_functype`). |
1260 | | // --------------------------------------------------------------------------- |
1261 | | |
1262 | | // Spec flatten_type: appends the flat core types of Q; false if unflattenable. |
1263 | | bool Context::flattenValType(const QualValType &Q, std::vector<ValType> &Out, |
1264 | 24.6k | const ValType &Ptr) noexcept { |
1265 | 24.6k | if (Out.size() > MaxFlatExpand) { |
1266 | 7 | return true; |
1267 | 7 | } |
1268 | 24.6k | if (const auto Prim = getPrimValType(Q)) { |
1269 | 18.3k | switch (*Prim) { |
1270 | 2.07k | case PrimValType::Bool: |
1271 | 2.52k | case PrimValType::S8: |
1272 | 3.24k | case PrimValType::U8: |
1273 | 3.48k | case PrimValType::S16: |
1274 | 3.68k | case PrimValType::U16: |
1275 | 3.83k | case PrimValType::S32: |
1276 | 14.2k | case PrimValType::U32: |
1277 | 14.6k | case PrimValType::Char: |
1278 | 14.6k | Out.push_back(ValType(TypeCode::I32)); |
1279 | 14.6k | return true; |
1280 | 223 | case PrimValType::S64: |
1281 | 1.21k | case PrimValType::U64: |
1282 | 1.21k | Out.push_back(ValType(TypeCode::I64)); |
1283 | 1.21k | return true; |
1284 | 1.07k | case PrimValType::F32: |
1285 | 1.07k | Out.push_back(ValType(TypeCode::F32)); |
1286 | 1.07k | return true; |
1287 | 435 | case PrimValType::F64: |
1288 | 435 | Out.push_back(ValType(TypeCode::F64)); |
1289 | 435 | return true; |
1290 | 91 | case PrimValType::String: |
1291 | 91 | Out.push_back(Ptr); |
1292 | 91 | Out.push_back(Ptr); |
1293 | 91 | return true; |
1294 | 841 | case PrimValType::ErrorContext: |
1295 | 841 | Out.push_back(ValType(TypeCode::I32)); |
1296 | 841 | return true; |
1297 | 0 | default: |
1298 | 0 | return false; |
1299 | 18.3k | } |
1300 | 18.3k | } |
1301 | 6.31k | TypeEntry Storage; |
1302 | 6.31k | const auto *Entry = getTypeEntry(Q, Storage); |
1303 | 6.31k | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
1304 | 6.31k | if (Def == nullptr) { |
1305 | 13 | return false; |
1306 | 13 | } |
1307 | 6.30k | const auto &D = *Def; |
1308 | 7.36k | auto Sub = [&](const ComponentValType &VT) noexcept { |
1309 | 7.36k | return flattenValType({VT, Entry->Home, Entry->Remap}, Out, Ptr); |
1310 | 7.36k | }; |
1311 | 6.30k | if (D.isRecordTy()) { |
1312 | 74 | for (const auto < : D.getRecord().LabelTypes) { |
1313 | 74 | if (!Sub(LT.getValType())) { |
1314 | 0 | return false; |
1315 | 0 | } |
1316 | 74 | } |
1317 | 74 | return true; |
1318 | 74 | } |
1319 | 6.23k | if (D.isTupleTy()) { |
1320 | 5.94k | for (const auto &Ty : D.getTuple().Types) { |
1321 | 5.94k | if (!Sub(Ty)) { |
1322 | 0 | return false; |
1323 | 0 | } |
1324 | 5.94k | } |
1325 | 2.97k | return true; |
1326 | 2.97k | } |
1327 | 3.25k | if (D.isListTy()) { |
1328 | 143 | const auto &L = D.getList(); |
1329 | 143 | if (L.Len.has_value()) { |
1330 | | // Fixed-length lists flatten to Len copies of the element. |
1331 | 1.46k | for (uint32_t I = 0; I < *L.Len && Out.size() <= MaxFlatExpand; ++I) { |
1332 | 1.33k | if (!Sub(L.ValTy)) { |
1333 | 0 | return false; |
1334 | 0 | } |
1335 | 1.33k | } |
1336 | 122 | return true; |
1337 | 122 | } |
1338 | 21 | Out.push_back(Ptr); |
1339 | 21 | Out.push_back(Ptr); |
1340 | 21 | return true; |
1341 | 143 | } |
1342 | 3.11k | if (D.isMapTy()) { |
1343 | | // (map k v) flattens like (list (tuple k v)). |
1344 | 3 | Out.push_back(Ptr); |
1345 | 3 | Out.push_back(Ptr); |
1346 | 3 | return true; |
1347 | 3 | } |
1348 | 3.11k | if (D.isFlagsTy() || D.isEnumTy() || D.isOwnTy() || D.isBorrowTy() || |
1349 | 2.65k | D.isStreamTy() || D.isFutureTy()) { |
1350 | 791 | Out.push_back(ValType(TypeCode::I32)); |
1351 | 791 | return true; |
1352 | 791 | } |
1353 | 2.32k | if (D.isVariantTy() || D.isOptionTy() || D.isResultTy()) { |
1354 | | // flatten_variant: discriminant + element-wise join of the payloads. |
1355 | 2.32k | std::vector<std::vector<ComponentValType>> Payloads; |
1356 | 2.32k | if (D.isVariantTy()) { |
1357 | 192 | for (const auto &[Label, Ty] : D.getVariant().Cases) { |
1358 | 192 | if (Ty.has_value()) { |
1359 | 126 | Payloads.push_back({*Ty}); |
1360 | 126 | } else { |
1361 | 66 | Payloads.push_back({}); |
1362 | 66 | } |
1363 | 192 | } |
1364 | 2.12k | } else if (D.isOptionTy()) { |
1365 | 143 | Payloads.push_back({}); |
1366 | 143 | Payloads.push_back({D.getOption().ValTy}); |
1367 | 1.98k | } else { |
1368 | 1.98k | const auto &R = D.getResult(); |
1369 | 1.98k | Payloads.push_back(R.ValTy.has_value() |
1370 | 1.98k | ? std::vector<ComponentValType>{*R.ValTy} |
1371 | 1.98k | : std::vector<ComponentValType>{}); |
1372 | 1.98k | Payloads.push_back(R.ErrTy.has_value() |
1373 | 1.98k | ? std::vector<ComponentValType>{*R.ErrTy} |
1374 | 1.98k | : std::vector<ComponentValType>{}); |
1375 | 1.98k | } |
1376 | 2.32k | auto Join = [](ValType A, ValType B) noexcept { |
1377 | 1.19k | if (A == B) { |
1378 | 401 | return A; |
1379 | 401 | } |
1380 | 793 | if ((A.getCode() == TypeCode::I32 && B.getCode() == TypeCode::F32) || |
1381 | 521 | (A.getCode() == TypeCode::F32 && B.getCode() == TypeCode::I32)) { |
1382 | 386 | return ValType(TypeCode::I32); |
1383 | 386 | } |
1384 | 407 | return ValType(TypeCode::I64); |
1385 | 793 | }; |
1386 | 2.32k | std::vector<ValType> Joined; |
1387 | 4.44k | for (const auto &Payload : Payloads) { |
1388 | 4.44k | std::vector<ValType> Flat; |
1389 | 4.44k | for (const auto &Ty : Payload) { |
1390 | 3.29k | if (!flattenValType({Ty, Entry->Home, Entry->Remap}, Flat, Ptr)) { |
1391 | 0 | return false; |
1392 | 0 | } |
1393 | 3.29k | } |
1394 | 8.93k | for (size_t I = 0; I < Flat.size(); ++I) { |
1395 | 4.48k | if (I < Joined.size()) { |
1396 | 1.19k | Joined[I] = Join(Joined[I], Flat[I]); |
1397 | 3.28k | } else { |
1398 | 3.28k | Joined.push_back(Flat[I]); |
1399 | 3.28k | } |
1400 | 4.48k | } |
1401 | 4.44k | } |
1402 | 2.32k | Out.push_back(ValType(TypeCode::I32)); |
1403 | 2.32k | Out.insert(Out.end(), Joined.begin(), Joined.end()); |
1404 | 2.32k | return true; |
1405 | 2.32k | } |
1406 | 0 | return false; |
1407 | 2.32k | } |
1408 | | |
1409 | | // True iff the type transitively contains a list, map, or string. |
1410 | 23.1k | bool Context::needsMemory(const QualValType &Q) noexcept { |
1411 | 23.1k | if (const auto Prim = getPrimValType(Q)) { |
1412 | 17.0k | return *Prim == PrimValType::String; |
1413 | 17.0k | } |
1414 | 6.19k | TypeEntry Storage; |
1415 | 6.19k | const auto *Entry = getTypeEntry(Q, Storage); |
1416 | 6.19k | const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr; |
1417 | 6.19k | if (Def == nullptr) { |
1418 | 0 | return false; |
1419 | 0 | } |
1420 | 6.19k | const auto &D = *Def; |
1421 | 6.19k | if (D.isListTy() || D.isMapTy()) { |
1422 | 123 | return true; |
1423 | 123 | } |
1424 | 6.06k | bool Found = false; |
1425 | 9.23k | forEachValType(D, [&](const ComponentValType &VT) noexcept { |
1426 | 9.23k | Found = Found || needsMemory({VT, Entry->Home, Entry->Remap}); |
1427 | 9.23k | }); |
1428 | 6.06k | return Found; |
1429 | 6.19k | } |
1430 | | |
1431 | | Expect<AST::FunctionType> |
1432 | | Context::flattenFuncType(const FuncInfo &FI, const ValType &Ptr, |
1433 | | bool &ParamsNeedMemory, |
1434 | 12.9k | bool &ResultsNeedMemory) noexcept { |
1435 | 12.9k | AST::FunctionType Sig; |
1436 | 12.9k | ParamsNeedMemory = false; |
1437 | 12.9k | ResultsNeedMemory = false; |
1438 | 12.9k | if (FI.FT == nullptr) { |
1439 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1440 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1441 | 0 | } |
1442 | 12.9k | for (const auto &P : FI.FT->getParamList()) { |
1443 | 14 | const QualValType Q{P.getValType(), FI.Home, FI.Remap}; |
1444 | 14 | if (!flattenValType(Q, Sig.getParamTypes(), Ptr)) { |
1445 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1446 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1447 | 0 | } |
1448 | 14 | ParamsNeedMemory = ParamsNeedMemory || needsMemory(Q); |
1449 | 14 | } |
1450 | 12.9k | if (const auto &R = FI.FT->getResult(); R.has_value()) { |
1451 | 10.8k | const QualValType Q{*R, FI.Home, FI.Remap}; |
1452 | 10.8k | if (!flattenValType(Q, Sig.getReturnTypes(), Ptr)) { |
1453 | 0 | spdlog::error(ErrCode::Value::InvalidTypeReference); |
1454 | 0 | return Unexpect(ErrCode::Value::InvalidTypeReference); |
1455 | 0 | } |
1456 | 10.8k | ResultsNeedMemory = ResultsNeedMemory || needsMemory(Q); |
1457 | 10.8k | } |
1458 | 12.9k | return Sig; |
1459 | 12.9k | } |
1460 | | |
1461 | | // --------------------------------------------------------------------------- |
1462 | | // The named-types rule: introduceExternTypes checks and records an extern. |
1463 | | // --------------------------------------------------------------------------- |
1464 | | |
1465 | | // Validate and register an extern for the named-types rule. |
1466 | | bool Context::introduceExternTypes(const ExternInfo &Info, |
1467 | 10.0k | bool IsImport) noexcept { |
1468 | 10.0k | auto &S = getTop(); |
1469 | 10.0k | switch (Info.Kind) { |
1470 | 19 | case ExternKind::CoreType: |
1471 | 788 | case ExternKind::ComponentType: |
1472 | 788 | return true; |
1473 | 8.39k | case ExternKind::TypeBound: { |
1474 | 8.39k | if (!areInnerTypesIntroduced(Info.Type, IsImport)) { |
1475 | 25 | return false; |
1476 | 25 | } |
1477 | | // Introduce: imported types are usable by exports as well. |
1478 | 8.37k | if (Info.Type.isResource() && Info.Type.NameId.has_value()) { |
1479 | 6.06k | if (IsImport) { |
1480 | 6.04k | S.ImportSide.NamedResources.insert(*Info.Type.NameId); |
1481 | 6.04k | S.ExportSide.NamedResources.insert(*Info.Type.NameId); |
1482 | 6.04k | } else { |
1483 | 21 | S.ExportSide.NamedResources.insert(*Info.Type.NameId); |
1484 | 21 | } |
1485 | 6.06k | } else if (Info.Type.getDefValType() != nullptr) { |
1486 | 2.05k | if (IsImport) { |
1487 | 646 | S.ImportSide.NamedTypes.emplace(Info.Type.DT, Info.Type.Home); |
1488 | 646 | S.ExportSide.NamedTypes.emplace(Info.Type.DT, Info.Type.Home); |
1489 | 646 | if (Info.Type.NameId.has_value()) { |
1490 | 646 | S.ImportSide.NamedIds.insert(*Info.Type.NameId); |
1491 | 646 | S.ExportSide.NamedIds.insert(*Info.Type.NameId); |
1492 | 646 | } |
1493 | 1.40k | } else { |
1494 | 1.40k | S.ExportSide.NamedTypes.emplace(Info.Type.DT, Info.Type.Home); |
1495 | 1.40k | if (Info.Type.NameId.has_value()) { |
1496 | 1.40k | S.ExportSide.NamedIds.insert(*Info.Type.NameId); |
1497 | 1.40k | } |
1498 | 1.40k | } |
1499 | 2.05k | } |
1500 | 8.37k | return true; |
1501 | 8.39k | } |
1502 | 240 | case ExternKind::InstanceType: { |
1503 | 240 | if (Info.Shape == nullptr) { |
1504 | 0 | return true; |
1505 | 0 | } |
1506 | 240 | auto Walk = [&](const ExternInfo &Sub) noexcept { |
1507 | 61 | return introduceExternTypes(Sub, IsImport); |
1508 | 61 | }; |
1509 | 240 | if (!Info.Shape->ExportOrder.empty()) { |
1510 | 61 | for (const auto &Name : Info.Shape->ExportOrder) { |
1511 | 61 | auto It = Info.Shape->Exports.find(Name); |
1512 | 61 | if (It != Info.Shape->Exports.end() && !Walk(It->second)) { |
1513 | 0 | return false; |
1514 | 0 | } |
1515 | 61 | } |
1516 | 61 | return true; |
1517 | 61 | } |
1518 | 179 | for (const auto &[Name, Sub] : Info.Shape->Exports) { |
1519 | 0 | if (!Walk(Sub)) { |
1520 | 0 | return false; |
1521 | 0 | } |
1522 | 0 | } |
1523 | 179 | return true; |
1524 | 179 | } |
1525 | 351 | case ExternKind::FuncType: { |
1526 | 351 | if (Info.Func.FT == nullptr) { |
1527 | 0 | return true; |
1528 | 0 | } |
1529 | 351 | for (const auto &P : Info.Func.FT->getParamList()) { |
1530 | 11 | if (!isIntroduced({P.getValType(), Info.Func.Home, Info.Func.Remap}, |
1531 | 11 | IsImport)) { |
1532 | 3 | return false; |
1533 | 3 | } |
1534 | 11 | } |
1535 | 348 | if (const auto &R = Info.Func.FT->getResult(); |
1536 | 348 | R.has_value() && |
1537 | 135 | !isIntroduced({*R, Info.Func.Home, Info.Func.Remap}, IsImport)) { |
1538 | 2 | return false; |
1539 | 2 | } |
1540 | 346 | return true; |
1541 | 348 | } |
1542 | 268 | case ExternKind::ValueBound: |
1543 | 268 | return isIntroduced(Info.Value, IsImport); |
1544 | 10.0k | } |
1545 | 0 | return true; |
1546 | 10.0k | } |
1547 | | |
1548 | 3.03k | bool Context::isIntroduced(const QualValType &Q, bool IsImport) noexcept { |
1549 | 3.03k | if (Q.VT.isPrimValType() || Q.Home == nullptr) { |
1550 | 2.52k | return true; |
1551 | 2.52k | } |
1552 | 501 | TypeEntry Storage; |
1553 | 501 | const auto *Entry = getTypeEntry(Q, Storage); |
1554 | 501 | if (Entry == nullptr) { |
1555 | 0 | return true; |
1556 | 0 | } |
1557 | 501 | return isIntroduced(*Entry, IsImport); |
1558 | 501 | } |
1559 | | |
1560 | 501 | bool Context::isIntroduced(const TypeEntry &E, bool IsImport) noexcept { |
1561 | 501 | auto &S = getTop(); |
1562 | 501 | const auto &NamedTys = S.getNameSide(IsImport).NamedTypes; |
1563 | 501 | const auto &NamedRes = S.getNameSide(IsImport).NamedResources; |
1564 | 501 | if (E.isResource()) { |
1565 | 0 | return E.NameId.has_value() && NamedRes.count(*E.NameId) != 0; |
1566 | 0 | } |
1567 | 501 | const auto *Def = E.getDefValType(); |
1568 | 501 | if (Def == nullptr) { |
1569 | 0 | return true; |
1570 | 0 | } |
1571 | 501 | const auto &D = *Def; |
1572 | 501 | if (D.isPrimValType()) { |
1573 | 79 | return true; |
1574 | 79 | } |
1575 | | // Local: the introduced identity. Foreign: a structurally equal named type. |
1576 | 422 | if (D.isFlagsTy() || D.isEnumTy() || D.isRecordTy() || D.isVariantTy()) { |
1577 | 44 | const auto &NamedIds = S.getNameSide(IsImport).NamedIds; |
1578 | 44 | if (E.Home == &S) { |
1579 | 44 | return E.NameId.has_value() && NamedIds.count(*E.NameId) != 0; |
1580 | 44 | } |
1581 | 0 | if (E.NameId.has_value() && NamedIds.count(*E.NameId) != 0) { |
1582 | 0 | return true; |
1583 | 0 | } |
1584 | 0 | if (NamedTys.count(E.DT) != 0) { |
1585 | 0 | return true; |
1586 | 0 | } |
1587 | 0 | for (const auto &[Named, Home] : NamedTys) { |
1588 | 0 | if (Named->isDefValType()) { |
1589 | 0 | TypeEntry Probe; |
1590 | 0 | Probe.DT = Named; |
1591 | 0 | Probe.Home = Home; |
1592 | 0 | ResourceMap Subst; |
1593 | 0 | if (Matcher::matchValType(*this, E, Probe, Subst)) { |
1594 | 0 | return true; |
1595 | 0 | } |
1596 | 0 | } |
1597 | 0 | } |
1598 | 0 | return false; |
1599 | 0 | } |
1600 | 482 | auto Sub = [&](const ComponentValType &VT) noexcept { |
1601 | 482 | return isIntroduced({VT, E.Home, E.Remap}, IsImport); |
1602 | 482 | }; |
1603 | 378 | if (D.isStreamTy() || D.isFutureTy()) { |
1604 | 103 | const auto &Elem = |
1605 | 103 | D.isStreamTy() ? D.getStream().ValTy : D.getFuture().ValTy; |
1606 | 103 | return !Elem.has_value() || Sub(*Elem); |
1607 | 103 | } |
1608 | 275 | if (D.isOwnTy() || D.isBorrowTy()) { |
1609 | 7 | const uint32_t Idx = D.isOwnTy() ? D.getOwn().Idx : D.getBorrow().Idx; |
1610 | 7 | const auto *Res = E.Home->getType(Idx); |
1611 | 7 | if (Res == nullptr || !Res->isResource()) { |
1612 | 0 | return true; |
1613 | 0 | } |
1614 | 7 | const uint32_t Eff = applyRemap(E.Remap, *Res->ResourceId); |
1615 | 7 | const uint32_t NameId = Eff != *Res->ResourceId |
1616 | 7 | ? getResource(Eff).NameId |
1617 | 7 | : Res->NameId.value_or(getResource(Eff).NameId); |
1618 | 7 | return NamedRes.count(NameId) != 0; |
1619 | 7 | } |
1620 | 268 | bool All = true; |
1621 | 268 | forEachValType( |
1622 | 440 | D, [&](const ComponentValType &VT) noexcept { All = All && Sub(VT); }); |
1623 | 268 | return All; |
1624 | 275 | } |
1625 | | |
1626 | | // The introduced type is exempt; its immediate components must be named. |
1627 | | bool Context::areInnerTypesIntroduced(const TypeEntry &E, |
1628 | 8.39k | bool IsImport) noexcept { |
1629 | 8.39k | if (E.isResource()) { |
1630 | 6.06k | return true; |
1631 | 6.06k | } |
1632 | 2.33k | if (E.Comp != nullptr) { |
1633 | 117 | return true; |
1634 | 117 | } |
1635 | 2.21k | if (E.Inst != nullptr) { |
1636 | 55 | for (const auto &[Name, Sub] : E.Inst->Exports) { |
1637 | 12 | if (!introduceExternTypes(Sub, IsImport)) { |
1638 | 0 | return false; |
1639 | 0 | } |
1640 | 12 | } |
1641 | 55 | return true; |
1642 | 55 | } |
1643 | 2.15k | if (const auto *FT = E.getFuncType()) { |
1644 | 84 | for (const auto &P : FT->getParamList()) { |
1645 | 16 | if (!isIntroduced({P.getValType(), E.Home, E.Remap}, IsImport)) { |
1646 | 1 | return false; |
1647 | 1 | } |
1648 | 16 | } |
1649 | 83 | if (const auto &R = FT->getResult(); |
1650 | 83 | R.has_value() && !isIntroduced({*R, E.Home, E.Remap}, IsImport)) { |
1651 | 1 | return false; |
1652 | 1 | } |
1653 | 82 | return true; |
1654 | 83 | } |
1655 | 2.07k | const auto *Def = E.getDefValType(); |
1656 | 2.07k | if (Def == nullptr) { |
1657 | 0 | return true; |
1658 | 0 | } |
1659 | 2.07k | const auto &D = *Def; |
1660 | 2.07k | if (D.isPrimValType() || D.isFlagsTy() || D.isEnumTy()) { |
1661 | 71 | return true; |
1662 | 71 | } |
1663 | 2.04k | auto Sub = [&](const ComponentValType &VT) noexcept { |
1664 | 2.04k | return isIntroduced({VT, E.Home, E.Remap}, IsImport); |
1665 | 2.04k | }; |
1666 | 2.00k | if (D.isStreamTy() || D.isFutureTy()) { |
1667 | 1.02k | const auto &Elem = |
1668 | 1.02k | D.isStreamTy() ? D.getStream().ValTy : D.getFuture().ValTy; |
1669 | 1.02k | return !Elem.has_value() || Sub(*Elem); |
1670 | 1.02k | } |
1671 | 981 | if (D.isOwnTy() || D.isBorrowTy()) { |
1672 | 16 | const auto &NamedRes = getTop().getNameSide(IsImport).NamedResources; |
1673 | 16 | const uint32_t Idx = D.isOwnTy() ? D.getOwn().Idx : D.getBorrow().Idx; |
1674 | 16 | const auto *Res = E.Home->getType(Idx); |
1675 | 16 | if (Res == nullptr || !Res->isResource()) { |
1676 | 0 | return true; |
1677 | 0 | } |
1678 | 16 | const uint32_t Eff = applyRemap(E.Remap, *Res->ResourceId); |
1679 | 16 | const uint32_t NameId = Eff != *Res->ResourceId |
1680 | 16 | ? getResource(Eff).NameId |
1681 | 16 | : Res->NameId.value_or(getResource(Eff).NameId); |
1682 | 16 | return NamedRes.count(NameId) != 0; |
1683 | 16 | } |
1684 | 965 | bool All = true; |
1685 | 965 | forEachValType( |
1686 | 1.77k | D, [&](const ComponentValType &VT) noexcept { All = All && Sub(VT); }); |
1687 | 965 | return All; |
1688 | 981 | } |
1689 | | |
1690 | | // --------------------------------------------------------------------------- |
1691 | | // The leaves of the walks above. |
1692 | | // --------------------------------------------------------------------------- |
1693 | | |
1694 | | void Context::collectResources(const TypeEntry &E, |
1695 | 0 | std::unordered_set<uint32_t> &Out) noexcept { |
1696 | 0 | const auto *Def = E.getDefValType(); |
1697 | 0 | if (Def == nullptr || Def->isPrimValType()) { |
1698 | 0 | return; |
1699 | 0 | } |
1700 | 0 | const auto &D = *Def; |
1701 | 0 | auto Sub = [&](const ComponentValType &VT) noexcept { |
1702 | 0 | collectResources({VT, E.Home, E.Remap}, Out); |
1703 | 0 | }; |
1704 | 0 | if (D.isOwnTy() || D.isBorrowTy()) { |
1705 | 0 | const uint32_t Idx = D.isOwnTy() ? D.getOwn().Idx : D.getBorrow().Idx; |
1706 | 0 | if (auto Id = getResourceId(E.Home, E.Remap, Idx)) { |
1707 | 0 | Out.insert(*Id); |
1708 | 0 | } |
1709 | 0 | return; |
1710 | 0 | } |
1711 | | // A stream or future payload can reach a resource too. |
1712 | 0 | if (D.isStreamTy() || D.isFutureTy()) { |
1713 | 0 | const auto &Elem = |
1714 | 0 | D.isStreamTy() ? D.getStream().ValTy : D.getFuture().ValTy; |
1715 | 0 | if (Elem.has_value()) { |
1716 | 0 | Sub(*Elem); |
1717 | 0 | } |
1718 | 0 | return; |
1719 | 0 | } |
1720 | 0 | forEachValType(D, Sub); |
1721 | 0 | } |
1722 | | |
1723 | | // Effective size of a value type (the spec limit metric), kept per node. |
1724 | 4.19M | uint64_t Context::getTypeSize(const QualValType &Q) noexcept { |
1725 | 4.19M | TypeEntry Storage; |
1726 | 4.19M | const auto *Entry = getTypeEntry(Q, Storage); |
1727 | 4.19M | return Entry != nullptr ? getTypeSize(*Entry) : 1; |
1728 | 4.19M | } |
1729 | | |
1730 | 3.23M | uint64_t Context::getTypeSize(const TypeEntry &E) noexcept { |
1731 | 3.23M | const auto *Def = E.getDefValType(); |
1732 | 3.23M | if (Def == nullptr || Def->isPrimValType()) { |
1733 | 45.2k | return 1; |
1734 | 45.2k | } |
1735 | 3.19M | auto It = TypeSizes.find(Def); |
1736 | 3.19M | if (It != TypeSizes.end()) { |
1737 | 1.03M | return It->second; |
1738 | 1.03M | } |
1739 | 2.15M | TypeSizes.emplace(Def, 1); // Break cycles defensively. |
1740 | 2.15M | const auto &D = *Def; |
1741 | 2.15M | uint64_t Size = 1; |
1742 | | // A payload-less variant case and every flags or enum label count one. |
1743 | 2.15M | if (D.isVariantTy()) { |
1744 | 7.25k | for (const auto &[Label, Ty] : D.getVariant().Cases) { |
1745 | 7.25k | if (!Ty.has_value()) { |
1746 | 106 | Size += 1; |
1747 | 106 | } |
1748 | 7.25k | } |
1749 | 2.14M | } else if (D.isFlagsTy()) { |
1750 | 3.27k | Size += D.getFlags().Labels.size(); |
1751 | 2.14M | } else if (D.isEnumTy()) { |
1752 | 6.74k | Size += D.getEnum().Labels.size(); |
1753 | 6.74k | } |
1754 | 3.44M | forEachValType(D, [&](const ComponentValType &VT) noexcept { |
1755 | 3.44M | Size += getTypeSize({VT, E.Home, E.Remap}); |
1756 | 3.44M | }); |
1757 | 2.15M | TypeSizes[Def] = Size; |
1758 | 2.15M | return Size; |
1759 | 3.19M | } |
1760 | | |
1761 | | // Depth of a value type: leaves count 1, wrappers add 1. |
1762 | 4.19M | uint64_t Context::getTypeDepth(const QualValType &Q) noexcept { |
1763 | 4.19M | TypeEntry Storage; |
1764 | 4.19M | const auto *Entry = getTypeEntry(Q, Storage); |
1765 | 4.19M | return Entry != nullptr ? getTypeDepth(*Entry) : 1; |
1766 | 4.19M | } |
1767 | | |
1768 | 3.23M | uint64_t Context::getTypeDepth(const TypeEntry &E) noexcept { |
1769 | 3.23M | const auto *Def = E.getDefValType(); |
1770 | 3.23M | if (Def == nullptr || Def->isPrimValType()) { |
1771 | 45.2k | return 1; |
1772 | 45.2k | } |
1773 | 3.19M | auto It = TypeDepths.find(Def); |
1774 | 3.19M | if (It != TypeDepths.end()) { |
1775 | 1.03M | return It->second; |
1776 | 1.03M | } |
1777 | 2.15M | TypeDepths.emplace(Def, 1); // Break cycles defensively. |
1778 | 2.15M | const auto &D = *Def; |
1779 | 2.15M | uint64_t Max = 0; |
1780 | 3.44M | forEachValType(D, [&](const ComponentValType &VT) noexcept { |
1781 | 3.44M | Max = std::max(Max, getTypeDepth({VT, E.Home, E.Remap})); |
1782 | 3.44M | }); |
1783 | 2.15M | const uint64_t Depth = Max + 1; |
1784 | 2.15M | TypeDepths[Def] = Depth; |
1785 | 2.15M | return Depth; |
1786 | 3.19M | } |
1787 | | |
1788 | | // --------------------------------------------------------------------------- |
1789 | | // Copies of views under a remap; Copies maps each shape met to its copy. |
1790 | | // --------------------------------------------------------------------------- |
1791 | | |
1792 | | // Copy a view across an instantiation boundary under the Node remap. |
1793 | | ExternInfo Context::copyExtern( |
1794 | | const ExternInfo &E, const ResourceMap *Node, |
1795 | 3.45k | std::unordered_map<const Shape *, const Shape *> &Copies) noexcept { |
1796 | 3.45k | ExternInfo R = E; |
1797 | 3.45k | switch (E.Kind) { |
1798 | 0 | case ExternKind::CoreType: |
1799 | 0 | break; |
1800 | 0 | case ExternKind::FuncType: |
1801 | 0 | R.Func.Remap = composeRemap(Node, E.Func.Remap); |
1802 | 0 | break; |
1803 | 0 | case ExternKind::ValueBound: |
1804 | 0 | R.Value.Remap = composeRemap(Node, E.Value.Remap); |
1805 | 0 | break; |
1806 | 0 | case ExternKind::TypeBound: |
1807 | 0 | R.Type = copyTypeEntry(E.Type, Node, Copies); |
1808 | 0 | break; |
1809 | 933 | case ExternKind::InstanceType: |
1810 | 3.45k | case ExternKind::ComponentType: |
1811 | 3.45k | R.Shape = copyShape(E.Shape, Node, Copies); |
1812 | 3.45k | break; |
1813 | 3.45k | } |
1814 | 3.45k | return R; |
1815 | 3.45k | } |
1816 | | |
1817 | | TypeEntry Context::copyTypeEntry( |
1818 | | const TypeEntry &E, const ResourceMap *Node, |
1819 | 0 | std::unordered_map<const Shape *, const Shape *> &Copies) noexcept { |
1820 | 0 | TypeEntry R = E; |
1821 | 0 | if (E.isResource()) { |
1822 | 0 | R.ResourceId = Node->apply(*E.ResourceId); |
1823 | 0 | if (*R.ResourceId != *E.ResourceId) { |
1824 | 0 | R.NameId = getResource(*R.ResourceId).NameId; |
1825 | 0 | } |
1826 | 0 | } |
1827 | 0 | R.Remap = composeRemap(Node, E.Remap); |
1828 | 0 | if (E.Inst != nullptr) { |
1829 | 0 | R.Inst = copyShape(E.Inst, Node, Copies); |
1830 | 0 | } |
1831 | 0 | if (E.Comp != nullptr) { |
1832 | 0 | R.Comp = copyShape(E.Comp, Node, Copies); |
1833 | 0 | } |
1834 | 0 | return R; |
1835 | 0 | } |
1836 | | |
1837 | | // One walk for both shapes; each loop is a no-op for the other kind. |
1838 | | const Shape *Context::copyShape( |
1839 | | const Shape *S, const ResourceMap *Node, |
1840 | 3.45k | std::unordered_map<const Shape *, const Shape *> &Copies) noexcept { |
1841 | 3.45k | if (S == nullptr) { |
1842 | 0 | return nullptr; |
1843 | 0 | } |
1844 | 3.45k | auto It = Copies.find(S); |
1845 | 3.45k | if (It != Copies.end()) { |
1846 | 0 | return It->second; |
1847 | 0 | } |
1848 | 3.45k | auto *R = addShape(); |
1849 | 3.45k | Copies.emplace(S, R); |
1850 | 3.45k | R->DeclScope = S->DeclScope; |
1851 | 3.45k | R->ExportOrder = S->ExportOrder; |
1852 | 3.45k | for (const auto &[Name, E] : S->Imports) { |
1853 | 0 | R->Imports.emplace_back(Name, copyExtern(E, Node, Copies)); |
1854 | 0 | } |
1855 | 3.45k | for (const auto &[Name, E] : S->Exports) { |
1856 | 0 | R->Exports.emplace(Name, copyExtern(E, Node, Copies)); |
1857 | 0 | } |
1858 | 3.45k | return R; |
1859 | 3.45k | } |
1860 | | |
1861 | | } // namespace Component |
1862 | | } // namespace Validator |
1863 | | } // namespace WasmEdge |