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