Coverage Report

Created: 2026-11-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 &LT : 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 &LT : 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