Coverage Report

Created: 2026-09-28 06:22

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.31k
      : Context(ContextRef) {
26
2.31k
    Context = NewContext;
27
2.31k
  }
28
2.31k
  ~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.04k
toLLVMLevel(WasmEdge::CompilerConfigure::OptimizationLevel Level) noexcept {
63
2.04k
  using OL = WasmEdge::CompilerConfigure::OptimizationLevel;
64
2.04k
  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.04k
  case OL::O3:
72
2.04k
    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.04k
  }
80
2.04k
}
81
#endif
82
83
static inline LLVMCodeGenOptLevel toLLVMCodeGenLevel(
84
2.04k
    WasmEdge::CompilerConfigure::OptimizationLevel Level) noexcept {
85
2.04k
  using OL = WasmEdge::CompilerConfigure::OptimizationLevel;
86
2.04k
  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.04k
  case OL::O3:
94
2.04k
    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.04k
  }
102
2.04k
}
103
} // namespace
104
105
namespace WasmEdge {
106
namespace LLVM {
107
108
2.31k
Expect<void> Compiler::checkConfigure() noexcept {
109
2.31k
  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.31k
  return {};
116
2.31k
}
117
118
Expect<void> Compiler::optimize(LLVM::Module &LLModule,
119
2.04k
                                LLVM::TargetMachine &TM) noexcept {
120
2.04k
  spdlog::info("optimize start"sv);
121
2.04k
  auto Triple = LLModule.getTarget();
122
2.04k
  auto [TheTarget, ErrorMessage] = LLVM::Target::getFromTriple(Triple);
123
2.04k
  if (ErrorMessage) {
124
0
    spdlog::error("getFromTriple failed:{}"sv, ErrorMessage.string_view());
125
0
    return Unexpect(ErrCode::Value::IllegalPath);
126
0
  }
127
128
2.04k
  std::string CPUName;
129
#if defined(__riscv) && __riscv_xlen == 64
130
  CPUName = "generic-rv64"s;
131
#else
132
2.04k
  if (!Conf.getCompilerConfigure().isGenericBinary()) {
133
2.04k
    CPUName = LLVM::getHostCPUName().string_view();
134
2.04k
  } else {
135
0
    CPUName = "generic"s;
136
0
  }
137
2.04k
#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.04k
  TM = LLVM::TargetMachine::create(
143
2.04k
      TheTarget, Triple, CPUName.c_str(),
144
#if defined(__riscv) && __riscv_xlen == 64
145
      "",
146
#else
147
2.04k
      LLVM::getHostCPUFeatures().unwrap(),
148
2.04k
#endif
149
2.04k
      toLLVMCodeGenLevel(Conf.getCompilerConfigure().getOptimizationLevel()),
150
2.04k
      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.04k
  auto FP = LLVM::PassManager::createForModule(LLModule);
185
2.04k
  auto MP = LLVM::PassManager::create();
186
187
2.04k
  TM.addAnalysisPasses(MP);
188
2.04k
  TM.addAnalysisPasses(FP);
189
2.04k
  {
190
2.04k
    auto PMB = LLVM::PassManagerBuilder::create();
191
2.04k
    auto [OptLevel, SizeLevel] =
192
2.04k
        toLLVMLevel(Conf.getCompilerConfigure().getOptimizationLevel());
193
2.04k
    PMB.setOptLevel(OptLevel);
194
2.04k
    PMB.setSizeLevel(SizeLevel);
195
2.04k
    PMB.populateFunctionPassManager(FP);
196
2.04k
    PMB.populateModulePassManager(MP);
197
2.04k
  }
198
2.04k
  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.04k
  default:
204
2.04k
    break;
205
2.04k
  }
206
207
2.04k
  FP.initializeFunctionPassManager();
208
21.2k
  for (auto Fn = LLModule.getFirstFunction(); Fn; Fn = Fn.getNextFunction()) {
209
19.2k
    FP.runFunctionPassManager(Fn);
210
19.2k
  }
211
2.04k
  FP.finalizeFunctionPassManager();
212
2.04k
  MP.runPassManager(LLModule);
213
2.04k
#endif
214
215
2.04k
  spdlog::info("optimize done"sv);
216
2.04k
  return {};
217
2.04k
}
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.31k
static LLVM::Context initLLVMModule(LLVM::Data &D) noexcept {
222
2.31k
  auto LLContext = D.extract().getLLContext();
223
2.31k
  LLVM::Core::init(LLContext.unwrap());
224
2.31k
  auto &LLModule = D.extract().LLModule;
225
2.31k
  LLModule.setTarget(LLVM::getDefaultTargetTriple().unwrap());
226
2.31k
  LLModule.addFlag(LLVMModuleFlagBehaviorError, "PIC Level"sv, 2);
227
2.31k
  return LLContext;
228
2.31k
}
229
230
2.31k
Expect<Data> Compiler::compile(const AST::Module &Module) noexcept {
231
  // Check that the module is validated.
232
2.31k
  if (unlikely(!Module.getIsValidated())) {
233
0
    spdlog::error(ErrCode::Value::NotValidated);
234
0
    return Unexpect(ErrCode::Value::NotValidated);
235
0
  }
236
237
2.31k
  std::unique_lock Lock(Mutex);
238
2.31k
  spdlog::info("compile start"sv);
239
240
2.31k
  LLVM::Data D;
241
2.31k
  auto LLContext = initLLVMModule(D);
242
2.31k
  auto &LLModule = D.extract().LLModule;
243
244
2.31k
  CompileContext NewContext(LLContext, LLModule,
245
2.31k
                            Conf.getCompilerConfigure().isGenericBinary());
246
2.31k
  RAIICleanup Cleanup(Context, &NewContext);
247
2.31k
  Context->addVersionGlobal();
248
249
  // Compile all sections and the function declarations.
250
2.31k
  compileSections(Module, false);
251
  // Compile all function bodies.
252
2.31k
  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.31k
  compile(Module.getExportSection());
258
  // StartSection is not required for compilation.
259
260
2.31k
  spdlog::info("verify start"sv);
261
2.31k
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
262
270
    spdlog::error("LLVM module verification failed: {}"sv,
263
270
                  VerifyMsg.string_view());
264
270
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
265
270
  }
266
267
2.04k
  auto &TM = D.extract().TM;
268
2.04k
  EXPECTED_TRY(optimize(LLModule, TM));
269
2.04k
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
270
0
    spdlog::error("LLVM module verification failed after optimization: {}"sv,
271
0
                  VerifyMsg.string_view());
272
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
273
0
  }
274
275
  // Set initializer for constant value
276
2.04k
  Context->finalizeIntrinsicsTable();
277
2.04k
  return Expect<Data>{std::move(D)};
278
2.04k
}
279
280
void Compiler::compile(const AST::TypeSection &TypeSec,
281
2.31k
                       bool DeclarationsOnly) noexcept {
282
2.31k
  auto WrapperTy = LLVM::Type::getFunctionType(
283
2.31k
      Context->VoidTy,
284
2.31k
      {Context->ModCtxPtrTy, Context->ExecCtxPtrTy, Context->Int8PtrTy,
285
2.31k
       Context->Int8PtrTy, Context->Int8PtrTy},
286
2.31k
      false);
287
2.31k
  auto SubTypes = TypeSec.getContent();
288
2.31k
  const auto Size = SubTypes.size();
289
2.31k
  if (Size == 0) {
290
132
    return;
291
132
  }
292
2.18k
  Context->CompositeTypes.reserve(Size);
293
2.18k
  Context->FunctionWrappers.reserve(Size);
294
295
4.79k
  auto SetFuncAttributes = [&](auto FDecl) {
296
4.79k
    FDecl.setVisibility(LLVMProtectedVisibility);
297
4.79k
    FDecl.setDSOLocal(true);
298
4.79k
    FDecl.setDLLStorageClass(LLVMDLLExportStorageClass);
299
4.79k
    FDecl.addFnAttr(Context->NoStackArgProbe);
300
4.79k
    FDecl.addFnAttr(Context->StrictFP);
301
4.79k
    FDecl.addFnAttr(Context->UWTable);
302
4.79k
    FDecl.addParamAttr(0, Context->ReadOnly);
303
4.79k
    FDecl.addParamAttr(0, Context->NoAlias);
304
4.79k
    FDecl.addParamAttr(1, Context->ReadOnly);
305
4.79k
    FDecl.addParamAttr(1, Context->NoAlias);
306
4.79k
    FDecl.addParamAttr(2, Context->NoAlias);
307
4.79k
    FDecl.addParamAttr(3, Context->NoAlias);
308
4.79k
    FDecl.addParamAttr(4, Context->NoAlias);
309
4.79k
  };
310
311
  // Iterate and compile types.
312
7.18k
  for (size_t I = 0; I < Size; ++I) {
313
5.00k
    const auto &CompType = SubTypes[I].getCompositeType();
314
5.00k
    const auto Name = fmt::format("t{}"sv, Context->CompositeTypes.size());
315
5.00k
    if (CompType.isFunc()) {
316
      // Check that the function type is unique.
317
4.83k
      {
318
4.83k
        bool Unique = true;
319
20.3k
        for (size_t J = 0; J < I; ++J) {
320
15.7k
          if (Context->CompositeTypes[J] &&
321
15.7k
              Context->CompositeTypes[J]->isFunc()) {
322
15.4k
            const auto &OldFuncType = Context->CompositeTypes[J]->getFuncType();
323
15.4k
            if (OldFuncType == CompType.getFuncType()) {
324
213
              Unique = false;
325
213
              Context->CompositeTypes.push_back(Context->CompositeTypes[J]);
326
213
              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
213
              } else {
332
213
                auto F = Context->FunctionWrappers[J];
333
213
                Context->FunctionWrappers.push_back(F);
334
213
                auto A = Context->LLModule.get().addAlias(WrapperTy, F,
335
213
                                                          Name.c_str());
336
213
                A.setLinkage(LLVMExternalLinkage);
337
213
                A.setVisibility(LLVMProtectedVisibility);
338
213
                A.setDSOLocal(true);
339
213
                A.setDLLStorageClass(LLVMDLLExportStorageClass);
340
213
              }
341
213
              break;
342
213
            }
343
15.4k
          }
344
15.7k
        }
345
4.83k
        if (!Unique) {
346
213
          continue;
347
213
        }
348
4.83k
      }
349
350
      // Create Wrapper
351
4.61k
      auto F = Context->LLModule.get().addFunction(
352
4.61k
          WrapperTy, LLVMExternalLinkage, Name.c_str());
353
4.61k
      {
354
4.61k
        SetFuncAttributes(F);
355
356
4.61k
        if (!DeclarationsOnly) {
357
4.61k
          LLVM::Builder Builder(Context->LLContext);
358
4.61k
          Builder.positionAtEnd(
359
4.61k
              LLVM::BasicBlock::create(Context->LLContext, F, "entry"));
360
361
4.61k
          auto FTy = toLLVMType(Context->LLContext, Context->ModCtxPtrTy,
362
4.61k
                                Context->ExecCtxPtrTy, CompType.getFuncType());
363
4.61k
          auto RTy = FTy.getReturnType();
364
4.61k
          std::vector<LLVM::Type> FPTy(FTy.getNumParams());
365
4.61k
          FTy.getParamTypes(FPTy);
366
367
4.61k
          const size_t ArgCount = FPTy.size() - 2;
368
4.61k
          auto ModCtxPtr = F.getFirstParam();
369
4.61k
          auto ExecCtxPtr = ModCtxPtr.getNextParam();
370
4.61k
          auto RawFunc = LLVM::FunctionCallee{
371
4.61k
              FTy, Builder.createBitCast(ExecCtxPtr.getNextParam(),
372
4.61k
                                         FTy.getPointerTo())};
373
4.61k
          auto RawArgs = ExecCtxPtr.getNextParam().getNextParam();
374
4.61k
          auto RawRets = RawArgs.getNextParam();
375
376
4.61k
          std::vector<LLVM::Value> Args;
377
4.61k
          Args.reserve(FTy.getNumParams());
378
4.61k
          Args.push_back(ModCtxPtr);
379
4.61k
          Args.push_back(ExecCtxPtr);
380
9.76k
          for (size_t J = 0; J < ArgCount; ++J) {
381
5.14k
            Args.push_back(Builder.createValuePtrLoad(
382
5.14k
                FPTy[J + 2], RawArgs, Context->Int8Ty, J * LLVM::kValSize));
383
5.14k
          }
384
385
4.61k
          auto Ret = Builder.createCall(RawFunc, Args);
386
4.61k
          if (RTy.isVoidTy()) {
387
            // nothing to do
388
2.99k
          } else if (RTy.isStructTy()) {
389
374
            auto Rets = unpackStruct(Builder, Ret);
390
374
            Builder.createArrayPtrStore(Rets, RawRets, Context->Int8Ty,
391
374
                                        LLVM::kValSize);
392
2.61k
          } else {
393
2.61k
            Builder.createValuePtrStore(Ret, RawRets, Context->Int8Ty);
394
2.61k
          }
395
4.61k
          Builder.createRetVoid();
396
4.61k
        }
397
4.61k
      }
398
      // Copy wrapper, param and return lists to module instance.
399
4.61k
      Context->FunctionWrappers.push_back(F);
400
4.61k
    } else {
401
      // Non function type case. Create empty wrapper.
402
172
      auto F = Context->LLModule.get().addFunction(
403
172
          WrapperTy, LLVMExternalLinkage, Name.c_str());
404
172
      {
405
172
        SetFuncAttributes(F);
406
407
172
        if (!DeclarationsOnly) {
408
172
          LLVM::Builder Builder(Context->LLContext);
409
172
          Builder.positionAtEnd(
410
172
              LLVM::BasicBlock::create(Context->LLContext, F, "entry"));
411
172
          Builder.createRetVoid();
412
172
        }
413
172
      }
414
172
      Context->FunctionWrappers.push_back(F);
415
172
    }
416
4.79k
    Context->CompositeTypes.push_back(&CompType);
417
4.79k
  }
418
2.18k
}
419
420
2.31k
void Compiler::compile(const AST::ImportSection &ImportSec) noexcept {
421
  // Iterate and compile import descriptions.
422
2.31k
  for (const auto &ImpDesc : ImportSec.getContent()) {
423
    // Get data from import description.
424
557
    const auto &ExtType = ImpDesc.getExternalType();
425
426
    // Add the imports to the module instance.
427
557
    switch (ExtType) {
428
380
    case ExternalType::Function: // Function type index
429
380
    {
430
380
      const auto FuncID = static_cast<uint32_t>(Context->Functions.size());
431
      // Get the function type index in module.
432
380
      uint32_t TypeIdx = ImpDesc.getExternalFuncTypeIdx();
433
380
      assuming(TypeIdx < Context->CompositeTypes.size());
434
380
      assuming(Context->CompositeTypes[TypeIdx]->isFunc());
435
380
      const auto &FuncType = Context->CompositeTypes[TypeIdx]->getFuncType();
436
380
      auto FTy = toLLVMType(Context->LLContext, Context->ModCtxPtrTy,
437
380
                            Context->ExecCtxPtrTy, FuncType);
438
380
      auto RTy = FTy.getReturnType();
439
380
      auto F =
440
380
          LLVM::FunctionCallee{FTy, Context->LLModule.get().addFunction(
441
380
                                        FTy, LLVMInternalLinkage,
442
380
                                        fmt::format("f{}"sv, FuncID).c_str())};
443
380
      F.Fn.setDSOLocal(true);
444
380
      F.Fn.addFnAttr(Context->NoStackArgProbe);
445
380
      F.Fn.addFnAttr(Context->StrictFP);
446
380
      F.Fn.addFnAttr(Context->UWTable);
447
380
      F.Fn.addParamAttr(0, Context->ReadOnly);
448
380
      F.Fn.addParamAttr(0, Context->NoAlias);
449
450
380
      LLVM::Builder Builder(Context->LLContext);
451
380
      Builder.positionAtEnd(
452
380
          LLVM::BasicBlock::create(Context->LLContext, F.Fn, "entry"));
453
454
380
      const auto ArgSize = FuncType.getParamTypes().size();
455
380
      const auto RetSize =
456
380
          RTy.isVoidTy() ? 0 : FuncType.getReturnTypes().size();
457
458
380
      LLVM::Value Args = Builder.createArray(ArgSize, LLVM::kValSize);
459
380
      LLVM::Value Rets = Builder.createArray(RetSize, LLVM::kValSize);
460
461
380
      auto ModCtx = Builder.createLoad(Context->ModCtxTy, F.Fn.getFirstParam());
462
380
      auto Arg = F.Fn.getFirstParam().getNextParam();
463
636
      for (unsigned I = 0; I < ArgSize; ++I) {
464
256
        Arg = Arg.getNextParam();
465
256
        Builder.createValuePtrStore(Arg, Args, Context->Int8Ty,
466
256
                                    I * LLVM::kValSize);
467
256
      }
468
469
380
      Builder.createCall(
470
380
          Context->getIntrinsic(Builder, Executable::Intrinsics::kCall,
471
380
                                LLVM::Type::getFunctionType(
472
380
                                    Context->VoidTy,
473
380
                                    {Context->Int8PtrTy, Context->Int32Ty,
474
380
                                     Context->Int8PtrTy, Context->Int8PtrTy},
475
380
                                    false)),
476
380
          {Context->getModuleInst(Builder, ModCtx),
477
380
           Context->LLContext.getInt32(FuncID), Args, Rets});
478
479
380
      if (RetSize == 0) {
480
251
        Builder.createRetVoid();
481
251
      } else if (RetSize == 1) {
482
95
        Builder.createRet(
483
95
            Builder.createValuePtrLoad(RTy, Rets, Context->Int8Ty));
484
95
      } else {
485
34
        Builder.createAggregateRet(Builder.createArrayPtrLoad(
486
34
            RetSize, RTy, Rets, Context->Int8Ty, LLVM::kValSize));
487
34
      }
488
489
380
      Context->Functions.emplace_back(TypeIdx, F, nullptr);
490
380
      Context->ImportCount++;
491
380
      break;
492
380
    }
493
59
    case ExternalType::Table: // Table type
494
59
    {
495
      // Get table address type. External type checked in validation.
496
59
      const auto &TabType = ImpDesc.getExternalTableType();
497
59
      const auto AddrType = TabType.getLimit().getAddrType();
498
59
      auto Type = toLLVMType(Context->LLContext, AddrType);
499
59
      Context->TableAddrTypes.push_back(Type);
500
59
      break;
501
380
    }
502
46
    case ExternalType::Memory: // Memory type
503
46
    {
504
      // Get memory address type. External type checked in validation.
505
46
      const auto &MemType = ImpDesc.getExternalMemoryType();
506
46
      const auto AddrType = MemType.getLimit().getAddrType();
507
46
      auto Type = toLLVMType(Context->LLContext, AddrType);
508
46
      Context->MemoryAddrTypes.push_back(Type);
509
46
      break;
510
380
    }
511
39
    case ExternalType::Global: // Global type
512
39
    {
513
      // Get global type. External type checked in validation.
514
39
      const auto &GlobType = ImpDesc.getExternalGlobalType();
515
39
      const auto &ValType = GlobType.getValType();
516
39
      auto Type = toLLVMType(Context->LLContext, ValType);
517
39
      Context->Globals.push_back(Type);
518
39
      break;
519
380
    }
520
33
    case ExternalType::Tag: // Tag type
521
33
    {
522
      // Get the tag type index. External type checked in validation.
523
33
      const auto &TgType = ImpDesc.getExternalTagType();
524
33
      Context->Tags.push_back(TgType.getTypeIdx());
525
33
      break;
526
380
    }
527
0
    default:
528
0
      assumingUnreachable();
529
557
    }
530
557
  }
531
2.31k
}
532
533
2.31k
void Compiler::compile(const AST::ExportSection &) noexcept {}
534
535
2.31k
void Compiler::compile(const AST::GlobalSection &GlobalSec) noexcept {
536
2.31k
  for (const auto &GlobalSeg : GlobalSec.getContent()) {
537
166
    const auto &ValType = GlobalSeg.getGlobalType().getValType();
538
166
    auto Type = toLLVMType(Context->LLContext, ValType);
539
166
    Context->Globals.push_back(Type);
540
166
  }
541
2.31k
}
542
543
void Compiler::compile(const AST::MemorySection &MemorySec,
544
2.31k
                       const AST::DataSection &) noexcept {
545
2.31k
  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.31k
}
551
552
void Compiler::compile(const AST::TableSection &TableSec,
553
2.31k
                       const AST::ElementSection &) noexcept {
554
2.31k
  for (const auto &TableSeg : TableSec.getContent()) {
555
289
    const auto AddrType = TableSeg.getTableType().getLimit().getAddrType();
556
289
    auto Type = toLLVMType(Context->LLContext, AddrType);
557
289
    Context->TableAddrTypes.push_back(Type);
558
289
  }
559
2.31k
}
560
561
2.31k
void Compiler::compile(const AST::TagSection &TagSec) noexcept {
562
2.31k
  for (const auto &TgType : TagSec.getContent()) {
563
41
    Context->Tags.push_back(TgType.getTypeIdx());
564
41
  }
565
2.31k
}
566
567
void Compiler::compileSections(const AST::Module &Module,
568
2.31k
                               bool DeclarationsOnly) noexcept {
569
  // Compile Function Types
570
2.31k
  compile(Module.getTypeSection(), DeclarationsOnly);
571
  // Compile ImportSection
572
2.31k
  compile(Module.getImportSection());
573
  // Compile GlobalSection
574
2.31k
  compile(Module.getGlobalSection());
575
  // Compile MemorySection (MemorySec, DataSec)
576
2.31k
  compile(Module.getMemorySection(), Module.getDataSection());
577
  // Compile TableSection (TableSec, ElemSec)
578
2.31k
  compile(Module.getTableSection(), Module.getElementSection());
579
  // Compile TagSection
580
2.31k
  compile(Module.getTagSection());
581
  // Create function declarations without compiling bodies. (FunctionSec,
582
  // CodeSec)
583
2.31k
  compileFunctionDeclarations(Module.getFunctionSection(),
584
2.31k
                              Module.getCodeSection());
585
2.31k
}
586
587
void Compiler::compileFunctionDeclarations(
588
    const AST::FunctionSection &FunctionSec,
589
2.31k
    const AST::CodeSection &CodeSec) noexcept {
590
2.31k
  const auto &TypeIdxs = FunctionSec.getContent();
591
2.31k
  const auto &CodeSegs = CodeSec.getContent();
592
2.31k
  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.31k
}
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.69M
    for (unsigned I = 0; I < Local.first; ++I) {
649
1.69M
      Locals.push_back(Local.second);
650
1.69M
    }
651
2.05k
  }
652
653
11.1k
  FunctionCompiler FC(
654
11.1k
      *Context, F, Locals, Conf.getCompilerConfigure().isInterruptible(),
655
11.1k
      Conf.getStatisticsConfigure().isInstructionCounting(),
656
11.1k
      Conf.getStatisticsConfigure().isCostMeasuring(),
657
11.1k
      Conf.getRuntimeConfigure().getRunMode() == RunMode::LazyJIT);
658
11.1k
  auto Type = Context->resolveBlockType(T);
659
11.1k
  EXPECTED_TRY(FC.compile(*Code, std::move(Type)));
660
11.1k
  F.Fn.eliminateUnreachableBlocks();
661
662
11.1k
  return {};
663
11.1k
}
664
665
Expect<LLVM::Data>
666
0
LLVM::Compiler::compileInfrastructure(const AST::Module &Module) noexcept {
667
  // Check the module is validated.
668
0
  if (unlikely(!Module.getIsValidated())) {
669
0
    spdlog::error(ErrCode::Value::NotValidated);
670
0
    return Unexpect(ErrCode::Value::NotValidated);
671
0
  }
672
673
0
  std::unique_lock Lock(Mutex);
674
0
  spdlog::info("[lazy-jit]: compile infrastructure start"sv);
675
676
0
  Data D;
677
0
  auto LLContext = initLLVMModule(D);
678
0
  auto &LLModule = D.extract().LLModule;
679
680
0
  CompileContext NewContext(LLContext, LLModule,
681
0
                            Conf.getCompilerConfigure().isGenericBinary());
682
0
  RAIICleanup Cleanup(Context, &NewContext);
683
0
  Context->addVersionGlobal();
684
685
  // Compile all sections and the function declarations without bodies.
686
0
  compileSections(Module, false);
687
  // Compile ExportSection
688
0
  compile(Module.getExportSection());
689
690
  // Set initializer for constant value
691
0
  Context->finalizeIntrinsicsTable();
692
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
693
0
    spdlog::error("LLVM module verification failed: {}"sv,
694
0
                  VerifyMsg.string_view());
695
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
696
0
  }
697
698
0
  spdlog::info("[lazy-jit]: infrastructure compilation done"sv);
699
700
0
  return Expect<Data>{std::move(D)};
701
0
}
702
703
Expect<LLVM::Data>
704
Compiler::compileFunctions(Data &&LLData, const AST::Module &Module,
705
0
                           Span<const uint32_t> LocalFuncIndices) noexcept {
706
0
  if (unlikely(!Module.getIsValidated())) {
707
0
    spdlog::error(ErrCode::Value::NotValidated);
708
0
    return Unexpect(ErrCode::Value::NotValidated);
709
0
  }
710
0
  if (unlikely(LocalFuncIndices.empty())) {
711
0
    spdlog::error("[lazy-jit]: compileFunctions with empty index list"sv);
712
0
    return Unexpect(ErrCode::Value::IllegalPath);
713
0
  }
714
715
0
  std::unique_lock Lock(Mutex);
716
0
  std::vector<uint32_t> Sorted(LocalFuncIndices.begin(),
717
0
                               LocalFuncIndices.end());
718
0
  std::sort(Sorted.begin(), Sorted.end());
719
0
  Sorted.erase(std::unique(Sorted.begin(), Sorted.end()), Sorted.end());
720
721
0
  spdlog::debug("[lazy-jit]: compile functions batch ({}) start"sv,
722
0
                Sorted.size());
723
724
  // Each batch starts from a fresh module sharing the same thread-safe
725
  // context: on success the previous batch module was consumed by the JIT,
726
  // and after a failed batch the leftover module must be discarded so its
727
  // declarations are not re-added on top of themselves.
728
0
  LLData.extract().resetModule();
729
0
  auto LLContext = initLLVMModule(LLData);
730
0
  auto &LLModule = LLData.extract().LLModule;
731
732
0
  CompileContext NewContext(LLContext, LLModule,
733
0
                            Conf.getCompilerConfigure().isGenericBinary());
734
0
  RAIICleanup Cleanup(Context, &NewContext);
735
736
  // Emit the type wrappers as external declarations resolved against the
737
  // infrastructure module, then declare the functions and compile the
738
  // requested bodies.
739
0
  compileSections(Module, true);
740
741
0
  for (uint32_t FuncIndex : Sorted) {
742
0
    EXPECTED_TRY(compileFunctionBody(FuncIndex));
743
0
  }
744
745
0
  spdlog::info("[lazy-jit]: verify batch ({} funcs) start"sv, Sorted.size());
746
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
747
0
    spdlog::error("[lazy-jit]: batch verification failed: {}"sv,
748
0
                  VerifyMsg.string_view());
749
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
750
0
  }
751
0
  spdlog::info("[lazy-jit]: verify batch ({} funcs) done"sv, Sorted.size());
752
753
0
  auto &TM = LLData.extract().TM;
754
0
  EXPECTED_TRY(optimize(LLModule, TM));
755
0
  if (LLVM::Message VerifyMsg; LLModule.hasVerificationError(VerifyMsg)) {
756
0
    spdlog::error("LLVM module verification failed after optimization: {}"sv,
757
0
                  VerifyMsg.string_view());
758
0
    return Unexpect(ErrCode::Value::InvalidAOTConfigure);
759
0
  }
760
761
0
  spdlog::debug("[lazy-jit]: compile functions batch ({}) done"sv,
762
0
                Sorted.size());
763
0
  return Expect<Data>{std::move(LLData)};
764
0
}
765
766
} // namespace LLVM
767
} // namespace WasmEdge