Coverage Report

Created: 2026-11-09 06:49

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