Coverage Report

Created: 2026-10-10 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/WasmEdge/lib/llvm/compiler.cpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright The WasmEdge Authors
3
4
#include "llvm/compiler.h"
5
6
#include "compiler/context.h"
7
#include "compiler/function_compiler.h"
8
9
#include "common/spdlog.h"
10
#include "data.h"
11
#include "llvm.h"
12
13
#include <algorithm>
14
#include <cstdint>
15
#include <string>
16
17
namespace LLVM = WasmEdge::LLVM;
18
using namespace std::literals;
19
20
namespace {
21
22
struct RAIICleanup {
23
  RAIICleanup(LLVM::Compiler::CompileContext *&ContextRef,
24
              LLVM::Compiler::CompileContext *NewContext)
25
2.32k
      : Context(ContextRef) {
26
2.32k
    Context = NewContext;
27
2.32k
  }
28
2.32k
  ~RAIICleanup() { Context = nullptr; }
29
  LLVM::Compiler::CompileContext *&Context;
30
};
31
32
// Translate Compiler::OptimizationLevel to llvm::PassBuilder version
33
#if LLVM_VERSION_MAJOR >= 13
34
static inline const char *
35
toLLVMLevel(WasmEdge::CompilerConfigure::OptimizationLevel Level) noexcept {
36
  using OL = WasmEdge::CompilerConfigure::OptimizationLevel;
37
  switch (Level) {
38
  case OL::O0:
39
    return "default<O0>,function(tailcallelim)";
40
  case OL::O1:
41
    return "default<O1>,function(tailcallelim)";
42
  case OL::O2:
43
    return "default<O2>";
44
  case OL::O3:
45
    return "default<O3>";
46
#if LLVM_VERSION_MAJOR >= 23
47
  case OL::Os:
48
  case OL::Oz:
49
    return "default<O2>";
50
#else
51
  case OL::Os:
52
    return "default<Os>";
53
  case OL::Oz:
54
    return "default<Oz>";
55
#endif
56
  default:
57
    assumingUnreachable();
58
  }
59
}
60
#else
61
static inline std::pair<unsigned int, unsigned int>
62
2.10k
toLLVMLevel(WasmEdge::CompilerConfigure::OptimizationLevel Level) noexcept {
63
2.10k
  using OL = WasmEdge::CompilerConfigure::OptimizationLevel;
64
2.10k
  switch (Level) {
65
0
  case OL::O0:
66
0
    return {0, 0};
67
0
  case OL::O1:
68
0
    return {1, 0};
69
0
  case OL::O2:
70
0
    return {2, 0};
71
2.10k
  case OL::O3:
72
2.10k
    return {3, 0};
73
0
  case OL::Os:
74
0
    return {2, 1};
75
0
  case OL::Oz:
76
0
    return {2, 2};
77
0
  default:
78
0
    assumingUnreachable();
79
2.10k
  }
80
2.10k
}
81
#endif
82
83
static inline LLVMCodeGenOptLevel toLLVMCodeGenLevel(
84
2.10k
    WasmEdge::CompilerConfigure::OptimizationLevel Level) noexcept {
85
2.10k
  using OL = WasmEdge::CompilerConfigure::OptimizationLevel;
86
2.10k
  switch (Level) {
87
0
  case OL::O0:
88
0
    return LLVMCodeGenLevelNone;
89
0
  case OL::O1:
90
0
    return LLVMCodeGenLevelLess;
91
0
  case OL::O2:
92
0
    return LLVMCodeGenLevelDefault;
93
2.10k
  case OL::O3:
94
2.10k
    return LLVMCodeGenLevelAggressive;
95
0
  case OL::Os:
96
0
    return LLVMCodeGenLevelDefault;
97
0
  case OL::Oz:
98
0
    return LLVMCodeGenLevelDefault;
99
0
  default:
100
0
    assumingUnreachable();
101
2.10k
  }
102
2.10k
}
103
} // namespace
104
105
namespace WasmEdge {
106
namespace LLVM {
107
108
2.32k
Expect<void> Compiler::checkConfigure() noexcept {
109
2.32k
  if (Conf.hasProposal(Proposal::Annotations)) {
110
0
    spdlog::error(ErrCode::Value::InvalidAOTConfigure);
111
0
    spdlog::error("    Proposal Custom Annotation Syntax is not yet supported "
112
0
                  "in WasmEdge AOT/JIT."sv);
113
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
114
0
  }
115
2.32k
  return {};
116
2.32k
}
117
118
Expect<void> Compiler::optimize(LLVM::Module &LLModule,
119
2.10k
                                LLVM::TargetMachine &TM) noexcept {
120
2.10k
  spdlog::info("optimize start"sv);
121
2.10k
  auto Triple = LLModule.getTarget();
122
2.10k
  auto [TheTarget, ErrorMessage] = LLVM::Target::getFromTriple(Triple);
123
2.10k
  if (ErrorMessage) {
124
0
    spdlog::error("getFromTriple failed:{}"sv, ErrorMessage.string_view());
125
0
    return Unexpect(ErrCode::Value::IllegalPath);
126
0
  }
127
128
2.10k
  std::string CPUName;
129
#if defined(__riscv) && __riscv_xlen == 64
130
  CPUName = "generic-rv64"s;
131
#else
132
2.10k
  if (!Conf.getCompilerConfigure().isGenericBinary()) {
133
2.10k
    CPUName = LLVM::getHostCPUName().string_view();
134
2.10k
  } else {
135
0
    CPUName = "generic"s;
136
0
  }
137
2.10k
#endif
138
139
  // On RISC-V we use generic-rv64 as the CPU, so also use default
140
  // features; host features under QEMU can be inconsistent (e.g.
141
  // zvl*b without v) which LLVM >= 20 rejects.
142
2.10k
  TM = LLVM::TargetMachine::create(
143
2.10k
      TheTarget, Triple, CPUName.c_str(),
144
#if defined(__riscv) && __riscv_xlen == 64
145
      "",
146
#else
147
2.10k
      LLVM::getHostCPUFeatures().unwrap(),
148
2.10k
#endif
149
2.10k
      toLLVMCodeGenLevel(Conf.getCompilerConfigure().getOptimizationLevel()),
150
2.10k
      LLVMRelocPIC, LLVMCodeModelDefault);
151
152
#if LLVM_VERSION_MAJOR >= 13
153
#if LLVM_VERSION_MAJOR >= 23
154
  {
155
    using OL = CompilerConfigure::OptimizationLevel;
156
    const auto Level = Conf.getCompilerConfigure().getOptimizationLevel();
157
    if (Level == OL::Os || Level == OL::Oz) {
158
      auto LLContext = LLModule.getContext();
159
      const auto OptSize = LLVM::Attribute::createEnum(
160
          LLContext, LLVM::Core::OptimizeForSize, 0);
161
      const auto MinSize =
162
          LLVM::Attribute::createEnum(LLContext, LLVM::Core::MinSize, 0);
163
      for (auto Fn = LLModule.getFirstFunction(); Fn;
164
           Fn = Fn.getNextFunction()) {
165
        if (Fn.isDeclaration()) {
166
          continue;
167
        }
168
        Fn.addFnAttr(OptSize);
169
        if (Level == OL::Oz) {
170
          Fn.addFnAttr(MinSize);
171
        }
172
      }
173
    }
174
  }
175
#endif
176
  auto PBO = LLVM::PassBuilderOptions::create();
177
  if (auto Error = PBO.runPasses(
178
          LLModule,
179
          toLLVMLevel(Conf.getCompilerConfigure().getOptimizationLevel()),
180
          TM)) {
181
    spdlog::error("{}"sv, Error.message().string_view());
182
  }
183
#else
184
2.10k
  auto FP = LLVM::PassManager::createForModule(LLModule);
185
2.10k
  auto MP = LLVM::PassManager::create();
186
187
2.10k
  TM.addAnalysisPasses(MP);
188
2.10k
  TM.addAnalysisPasses(FP);
189
2.10k
  {
190
2.10k
    auto PMB = LLVM::PassManagerBuilder::create();
191
2.10k
    auto [OptLevel, SizeLevel] =
192
2.10k
        toLLVMLevel(Conf.getCompilerConfigure().getOptimizationLevel());
193
2.10k
    PMB.setOptLevel(OptLevel);
194
2.10k
    PMB.setSizeLevel(SizeLevel);
195
2.10k
    PMB.populateFunctionPassManager(FP);
196
2.10k
    PMB.populateModulePassManager(MP);
197
2.10k
  }
198
2.10k
  switch (Conf.getCompilerConfigure().getOptimizationLevel()) {
199
0
  case CompilerConfigure::OptimizationLevel::O0:
200
0
  case CompilerConfigure::OptimizationLevel::O1:
201
0
    FP.addTailCallEliminationPass();
202
0
    break;
203
2.10k
  default:
204
2.10k
    break;
205
2.10k
  }
206
207
2.10k
  FP.initializeFunctionPassManager();
208
23.0k
  for (auto Fn = LLModule.getFirstFunction(); Fn; Fn = Fn.getNextFunction()) {
209
20.9k
    FP.runFunctionPassManager(Fn);
210
20.9k
  }
211
2.10k
  FP.finalizeFunctionPassManager();
212
2.10k
  MP.runPassManager(LLModule);
213
2.10k
#endif
214
215
2.10k
  spdlog::info("optimize done"sv);
216
2.10k
  return {};
217
2.10k
}
218
219
// Initialize the LLVM module held by the data for compilation: set the
220
// target triple and the PIC level, and return the LLVM context.
221
2.32k
static LLVM::Context initLLVMModule(LLVM::Data &D) noexcept {
222
2.32k
  auto LLContext = D.extract().getLLContext();
223
2.32k
  LLVM::Core::init(LLContext.unwrap());
224
2.32k
  auto &LLModule = D.extract().LLModule;
225
2.32k
  LLModule.setTarget(LLVM::getDefaultTargetTriple().unwrap());
226
2.32k
  LLModule.addFlag(LLVMModuleFlagBehaviorError, "PIC Level"sv, 2);
227
2.32k
  return LLContext;
228
2.32k
}
229
230
2.32k
Expect<Data> Compiler::compile(const AST::Module &Module) noexcept {
231
  // Check that the module is validated.
232
2.32k
  if (unlikely(!Module.getIsValidated())) {
233
0
    spdlog::error(ErrCode::Value::NotValidated);
234
0
    return Unexpect(ErrCode::Value::NotValidated);
235
0
  }
236
237
2.32k
  std::unique_lock Lock(Mutex);
238
2.32k
  spdlog::info("compile start"sv);
239
240
2.32k
  LLVM::Data D;
241
2.32k
  auto LLContext = initLLVMModule(D);
242
2.32k
  auto &LLModule = D.extract().LLModule;
243
244
2.32k
  CompileContext NewContext(LLContext, LLModule,
245
2.32k
                            Conf.getCompilerConfigure().isGenericBinary());
246
2.32k
  RAIICleanup Cleanup(Context, &NewContext);
247
2.32k
  Context->addVersionGlobal();
248
249
  // Compile all sections and the function declarations.
250
2.32k
  compileSections(Module, false);
251
  // Compile all function bodies.
252
2.32k
  const auto DefinedCount = Module.getDefinedFuncCount();
253
13.4k
  for (uint32_t I = 0; I < DefinedCount; ++I) {
254
11.1k
    EXPECTED_TRY(compileFunctionBody(I));
255
11.1k
  }
256
  // Compile ExportSection.
257
2.32k
  compile(Module.getExportSection());
258
  // StartSection is not required for compilation.
259
260
2.32k
  spdlog::info("verify start"sv);
261
2.32k
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
262
219
    spdlog::error("LLVM module verification failed: {}"sv,
263
219
                  VerifyMsg.string_view());
264
219
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
265
219
  }
266
267
2.10k
  auto &TM = D.extract().TM;
268
2.10k
  EXPECTED_TRY(optimize(LLModule, TM));
269
2.10k
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
270
1
    spdlog::error("LLVM module verification failed after optimization: {}"sv,
271
1
                  VerifyMsg.string_view());
272
1
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
273
1
  }
274
275
  // Set initializer for constant value
276
2.10k
  Context->finalizeIntrinsicsTable();
277
2.10k
  return Expect<Data>{std::move(D)};
278
2.10k
}
279
280
void Compiler::compile(const AST::TypeSection &TypeSec,
281
2.32k
                       bool DeclarationsOnly) noexcept {
282
2.32k
  auto WrapperTy = LLVM::Type::getFunctionType(
283
2.32k
      Context->VoidTy,
284
2.32k
      {Context->ModCtxPtrTy, Context->ExecCtxPtrTy, Context->Int8PtrTy,
285
2.32k
       Context->Int8PtrTy, Context->Int8PtrTy},
286
2.32k
      false);
287
2.32k
  auto SubTypes = TypeSec.getContent();
288
2.32k
  const auto Size = SubTypes.size();
289
2.32k
  if (Size == 0) {
290
135
    return;
291
135
  }
292
2.19k
  Context->CompositeTypes.reserve(Size);
293
2.19k
  Context->FunctionWrappers.reserve(Size);
294
295
4.82k
  auto SetFuncAttributes = [&](auto FDecl) {
296
4.82k
    FDecl.setVisibility(LLVMProtectedVisibility);
297
4.82k
    FDecl.setDSOLocal(true);
298
4.82k
    FDecl.setDLLStorageClass(LLVMDLLExportStorageClass);
299
4.82k
    FDecl.addFnAttr(Context->NoStackArgProbe);
300
4.82k
    FDecl.addFnAttr(Context->StrictFP);
301
4.82k
    FDecl.addFnAttr(Context->UWTable);
302
4.82k
    FDecl.addParamAttr(0, Context->ReadOnly);
303
4.82k
    FDecl.addParamAttr(0, Context->NoAlias);
304
4.82k
    FDecl.addParamAttr(1, Context->ReadOnly);
305
4.82k
    FDecl.addParamAttr(1, Context->NoAlias);
306
4.82k
    FDecl.addParamAttr(2, Context->NoAlias);
307
4.82k
    FDecl.addParamAttr(3, Context->NoAlias);
308
4.82k
    FDecl.addParamAttr(4, Context->NoAlias);
309
4.82k
  };
310
311
  // Iterate and compile types.
312
7.24k
  for (size_t I = 0; I < Size; ++I) {
313
5.05k
    const auto &CompType = SubTypes[I].getCompositeType();
314
5.05k
    const auto Name = fmt::format("t{}"sv, Context->CompositeTypes.size());
315
5.05k
    if (CompType.isFunc()) {
316
      // Check that the function type is unique.
317
4.86k
      {
318
4.86k
        bool Unique = true;
319
20.5k
        for (size_t J = 0; J < I; ++J) {
320
15.8k
          if (Context->CompositeTypes[J] &&
321
15.8k
              Context->CompositeTypes[J]->isFunc()) {
322
15.6k
            const auto &OldFuncType = Context->CompositeTypes[J]->getFuncType();
323
15.6k
            if (OldFuncType == CompType.getFuncType()) {
324
227
              Unique = false;
325
227
              Context->CompositeTypes.push_back(Context->CompositeTypes[J]);
326
227
              if (DeclarationsOnly) {
327
0
                auto FDecl = Context->LLModule.get().addFunction(
328
0
                    WrapperTy, LLVMExternalLinkage, Name.c_str());
329
0
                SetFuncAttributes(FDecl);
330
0
                Context->FunctionWrappers.push_back(FDecl);
331
227
              } else {
332
227
                auto F = Context->FunctionWrappers[J];
333
227
                Context->FunctionWrappers.push_back(F);
334
227
                auto A = Context->LLModule.get().addAlias(WrapperTy, F,
335
227
                                                          Name.c_str());
336
227
                A.setLinkage(LLVMExternalLinkage);
337
227
                A.setVisibility(LLVMProtectedVisibility);
338
227
                A.setDSOLocal(true);
339
227
                A.setDLLStorageClass(LLVMDLLExportStorageClass);
340
227
              }
341
227
              break;
342
227
            }
343
15.6k
          }
344
15.8k
        }
345
4.86k
        if (!Unique) {
346
227
          continue;
347
227
        }
348
4.86k
      }
349
350
      // Create Wrapper
351
4.64k
      auto F = Context->LLModule.get().addFunction(
352
4.64k
          WrapperTy, LLVMExternalLinkage, Name.c_str());
353
4.64k
      {
354
4.64k
        SetFuncAttributes(F);
355
356
4.64k
        if (!DeclarationsOnly) {
357
4.64k
          LLVM::Builder Builder(Context->LLContext);
358
4.64k
          Builder.positionAtEnd(
359
4.64k
              LLVM::BasicBlock::create(Context->LLContext, F, "entry"));
360
361
4.64k
          auto FTy = toLLVMType(Context->LLContext, Context->ModCtxPtrTy,
362
4.64k
                                Context->ExecCtxPtrTy, CompType.getFuncType());
363
4.64k
          auto RTy = FTy.getReturnType();
364
4.64k
          std::vector<LLVM::Type> FPTy(FTy.getNumParams());
365
4.64k
          FTy.getParamTypes(FPTy);
366
367
4.64k
          const size_t ArgCount = FPTy.size() - 2;
368
4.64k
          auto ModCtxPtr = F.getFirstParam();
369
4.64k
          auto ExecCtxPtr = ModCtxPtr.getNextParam();
370
4.64k
          auto RawFunc = LLVM::FunctionCallee{
371
4.64k
              FTy, Builder.createBitCast(ExecCtxPtr.getNextParam(),
372
4.64k
                                         FTy.getPointerTo())};
373
4.64k
          auto RawArgs = ExecCtxPtr.getNextParam().getNextParam();
374
4.64k
          auto RawRets = RawArgs.getNextParam();
375
376
4.64k
          std::vector<LLVM::Value> Args;
377
4.64k
          Args.reserve(FTy.getNumParams());
378
4.64k
          Args.push_back(ModCtxPtr);
379
4.64k
          Args.push_back(ExecCtxPtr);
380
9.85k
          for (size_t J = 0; J < ArgCount; ++J) {
381
5.21k
            Args.push_back(Builder.createValuePtrLoad(
382
5.21k
                FPTy[J + 2], RawArgs, Context->Int8Ty, J * LLVM::kValSize));
383
5.21k
          }
384
385
4.64k
          auto Ret = Builder.createCall(RawFunc, Args);
386
4.64k
          if (RTy.isVoidTy()) {
387
            // nothing to do
388
3.00k
          } else if (RTy.isStructTy()) {
389
375
            auto Rets = unpackStruct(Builder, Ret);
390
375
            Builder.createArrayPtrStore(Rets, RawRets, Context->Int8Ty,
391
375
                                        LLVM::kValSize);
392
2.63k
          } else {
393
2.63k
            Builder.createValuePtrStore(Ret, RawRets, Context->Int8Ty);
394
2.63k
          }
395
4.64k
          Builder.createRetVoid();
396
4.64k
        }
397
4.64k
      }
398
      // Copy wrapper, param and return lists to module instance.
399
4.64k
      Context->FunctionWrappers.push_back(F);
400
4.64k
    } else {
401
      // Non function type case. Create empty wrapper.
402
184
      auto F = Context->LLModule.get().addFunction(
403
184
          WrapperTy, LLVMExternalLinkage, Name.c_str());
404
184
      {
405
184
        SetFuncAttributes(F);
406
407
184
        if (!DeclarationsOnly) {
408
184
          LLVM::Builder Builder(Context->LLContext);
409
184
          Builder.positionAtEnd(
410
184
              LLVM::BasicBlock::create(Context->LLContext, F, "entry"));
411
184
          Builder.createRetVoid();
412
184
        }
413
184
      }
414
184
      Context->FunctionWrappers.push_back(F);
415
184
    }
416
4.82k
    Context->CompositeTypes.push_back(&CompType);
417
4.82k
  }
418
2.19k
}
419
420
2.32k
void Compiler::compile(const AST::ImportSection &ImportSec) noexcept {
421
  // Iterate and compile import descriptions.
422
2.32k
  for (const auto &ImpDesc : ImportSec.getContent()) {
423
    // Get data from import description.
424
573
    const auto &ExtType = ImpDesc.getExternalType();
425
426
    // Add the imports to the module instance.
427
573
    switch (ExtType) {
428
379
    case ExternalType::Function: // Function type index
429
379
    {
430
379
      const auto FuncID = static_cast<uint32_t>(Context->Functions.size());
431
      // Get the function type index in module.
432
379
      uint32_t TypeIdx = ImpDesc.getExternalFuncTypeIdx();
433
379
      assuming(TypeIdx < Context->CompositeTypes.size());
434
379
      assuming(Context->CompositeTypes[TypeIdx]->isFunc());
435
379
      const auto &FuncType = Context->CompositeTypes[TypeIdx]->getFuncType();
436
379
      auto FTy = toLLVMType(Context->LLContext, Context->ModCtxPtrTy,
437
379
                            Context->ExecCtxPtrTy, FuncType);
438
379
      auto RTy = FTy.getReturnType();
439
379
      auto F =
440
379
          LLVM::FunctionCallee{FTy, Context->LLModule.get().addFunction(
441
379
                                        FTy, LLVMInternalLinkage,
442
379
                                        fmt::format("f{}"sv, FuncID).c_str())};
443
379
      F.Fn.setDSOLocal(true);
444
379
      F.Fn.addFnAttr(Context->NoStackArgProbe);
445
379
      F.Fn.addFnAttr(Context->StrictFP);
446
379
      F.Fn.addFnAttr(Context->UWTable);
447
379
      F.Fn.addParamAttr(0, Context->ReadOnly);
448
379
      F.Fn.addParamAttr(0, Context->NoAlias);
449
450
379
      LLVM::Builder Builder(Context->LLContext);
451
379
      Builder.positionAtEnd(
452
379
          LLVM::BasicBlock::create(Context->LLContext, F.Fn, "entry"));
453
454
379
      const auto ArgSize = FuncType.getParamTypes().size();
455
379
      const auto RetSize =
456
379
          RTy.isVoidTy() ? 0 : FuncType.getReturnTypes().size();
457
458
379
      LLVM::Value Args = Builder.createArray(ArgSize, LLVM::kValSize);
459
379
      LLVM::Value Rets = Builder.createArray(RetSize, LLVM::kValSize);
460
461
379
      auto ModCtx = Builder.createLoad(Context->ModCtxTy, F.Fn.getFirstParam());
462
379
      auto Arg = F.Fn.getFirstParam().getNextParam();
463
627
      for (unsigned I = 0; I < ArgSize; ++I) {
464
248
        Arg = Arg.getNextParam();
465
248
        Builder.createValuePtrStore(Arg, Args, Context->Int8Ty,
466
248
                                    I * LLVM::kValSize);
467
248
      }
468
469
379
      Builder.createCall(
470
379
          Context->getIntrinsic(Builder, Executable::Intrinsics::kCall,
471
379
                                LLVM::Type::getFunctionType(
472
379
                                    Context->VoidTy,
473
379
                                    {Context->Int8PtrTy, Context->Int32Ty,
474
379
                                     Context->Int8PtrTy, Context->Int8PtrTy},
475
379
                                    false)),
476
379
          {Context->getModuleInst(Builder, ModCtx),
477
379
           Context->LLContext.getInt32(FuncID), Args, Rets});
478
479
379
      if (RetSize == 0) {
480
253
        Builder.createRetVoid();
481
253
      } else if (RetSize == 1) {
482
88
        Builder.createRet(
483
88
            Builder.createValuePtrLoad(RTy, Rets, Context->Int8Ty));
484
88
      } else {
485
38
        Builder.createAggregateRet(Builder.createArrayPtrLoad(
486
38
            RetSize, RTy, Rets, Context->Int8Ty, LLVM::kValSize));
487
38
      }
488
489
379
      Context->Functions.emplace_back(TypeIdx, F, nullptr);
490
379
      Context->ImportCount++;
491
379
      break;
492
379
    }
493
61
    case ExternalType::Table: // Table type
494
61
    {
495
      // Get table address type. External type checked in validation.
496
61
      const auto &TabType = ImpDesc.getExternalTableType();
497
61
      const auto AddrType = TabType.getLimit().getAddrType();
498
61
      auto Type = toLLVMType(Context->LLContext, AddrType);
499
61
      Context->TableAddrTypes.push_back(Type);
500
61
      break;
501
379
    }
502
58
    case ExternalType::Memory: // Memory type
503
58
    {
504
      // Get memory address type. External type checked in validation.
505
58
      const auto &MemType = ImpDesc.getExternalMemoryType();
506
58
      const auto AddrType = MemType.getLimit().getAddrType();
507
58
      auto Type = toLLVMType(Context->LLContext, AddrType);
508
58
      Context->MemoryAddrTypes.push_back(Type);
509
58
      break;
510
379
    }
511
43
    case ExternalType::Global: // Global type
512
43
    {
513
      // Get global type. External type checked in validation.
514
43
      const auto &GlobType = ImpDesc.getExternalGlobalType();
515
43
      const auto &ValType = GlobType.getValType();
516
43
      auto Type = toLLVMType(Context->LLContext, ValType);
517
43
      Context->Globals.push_back(Type);
518
43
      break;
519
379
    }
520
32
    case ExternalType::Tag: // Tag type
521
32
    {
522
      // Get the tag type index. External type checked in validation.
523
32
      const auto &TgType = ImpDesc.getExternalTagType();
524
32
      Context->Tags.push_back(TgType.getTypeIdx());
525
32
      break;
526
379
    }
527
0
    default:
528
0
      assumingUnreachable();
529
573
    }
530
573
  }
531
2.32k
}
532
533
2.32k
void Compiler::compile(const AST::ExportSection &) noexcept {}
534
535
2.32k
void Compiler::compile(const AST::GlobalSection &GlobalSec) noexcept {
536
2.32k
  for (const auto &GlobalSeg : GlobalSec.getContent()) {
537
163
    const auto &ValType = GlobalSeg.getGlobalType().getValType();
538
163
    auto Type = toLLVMType(Context->LLContext, ValType);
539
163
    Context->Globals.push_back(Type);
540
163
  }
541
2.32k
}
542
543
void Compiler::compile(const AST::MemorySection &MemorySec,
544
2.32k
                       const AST::DataSection &) noexcept {
545
2.32k
  for (const auto &MemType : MemorySec.getContent()) {
546
1.05k
    const auto AddrType = MemType.getLimit().getAddrType();
547
1.05k
    auto Type = toLLVMType(Context->LLContext, AddrType);
548
1.05k
    Context->MemoryAddrTypes.push_back(Type);
549
1.05k
  }
550
2.32k
}
551
552
void Compiler::compile(const AST::TableSection &TableSec,
553
2.32k
                       const AST::ElementSection &) noexcept {
554
2.32k
  for (const auto &TableSeg : TableSec.getContent()) {
555
489
    const auto AddrType = TableSeg.getTableType().getLimit().getAddrType();
556
489
    auto Type = toLLVMType(Context->LLContext, AddrType);
557
489
    Context->TableAddrTypes.push_back(Type);
558
489
  }
559
2.32k
}
560
561
2.32k
void Compiler::compile(const AST::TagSection &TagSec) noexcept {
562
2.32k
  for (const auto &TgType : TagSec.getContent()) {
563
35
    Context->Tags.push_back(TgType.getTypeIdx());
564
35
  }
565
2.32k
}
566
567
void Compiler::compileSections(const AST::Module &Module,
568
2.32k
                               bool DeclarationsOnly) noexcept {
569
  // Compile Function Types
570
2.32k
  compile(Module.getTypeSection(), DeclarationsOnly);
571
  // Compile ImportSection
572
2.32k
  compile(Module.getImportSection());
573
  // Compile GlobalSection
574
2.32k
  compile(Module.getGlobalSection());
575
  // Compile MemorySection (MemorySec, DataSec)
576
2.32k
  compile(Module.getMemorySection(), Module.getDataSection());
577
  // Compile TableSection (TableSec, ElemSec)
578
2.32k
  compile(Module.getTableSection(), Module.getElementSection());
579
  // Compile TagSection
580
2.32k
  compile(Module.getTagSection());
581
  // Create function declarations without compiling bodies. (FunctionSec,
582
  // CodeSec)
583
2.32k
  compileFunctionDeclarations(Module.getFunctionSection(),
584
2.32k
                              Module.getCodeSection());
585
2.32k
}
586
587
void Compiler::compileFunctionDeclarations(
588
    const AST::FunctionSection &FunctionSec,
589
2.32k
    const AST::CodeSection &CodeSec) noexcept {
590
2.32k
  const auto &TypeIdxs = FunctionSec.getContent();
591
2.32k
  const auto &CodeSegs = CodeSec.getContent();
592
2.32k
  assuming(TypeIdxs.size() == CodeSegs.size());
593
594
13.4k
  for (size_t I = 0; I < CodeSegs.size(); ++I) {
595
11.1k
    const auto &TypeIdx = TypeIdxs[I];
596
11.1k
    const auto &Code = CodeSegs[I];
597
11.1k
    assuming(TypeIdx < Context->CompositeTypes.size());
598
11.1k
    assuming(Context->CompositeTypes[TypeIdx]->isFunc());
599
11.1k
    const auto &FuncType = Context->CompositeTypes[TypeIdx]->getFuncType();
600
11.1k
    const auto FuncID = Context->Functions.size();
601
11.1k
    auto FTy = toLLVMType(Context->LLContext, Context->ModCtxPtrTy,
602
11.1k
                          Context->ExecCtxPtrTy, FuncType);
603
11.1k
    LLVM::FunctionCallee F = {FTy, Context->LLModule.get().addFunction(
604
11.1k
                                       FTy, LLVMExternalLinkage,
605
11.1k
                                       fmt::format("f{}"sv, FuncID).c_str())};
606
11.1k
    F.Fn.setVisibility(LLVMProtectedVisibility);
607
11.1k
    F.Fn.setDSOLocal(true);
608
11.1k
    F.Fn.setDLLStorageClass(LLVMDLLExportStorageClass);
609
11.1k
    F.Fn.addFnAttr(Context->NoStackArgProbe);
610
11.1k
    F.Fn.addFnAttr(Context->StrictFP);
611
11.1k
    F.Fn.addFnAttr(Context->UWTable);
612
11.1k
    F.Fn.addParamAttr(0, Context->ReadOnly);
613
11.1k
    F.Fn.addParamAttr(0, Context->NoAlias);
614
11.1k
    F.Fn.addParamAttr(1, Context->ReadOnly);
615
11.1k
    F.Fn.addParamAttr(1, Context->NoAlias);
616
617
11.1k
    Context->Functions.emplace_back(TypeIdx, F, &Code);
618
11.1k
  }
619
2.32k
}
620
621
11.1k
Expect<void> Compiler::compileFunctionBody(uint32_t LocalFuncIndex) noexcept {
622
  // Find the function in the Functions list
623
  // LocalFuncIndex is relative to the defined functions (not imports)
624
11.1k
  uint32_t GlobalFuncIndex = Context->ImportCount + LocalFuncIndex;
625
11.1k
  if (GlobalFuncIndex >= Context->Functions.size()) {
626
0
    spdlog::error("[lazy-jit]: function index {} out of range"sv,
627
0
                  LocalFuncIndex);
628
0
    return Unexpect(ErrCode::Value::IllegalPath);
629
0
  }
630
631
11.1k
  auto &[T, F, Code] = Context->Functions[GlobalFuncIndex];
632
11.1k
  if (!Code) {
633
0
    spdlog::error("[lazy-jit]: cannot compile import function {}"sv,
634
0
                  LocalFuncIndex);
635
0
    return Unexpect(ErrCode::Value::IllegalPath);
636
0
  }
637
638
  // Check if already compiled (function has basic blocks)
639
11.1k
  if (F.Fn.countBasicBlocks() > 0) {
640
0
    spdlog::debug("[lazy-jit]: function {} already compiled"sv, LocalFuncIndex);
641
0
    return {};
642
0
  }
643
644
11.1k
  spdlog::debug("[lazy-jit]: compiling function {}"sv, LocalFuncIndex);
645
646
11.1k
  std::vector<ValType> Locals;
647
11.1k
  for (const auto &Local : Code->getLocals()) {
648
1.88M
    for (unsigned I = 0; I < Local.first; ++I) {
649
1.87M
      Locals.push_back(Local.second);
650
1.87M
    }
651
1.94k
  }
652
653
11.1k
  auto Type = Context->resolveBlockType(T);
654
  // A small leaf cannot recurse and its frame fits in the stack margin kept
655
  // below the limit, so it skips the stack limit check.
656
11.1k
  const auto Instrs = Code->getExpr().getInstrs();
657
11.1k
  const bool IsSmallLeaf = Type.first.size() + Locals.size() <= 64 &&
658
10.7k
                           Instrs.size() <= 256 &&
659
10.4k
                           std::none_of(Instrs.begin(), Instrs.end(),
660
108k
                                        [](const AST::Instruction &Instr) {
661
108k
                                          switch (Instr.getOpCode()) {
662
1.28k
                                          case OpCode::Call:
663
2.01k
                                          case OpCode::Call_indirect:
664
2.02k
                                          case OpCode::Call_ref:
665
2.06k
                                          case OpCode::Return_call:
666
2.16k
                                          case OpCode::Return_call_indirect:
667
2.16k
                                          case OpCode::Return_call_ref:
668
2.16k
                                            return true;
669
106k
                                          default:
670
106k
                                            return false;
671
108k
                                          }
672
108k
                                        });
673
11.1k
  FunctionCompiler FC(
674
11.1k
      *Context, F, Locals, Conf.getCompilerConfigure().isInterruptible(),
675
11.1k
      Conf.getStatisticsConfigure().isInstructionCounting(),
676
11.1k
      Conf.getStatisticsConfigure().isCostMeasuring(),
677
11.1k
      Conf.getRuntimeConfigure().getRunMode() == RunMode::LazyJIT,
678
11.1k
      !IsSmallLeaf);
679
11.1k
  EXPECTED_TRY(FC.compile(*Code, std::move(Type)));
680
11.1k
  F.Fn.eliminateUnreachableBlocks();
681
682
11.1k
  return {};
683
11.1k
}
684
685
Expect<LLVM::Data>
686
0
LLVM::Compiler::compileInfrastructure(const AST::Module &Module) noexcept {
687
  // Check the module is validated.
688
0
  if (unlikely(!Module.getIsValidated())) {
689
0
    spdlog::error(ErrCode::Value::NotValidated);
690
0
    return Unexpect(ErrCode::Value::NotValidated);
691
0
  }
692
693
0
  std::unique_lock Lock(Mutex);
694
0
  spdlog::info("[lazy-jit]: compile infrastructure start"sv);
695
696
0
  Data D;
697
0
  auto LLContext = initLLVMModule(D);
698
0
  auto &LLModule = D.extract().LLModule;
699
700
0
  CompileContext NewContext(LLContext, LLModule,
701
0
                            Conf.getCompilerConfigure().isGenericBinary());
702
0
  RAIICleanup Cleanup(Context, &NewContext);
703
0
  Context->addVersionGlobal();
704
705
  // Compile all sections and the function declarations without bodies.
706
0
  compileSections(Module, false);
707
  // Compile ExportSection
708
0
  compile(Module.getExportSection());
709
710
  // Set initializer for constant value
711
0
  Context->finalizeIntrinsicsTable();
712
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
713
0
    spdlog::error("LLVM module verification failed: {}"sv,
714
0
                  VerifyMsg.string_view());
715
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
716
0
  }
717
718
0
  spdlog::info("[lazy-jit]: infrastructure compilation done"sv);
719
720
0
  return Expect<Data>{std::move(D)};
721
0
}
722
723
Expect<LLVM::Data>
724
Compiler::compileFunctions(Data &&LLData, const AST::Module &Module,
725
0
                           Span<const uint32_t> LocalFuncIndices) noexcept {
726
0
  if (unlikely(!Module.getIsValidated())) {
727
0
    spdlog::error(ErrCode::Value::NotValidated);
728
0
    return Unexpect(ErrCode::Value::NotValidated);
729
0
  }
730
0
  if (unlikely(LocalFuncIndices.empty())) {
731
0
    spdlog::error("[lazy-jit]: compileFunctions with empty index list"sv);
732
0
    return Unexpect(ErrCode::Value::IllegalPath);
733
0
  }
734
735
0
  std::unique_lock Lock(Mutex);
736
0
  std::vector<uint32_t> Sorted(LocalFuncIndices.begin(),
737
0
                               LocalFuncIndices.end());
738
0
  std::sort(Sorted.begin(), Sorted.end());
739
0
  Sorted.erase(std::unique(Sorted.begin(), Sorted.end()), Sorted.end());
740
741
0
  spdlog::debug("[lazy-jit]: compile functions batch ({}) start"sv,
742
0
                Sorted.size());
743
744
  // Each batch starts from a fresh module sharing the same thread-safe
745
  // context: on success the previous batch module was consumed by the JIT,
746
  // and after a failed batch the leftover module must be discarded so its
747
  // declarations are not re-added on top of themselves.
748
0
  LLData.extract().resetModule();
749
0
  auto LLContext = initLLVMModule(LLData);
750
0
  auto &LLModule = LLData.extract().LLModule;
751
752
0
  CompileContext NewContext(LLContext, LLModule,
753
0
                            Conf.getCompilerConfigure().isGenericBinary());
754
0
  RAIICleanup Cleanup(Context, &NewContext);
755
756
  // Emit the type wrappers as external declarations resolved against the
757
  // infrastructure module, then declare the functions and compile the
758
  // requested bodies.
759
0
  compileSections(Module, true);
760
761
0
  for (uint32_t FuncIndex : Sorted) {
762
0
    EXPECTED_TRY(compileFunctionBody(FuncIndex));
763
0
  }
764
765
0
  spdlog::info("[lazy-jit]: verify batch ({} funcs) start"sv, Sorted.size());
766
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
767
0
    spdlog::error("[lazy-jit]: batch verification failed: {}"sv,
768
0
                  VerifyMsg.string_view());
769
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
770
0
  }
771
0
  spdlog::info("[lazy-jit]: verify batch ({} funcs) done"sv, Sorted.size());
772
773
0
  auto &TM = LLData.extract().TM;
774
0
  EXPECTED_TRY(optimize(LLModule, TM));
775
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
776
0
    spdlog::error("LLVM module verification failed after optimization: {}"sv,
777
0
                  VerifyMsg.string_view());
778
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
779
0
  }
780
781
0
  spdlog::debug("[lazy-jit]: compile functions batch ({}) done"sv,
782
0
                Sorted.size());
783
0
  return Expect<Data>{std::move(LLData)};
784
0
}
785
786
} // namespace LLVM
787
} // namespace WasmEdge