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_canon.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
//===-- component_canon.cpp - Canonical built-in validation ---------------===//
5
//
6
// Validation of canon lift / lower / resource.* definitions.
7
//
8
//===----------------------------------------------------------------------===//
9
10
#include "common/errinfo.h"
11
#include "common/spdlog.h"
12
#include "validator/validator.h"
13
14
#include <vector>
15
16
namespace WasmEdge {
17
namespace Validator {
18
19
using namespace std::literals;
20
21
// canon ::= lift | lower | resource.* | async built-in, one switch per opcode.
22
Expect<void>
23
254k
Validator::validate(const AST::Component::Canonical &Canon) noexcept {
24
254k
  auto &S = CompCtx.getTop();
25
254k
  const ValType I32V{TypeCode::I32};
26
254k
  const ValType I64V{TypeCode::I64};
27
28
  // Defines the core function a canonical definition lowers to.
29
254k
  auto PushCoreFunc = [&](std::vector<ValType> Params,
30
254k
                          std::vector<ValType> Results) -> Expect<void> {
31
244k
    S.addCoreFunc(CompCtx.addCoreFuncType(Params, Results));
32
244k
    return {};
33
244k
  };
34
  // A built-in takes its core type from the shared table, keyed on Addr.
35
254k
  auto PushBuiltin = [&](const ValType &Addr) -> Expect<void> {
36
233k
    auto [Params, Results] = AST::Component::Canonical::getBuiltinCoreFuncType(
37
233k
        Canon.getOpCode(), Addr);
38
233k
    return PushCoreFunc(std::move(Params), std::move(Results));
39
233k
  };
40
41
  // The resource built-ins take no options and name a resource type.
42
254k
  auto GetResourceEntry =
43
254k
      [&](std::string_view What) -> Expect<const Component::TypeEntry *> {
44
1.52k
    if (!Canon.getOptions().empty()) {
45
0
      spdlog::error(ErrCode::Value::CanonOptionOnBuiltin);
46
0
      spdlog::error("    resource built-ins take no canonical options."sv);
47
0
      return Unexpect(ErrCode::Value::CanonOptionOnBuiltin);
48
0
    }
49
1.52k
    const auto *Entry = S.getType(Canon.getIndex());
50
1.52k
    if (Entry == nullptr) {
51
29
      spdlog::error(ErrCode::Value::ComponentTypeIndexOutOfBounds);
52
29
      spdlog::error("    {} type index {} out of bounds."sv, What,
53
29
                    Canon.getIndex());
54
29
      return Unexpect(ErrCode::Value::ComponentTypeIndexOutOfBounds);
55
29
    }
56
1.49k
    if (!Entry->isResource()) {
57
5
      spdlog::error(ErrCode::Value::ComponentNotResourceType);
58
5
      spdlog::error("    {} type index {} is not a resource."sv, What,
59
5
                    Canon.getIndex());
60
5
      return Unexpect(ErrCode::Value::ComponentNotResourceType);
61
5
    }
62
1.48k
    return Entry;
63
1.49k
  };
64
65
  // resource.new / resource.rep need a locally-defined resource and its rep.
66
254k
  auto LocalResourceRep = [&](std::string_view What) -> Expect<ValType> {
67
1.44k
    EXPECTED_TRY(const auto *Entry, GetResourceEntry(What));
68
1.41k
    const auto &Res = CompCtx.getResource(*Entry->ResourceId);
69
1.41k
    if (Res.RT == nullptr || Res.Origin != &S) {
70
2
      spdlog::error(ErrCode::Value::ComponentNotLocalResource);
71
2
      spdlog::error("    {} requires a locally-defined resource type."sv, What);
72
2
      return Unexpect(ErrCode::Value::ComponentNotLocalResource);
73
2
    }
74
1.41k
    return Res.RT->isAddrI64() ? I64V : I32V;
75
1.41k
  };
76
77
  // stream/future built-ins name their element type in the type immediate.
78
254k
  auto CheckTypeImmediate =
79
254k
      [&](bool WantStream) -> Expect<const Component::TypeEntry *> {
80
184k
    const auto *Entry = S.getType(Canon.getIndex());
81
184k
    const auto *Def = Entry != nullptr ? Entry->getDefValType() : nullptr;
82
184k
    const bool Ok =
83
184k
        Def != nullptr && (WantStream ? Def->isStreamTy() : Def->isFutureTy());
84
184k
    if (!Ok) {
85
82
      const auto Code = WantStream
86
82
                            ? ErrCode::Value::ComponentStreamTypeRequired
87
82
                            : ErrCode::Value::ComponentFutureTypeRequired;
88
82
      spdlog::error(Code);
89
82
      spdlog::error("    `{}` type index {} does not refer to a {} type."sv,
90
82
                    ComponentCanonOpCodeStr[Canon.getOpCode()],
91
82
                    Canon.getIndex(), WantStream ? "stream"sv : "future"sv);
92
82
      return Unexpect(Code);
93
82
    }
94
184k
    return Entry;
95
184k
  };
96
97
  // Option-bearing built-ins are lowerings: no post-return or callback.
98
254k
  auto BuiltinOptions = [&](bool NeedMemory, bool NeedRealloc) -> Expect<void> {
99
1.32k
    EXPECTED_TRY(CompCtx.checkOptions(Canon, false));
100
1.31k
    return CompCtx.checkRequiredOptions(Canon, NeedMemory, NeedRealloc,
101
1.31k
                                        "this canonical built-in"sv);
102
1.32k
  };
103
104
  // Built-ins the spec restricts to synchronous use.
105
254k
  auto RequireSync = [&]() -> Expect<void> {
106
3.16k
    if (Canon.hasOption(ComponentCanonOptCode::Async)) {
107
3
      spdlog::error(ErrCode::Value::CanonAsyncOnBuiltin);
108
3
      spdlog::error("    this canonical built-in is always synchronous."sv);
109
3
      return Unexpect(ErrCode::Value::CanonAsyncOnBuiltin);
110
3
    }
111
3.15k
    return {};
112
3.16k
  };
113
114
254k
  bool ParamsNeedMemory = false, ResultsNeedMemory = false;
115
116
254k
  switch (Canon.getOpCode()) {
117
10.0k
  case ComponentCanonOpCode::Lift: {
118
10.0k
    const auto *Entry = S.getType(Canon.getTargetIndex());
119
10.0k
    if (Entry == nullptr) {
120
6
      spdlog::error(ErrCode::Value::ComponentTypeIndexOutOfBounds);
121
6
      spdlog::error("    canon lift type index {} out of bounds."sv,
122
6
                    Canon.getTargetIndex());
123
6
      return Unexpect(ErrCode::Value::ComponentTypeIndexOutOfBounds);
124
6
    }
125
10.0k
    if (Entry->getFuncType() == nullptr) {
126
3
      spdlog::error(ErrCode::Value::ComponentNotFunctionType);
127
3
      spdlog::error("    canon lift type index {} is not a function type."sv,
128
3
                    Canon.getTargetIndex());
129
3
      return Unexpect(ErrCode::Value::ComponentNotFunctionType);
130
3
    }
131
10.0k
    const Component::FuncInfo FI{Entry->getFuncType(), Entry->Home,
132
10.0k
                                 Entry->Remap};
133
10.0k
    EXPECTED_TRY(CompCtx.checkOptions(Canon, true));
134
10.0k
    const bool AsyncOpt = Canon.hasOption(ComponentCanonOptCode::Async);
135
10.0k
    if (AsyncOpt && !FI.FT->isAsync()) {
136
1
      spdlog::error(ErrCode::Value::CanonAsyncRequiresAsyncType);
137
1
      spdlog::error("    canon lift with `async` needs `(func async ...)`."sv);
138
1
      return Unexpect(ErrCode::Value::CanonAsyncRequiresAsyncType);
139
1
    }
140
10.0k
    if (Canon.hasOption(ComponentCanonOptCode::Callback) && !AsyncOpt) {
141
0
      spdlog::error(ErrCode::Value::CanonCallbackRequiresAsync);
142
0
      spdlog::error("    the `callback` option requires the `async` option."sv);
143
0
      return Unexpect(ErrCode::Value::CanonCallbackRequiresAsync);
144
0
    }
145
10.0k
    if (AsyncOpt && Canon.hasOption(ComponentCanonOptCode::PostReturn)) {
146
1
      spdlog::error(ErrCode::Value::CanonPostReturnWithAsync);
147
1
      spdlog::error("    cannot specify post-return function in combination "
148
1
                    "with async."sv);
149
1
      return Unexpect(ErrCode::Value::CanonPostReturnWithAsync);
150
1
    }
151
152
10.0k
    const ValType Ptr = CompCtx.getCanonPtrType(Canon);
153
10.0k
    EXPECTED_TRY(auto Sig, CompCtx.flattenFuncType(FI, Ptr, ParamsNeedMemory,
154
10.0k
                                                   ResultsNeedMemory));
155
10.0k
    const bool ParamsIndirect =
156
10.0k
        Sig.getParamTypes().size() > Component::Context::MaxFlatParams;
157
10.0k
    const bool ResultsIndirect =
158
10.0k
        Sig.getReturnTypes().size() > Component::Context::MaxFlatResults;
159
10.0k
    if (ParamsIndirect) {
160
12
      Sig.getParamTypes().assign(1, Ptr);
161
12
    }
162
10.0k
    if (AsyncOpt) {
163
      // Async lift returns the packed callback code, or nothing when stackful.
164
512
      Sig.getReturnTypes().clear();
165
512
      if (Canon.hasOption(ComponentCanonOptCode::Callback)) {
166
0
        Sig.getReturnTypes().push_back(I32V);
167
0
      }
168
9.52k
    } else if (ResultsIndirect) {
169
14
      Sig.getReturnTypes().assign(1, Ptr);
170
14
    }
171
    // Required options: lifting params lowers them into the callee's memory.
172
10.0k
    EXPECTED_TRY(CompCtx.checkRequiredOptions(
173
10.0k
        Canon,
174
10.0k
        ParamsNeedMemory || ParamsIndirect ||
175
10.0k
            (!AsyncOpt && (ResultsNeedMemory || ResultsIndirect)),
176
10.0k
        ParamsNeedMemory || ParamsIndirect, "canon lift"sv));
177
178
    // The callee must have exactly the flattened core type.
179
10.0k
    const auto *Callee = S.getCoreFunc(Canon.getIndex());
180
10.0k
    if (Callee == nullptr) {
181
8
      spdlog::error(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
182
8
      spdlog::error("    canon lift core function index {} out of bounds."sv,
183
8
                    Canon.getIndex());
184
8
      return Unexpect(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
185
8
    }
186
10.0k
    const auto &CalleeType = Callee->getCompositeType();
187
10.0k
    if (!CalleeType.isFunc() ||
188
10.0k
        CalleeType.getFuncType().getParamTypes() != Sig.getParamTypes()) {
189
1
      spdlog::error(ErrCode::Value::CanonLoweredParamsMismatch);
190
1
      spdlog::error("    canon lift core function does not match the "
191
1
                    "flattened parameters."sv);
192
1
      return Unexpect(ErrCode::Value::CanonLoweredParamsMismatch);
193
1
    }
194
10.0k
    if (CalleeType.getFuncType().getReturnTypes() != Sig.getReturnTypes()) {
195
2
      spdlog::error(ErrCode::Value::CanonLoweredResultsMismatch);
196
2
      spdlog::error("    canon lift core function does not match the "
197
2
                    "flattened results."sv);
198
2
      return Unexpect(ErrCode::Value::CanonLoweredResultsMismatch);
199
2
    }
200
201
    // post-return has type (func (param flat_results)).
202
10.0k
    for (const auto &Opt : Canon.getOptions()) {
203
1.88k
      if (Opt.getCode() != ComponentCanonOptCode::PostReturn) {
204
812
        continue;
205
812
      }
206
1.07k
      const auto *Post = S.getCoreFunc(Opt.getIndex());
207
1.07k
      if (Post == nullptr) {
208
3
        spdlog::error(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
209
3
        spdlog::error("    post-return core function index {} out of bounds."sv,
210
3
                      Opt.getIndex());
211
3
        return Unexpect(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
212
3
      }
213
1.06k
      const auto &PT = Post->getCompositeType();
214
1.06k
      if (!PT.isFunc() ||
215
1.06k
          PT.getFuncType().getParamTypes() != Sig.getReturnTypes() ||
216
1.06k
          !PT.getFuncType().getReturnTypes().empty()) {
217
1
        spdlog::error(ErrCode::Value::CanonPostReturnSignature);
218
1
        spdlog::error(
219
1
            "    post-return must take the lifted core results and return "
220
1
            "nothing."sv);
221
1
        return Unexpect(ErrCode::Value::CanonPostReturnSignature);
222
1
      }
223
1.06k
    }
224
225
10.0k
    S.addFunc(FI);
226
10.0k
    return {};
227
10.0k
  }
228
229
2.91k
  case ComponentCanonOpCode::Lower: {
230
2.91k
    const auto *FI = S.getFunc(Canon.getIndex());
231
2.91k
    if (FI == nullptr) {
232
20
      spdlog::error(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
233
20
      spdlog::error("    canon lower function index {} out of bounds."sv,
234
20
                    Canon.getIndex());
235
20
      return Unexpect(ErrCode::Value::ComponentFunctionIndexOutOfBounds);
236
20
    }
237
2.89k
    EXPECTED_TRY(CompCtx.checkOptions(Canon, false));
238
2.89k
    const bool AsyncOpt = Canon.hasOption(ComponentCanonOptCode::Async);
239
2.89k
    if (FI->FT == nullptr) {
240
0
      spdlog::error(ErrCode::Value::InvalidTypeReference);
241
0
      return Unexpect(ErrCode::Value::InvalidTypeReference);
242
0
    }
243
2.89k
    if (AsyncOpt && !FI->FT->isAsync()) {
244
1
      spdlog::error(ErrCode::Value::CanonAsyncRequiresAsyncType);
245
1
      spdlog::error("    canon lower with `async` needs `(func async ...)`."sv);
246
1
      return Unexpect(ErrCode::Value::CanonAsyncRequiresAsyncType);
247
1
    }
248
249
2.89k
    const ValType Ptr = CompCtx.getCanonPtrType(Canon);
250
2.89k
    EXPECTED_TRY(auto Sig, CompCtx.flattenFuncType(*FI, Ptr, ParamsNeedMemory,
251
2.89k
                                                   ResultsNeedMemory));
252
2.89k
    const bool ParamsIndirect =
253
2.89k
        Sig.getParamTypes().size() >
254
2.89k
        (AsyncOpt ? Component::Context::MaxFlatAsyncParams
255
2.89k
                  : Component::Context::MaxFlatParams);
256
2.89k
    const bool ResultsIndirect =
257
2.89k
        AsyncOpt
258
2.89k
            ? !Sig.getReturnTypes().empty()
259
2.89k
            : Sig.getReturnTypes().size() > Component::Context::MaxFlatResults;
260
2.89k
    if (ParamsIndirect) {
261
0
      Sig.getParamTypes().assign(1, Ptr);
262
0
    }
263
2.89k
    if (ResultsIndirect) {
264
      // The caller passes a pointer for the results as the last parameter.
265
3
      Sig.getParamTypes().push_back(Ptr);
266
3
      Sig.getReturnTypes().clear();
267
3
    }
268
2.89k
    if (AsyncOpt) {
269
      // Async lower: the core function returns the packed subtask state.
270
44
      Sig.getReturnTypes().assign(1, I32V);
271
44
    }
272
2.89k
    EXPECTED_TRY(
273
2.88k
        CompCtx.checkRequiredOptions(Canon,
274
2.88k
                                     ParamsNeedMemory || ResultsNeedMemory ||
275
2.88k
                                         ParamsIndirect || ResultsIndirect,
276
2.88k
                                     ResultsNeedMemory, "canon lower"sv));
277
2.88k
    return PushCoreFunc(Sig.getParamTypes(), Sig.getReturnTypes());
278
2.89k
  }
279
280
847
  case ComponentCanonOpCode::Resource__new: {
281
847
    EXPECTED_TRY(const auto Rep, LocalResourceRep("resource.new"sv));
282
836
    return PushCoreFunc({Rep}, {I32V});
283
847
  }
284
595
  case ComponentCanonOpCode::Resource__rep: {
285
595
    EXPECTED_TRY(const auto Rep, LocalResourceRep("resource.rep"sv));
286
578
    return PushCoreFunc({I32V}, {Rep});
287
595
  }
288
78
  case ComponentCanonOpCode::Resource__drop:
289
78
    EXPECTED_TRY(GetResourceEntry("resource.drop"sv));
290
70
    return PushCoreFunc({I32V}, {});
291
292
  // Async built-ins take their core signatures from the shared table.
293
219
  case ComponentCanonOpCode::Backpressure__inc:
294
417
  case ComponentCanonOpCode::Backpressure__dec:
295
417
    return PushBuiltin(I32V);
296
1.02k
  case ComponentCanonOpCode::Thread__index:
297
1.02k
    return PushBuiltin(I32V);
298
3.16k
  case ComponentCanonOpCode::Task__return: {
299
3.16k
    if (const auto &R = Canon.getResult();
300
3.16k
        R.has_value() && CompCtx.hasBorrow({*R, &S, nullptr})) {
301
1
      spdlog::error(ErrCode::Value::CanonTaskReturnBorrow);
302
1
      return Unexpect(ErrCode::Value::CanonTaskReturnBorrow);
303
1
    }
304
    // The results lower as the core parameters; task.return never reallocates.
305
3.16k
    EXPECTED_TRY(CompCtx.checkOptions(Canon, false));
306
3.16k
    if (Canon.hasOption(ComponentCanonOptCode::Realloc)) {
307
0
      spdlog::error(ErrCode::Value::CanonReallocOnBuiltin);
308
0
      spdlog::error("    realloc cannot be specified for task.return."sv);
309
0
      return Unexpect(ErrCode::Value::CanonReallocOnBuiltin);
310
0
    }
311
3.16k
    EXPECTED_TRY(RequireSync());
312
3.15k
    const ValType Ptr = CompCtx.getCanonPtrType(Canon);
313
3.15k
    std::vector<ValType> Params;
314
3.15k
    if (const auto &R = Canon.getResult(); R.has_value()) {
315
3.07k
      const Component::QualValType Q{*R, &S, nullptr};
316
3.07k
      if (!CompCtx.flattenValType(Q, Params, Ptr)) {
317
13
        spdlog::error(ErrCode::Value::InvalidTypeReference);
318
13
        return Unexpect(ErrCode::Value::InvalidTypeReference);
319
13
      }
320
3.06k
      ParamsNeedMemory = ParamsNeedMemory || CompCtx.needsMemory(Q);
321
3.06k
    }
322
3.14k
    if (Params.size() > Component::Context::MaxFlatParams) {
323
39
      Params.assign(1, Ptr);
324
39
      ParamsNeedMemory = true;
325
39
    }
326
3.14k
    EXPECTED_TRY(CompCtx.checkRequiredOptions(Canon, ParamsNeedMemory, false,
327
3.04k
                                              "task.return"sv));
328
3.04k
    return PushCoreFunc(std::move(Params), {});
329
3.14k
  }
330
171
  case ComponentCanonOpCode::Task__cancel:
331
171
    return PushBuiltin(I32V);
332
3.73k
  case ComponentCanonOpCode::Context__get:
333
3.87k
  case ComponentCanonOpCode::Context__set: {
334
3.87k
    const bool IsGet = Canon.getOpCode() == ComponentCanonOpCode::Context__get;
335
3.87k
    const std::string_view Name = IsGet ? "context.get"sv : "context.set"sv;
336
    // The slot immediate is stored as the constant value, not as an index.
337
3.87k
    if (Canon.getConstVal() >= Component::Context::MaxContextSlots) {
338
30
      spdlog::error(ErrCode::Value::ComponentContextSlotOutOfBounds);
339
30
      spdlog::error(
340
30
          "    Context slot {} out of bounds, must be less than {}."sv,
341
30
          Canon.getConstVal(), Component::Context::MaxContextSlots);
342
30
      return Unexpect(ErrCode::Value::ComponentContextSlotOutOfBounds);
343
30
    }
344
3.84k
    const ValType Ty = Canon.getContextType();
345
3.84k
    if (Ty != I32V && Ty != I64V) {
346
11
      spdlog::error(ErrCode::Value::ComponentContextTypeInvalid);
347
11
      spdlog::error("    `{}` only supports `i32` or `i64`."sv, Name);
348
11
      return Unexpect(ErrCode::Value::ComponentContextTypeInvalid);
349
11
    }
350
    // A 64-bit slot is gated the same way a 64-bit resource rep is.
351
3.83k
    if (Ty == I64V && !Conf.hasProposal(Proposal::Memory64)) {
352
0
      spdlog::error(ErrCode::Value::ComponentContextTypeInvalid);
353
0
      spdlog::error(ErrInfo::InfoProposal(Proposal::Memory64));
354
0
      spdlog::error("    a 64-bit `{}` needs the memory64 proposal."sv, Name);
355
0
      return Unexpect(ErrCode::Value::ComponentContextTypeInvalid);
356
0
    }
357
    // The whole component shares one thread-local slot type.
358
3.83k
    auto &Shared = CompCtx.getTop().ContextType;
359
3.83k
    if (Shared.has_value() && *Shared != Ty) {
360
1
      spdlog::error(ErrCode::Value::ComponentContextTypeMismatch);
361
1
      spdlog::error("    `{}` type must match previous context type."sv, Name);
362
1
      return Unexpect(ErrCode::Value::ComponentContextTypeMismatch);
363
1
    }
364
3.83k
    Shared = Ty;
365
3.83k
    if (IsGet) {
366
3.69k
      return PushCoreFunc({}, {Ty});
367
3.69k
    }
368
137
    return PushCoreFunc({Ty}, {});
369
3.83k
  }
370
14
  case ComponentCanonOpCode::Thread__yield:
371
14
    return PushBuiltin(I32V);
372
229
  case ComponentCanonOpCode::Subtask__cancel:
373
229
    return PushBuiltin(I32V);
374
7.44k
  case ComponentCanonOpCode::Subtask__drop:
375
7.44k
    return PushBuiltin(I32V);
376
1.22k
  case ComponentCanonOpCode::Stream__new:
377
1.22k
    EXPECTED_TRY(CheckTypeImmediate(true));
378
1.21k
    return PushBuiltin(I32V);
379
90
  case ComponentCanonOpCode::Stream__read:
380
1.05k
  case ComponentCanonOpCode::Stream__write: {
381
1.05k
    EXPECTED_TRY(const auto *Entry, CheckTypeImmediate(true));
382
1.04k
    const auto &Elem = Entry->getDefValType()->getStream().ValTy;
383
    // Reading a payload that allocates needs realloc; writing never does.
384
1.04k
    const bool NeedRealloc =
385
1.04k
        Canon.getOpCode() == ComponentCanonOpCode::Stream__read &&
386
85
        Elem.has_value() &&
387
0
        CompCtx.needsMemory({*Elem, Entry->Home, Entry->Remap});
388
1.04k
    EXPECTED_TRY(BuiltinOptions(Elem.has_value(), NeedRealloc));
389
1.04k
    return PushBuiltin(CompCtx.getCanonPtrType(Canon));
390
1.04k
  }
391
691
  case ComponentCanonOpCode::Stream__cancel_read:
392
737
  case ComponentCanonOpCode::Stream__cancel_write:
393
737
    EXPECTED_TRY(CheckTypeImmediate(true));
394
730
    return PushBuiltin(I32V);
395
76
  case ComponentCanonOpCode::Stream__drop_readable:
396
133
  case ComponentCanonOpCode::Stream__drop_writable:
397
210
  case ComponentCanonOpCode::Stream__forward:
398
210
    EXPECTED_TRY(CheckTypeImmediate(true));
399
197
    return PushBuiltin(I32V);
400
56.1k
  case ComponentCanonOpCode::Future__new:
401
56.1k
    EXPECTED_TRY(CheckTypeImmediate(false));
402
56.1k
    return PushBuiltin(I32V);
403
199
  case ComponentCanonOpCode::Future__read:
404
271
  case ComponentCanonOpCode::Future__write: {
405
271
    EXPECTED_TRY(const auto *Entry, CheckTypeImmediate(false));
406
267
    const auto &Elem = Entry->getDefValType()->getFuture().ValTy;
407
    // Reading a payload that allocates needs realloc; writing never does.
408
267
    const bool NeedRealloc =
409
267
        Canon.getOpCode() == ComponentCanonOpCode::Future__read &&
410
198
        Elem.has_value() &&
411
1
        CompCtx.needsMemory({*Elem, Entry->Home, Entry->Remap});
412
267
    EXPECTED_TRY(BuiltinOptions(Elem.has_value(), NeedRealloc));
413
265
    return PushBuiltin(CompCtx.getCanonPtrType(Canon));
414
267
  }
415
332
  case ComponentCanonOpCode::Future__cancel_read:
416
2.68k
  case ComponentCanonOpCode::Future__cancel_write:
417
2.68k
    EXPECTED_TRY(CheckTypeImmediate(false));
418
2.67k
    return PushBuiltin(I32V);
419
91
  case ComponentCanonOpCode::Future__drop_readable:
420
120k
  case ComponentCanonOpCode::Future__drop_writable:
421
121k
  case ComponentCanonOpCode::Future__forward:
422
121k
    EXPECTED_TRY(CheckTypeImmediate(false));
423
121k
    return PushBuiltin(I32V);
424
3
  case ComponentCanonOpCode::Error_context__new:
425
3
    EXPECTED_TRY(BuiltinOptions(true, false));
426
0
    EXPECTED_TRY(RequireSync());
427
0
    return PushBuiltin(CompCtx.getCanonPtrType(Canon));
428
6
  case ComponentCanonOpCode::Error_context__debug_message:
429
6
    EXPECTED_TRY(BuiltinOptions(true, true));
430
0
    EXPECTED_TRY(RequireSync());
431
0
    return PushBuiltin(CompCtx.getCanonPtrType(Canon));
432
1.00k
  case ComponentCanonOpCode::Error_context__drop:
433
1.00k
    return PushBuiltin(I32V);
434
4.73k
  case ComponentCanonOpCode::Waitable_set__new:
435
4.73k
    return PushBuiltin(I32V);
436
6
  case ComponentCanonOpCode::Waitable_set__wait:
437
22
  case ComponentCanonOpCode::Waitable_set__poll: {
438
22
    if (Canon.getIndex() >= S.CoreMemories.size()) {
439
22
      spdlog::error(ErrCode::Value::ComponentMemoryIndexOutOfBounds);
440
22
      spdlog::error("    Canonical built-in memory index {} out of bounds."sv,
441
22
                    Canon.getIndex());
442
22
      return Unexpect(ErrCode::Value::ComponentMemoryIndexOutOfBounds);
443
22
    }
444
    // The payload address carries the address type of the given memory.
445
0
    const auto *Mem = S.CoreMemories[Canon.getIndex()];
446
0
    const ValType Addr =
447
0
        ValType(Mem != nullptr && Mem->getLimit().is64() ? TypeCode::I64
448
0
                                                         : TypeCode::I32);
449
0
    return PushBuiltin(Addr);
450
22
  }
451
65
  case ComponentCanonOpCode::Waitable_set__drop:
452
65
    return PushBuiltin(I32V);
453
1.00k
  case ComponentCanonOpCode::Waitable__join:
454
1.00k
    return PushBuiltin(I32V);
455
3
  case ComponentCanonOpCode::Thread__new_indirect: {
456
    // The start type must be (func (param i32)) and the target a core table.
457
3
    const auto *CT = S.getCoreType(Canon.getIndex());
458
3
    const bool TypeOk =
459
3
        CT != nullptr && CT->Func != nullptr &&
460
1
        CT->Func->getCompositeType().isFunc() &&
461
0
        CT->Func->getCompositeType().getFuncType().getParamTypes() ==
462
0
            std::vector<ValType>{I32V} &&
463
0
        CT->Func->getCompositeType().getFuncType().getReturnTypes().empty();
464
3
    if (!TypeOk) {
465
3
      spdlog::error(ErrCode::Value::InvalidTypeReference);
466
3
      spdlog::error(
467
3
          "    thread.new-indirect start type must be (func (param i32))."sv);
468
3
      return Unexpect(ErrCode::Value::InvalidTypeReference);
469
3
    }
470
0
    if (Canon.getTargetIndex() >= S.CoreTables.size()) {
471
0
      spdlog::error(ErrCode::Value::DefTypeIndexOutOfBounds);
472
0
      spdlog::error("    thread.new-indirect table index {} out of bounds."sv,
473
0
                    Canon.getTargetIndex());
474
0
      return Unexpect(ErrCode::Value::DefTypeIndexOutOfBounds);
475
0
    }
476
    // The table element type must be exactly `funcref`, not a subtype.
477
0
    const auto *Tab = S.CoreTables[Canon.getTargetIndex()];
478
0
    if (Tab != nullptr && Tab->getRefType() != ValType(TypeCode::FuncRef)) {
479
0
      spdlog::error(ErrCode::Value::CanonThreadTableType);
480
0
      return Unexpect(ErrCode::Value::CanonThreadTableType);
481
0
    }
482
    // The thread index carries the address type of the table.
483
0
    return PushBuiltin(ValType(Tab != nullptr && Tab->getLimit().is64()
484
0
                                   ? TypeCode::I64
485
0
                                   : TypeCode::I32));
486
0
  }
487
88
  case ComponentCanonOpCode::Thread__resume_later:
488
88
    return PushBuiltin(I32V);
489
302
  case ComponentCanonOpCode::Thread__suspend:
490
302
    return PushBuiltin(I32V);
491
750
  case ComponentCanonOpCode::Thread__suspend_then_resume:
492
1.05k
  case ComponentCanonOpCode::Thread__yield_then_resume:
493
32.5k
  case ComponentCanonOpCode::Thread__suspend_then_promote:
494
32.6k
  case ComponentCanonOpCode::Thread__yield_then_promote:
495
32.6k
    return PushBuiltin(I32V);
496
10
  default:
497
10
    spdlog::error(ErrCode::Value::ComponentNotImplValidator);
498
10
    spdlog::error("    canonical built-in {} is not supported yet."sv,
499
10
                  static_cast<uint32_t>(Canon.getOpCode()));
500
10
    return Unexpect(ErrCode::Value::ComponentNotImplValidator);
501
254k
  }
502
254k
}
503
504
} // namespace Validator
505
} // namespace WasmEdge