Coverage Report

Created: 2026-11-12 06:35

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