Coverage Report

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