Skip to content

Commit

Permalink
Introduce new ThreadLocalLowering pass
Browse files Browse the repository at this point in the history
This pass will make it possible to use wasm threadlocals in genericjs, by creating and calling a function in wasm that will calculate the address in memory of the threadlocal.
  • Loading branch information
Maqrkk committed Jan 14, 2025
1 parent e8d9684 commit 958511a
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions llvm/include/llvm/Cheerp/PassRegistry.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "llvm/Cheerp/CallConstructors.h"
#include "llvm/Cheerp/CommandLine.h"
#include "llvm/Cheerp/CheerpLowerAtomic.h"
#include "llvm/Cheerp/ThreadLocalLowering.h"

namespace cheerp {

Expand Down
40 changes: 40 additions & 0 deletions llvm/include/llvm/Cheerp/ThreadLocalLowering.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===-- ThreadLocalLowering.h - Cheerp helper -------------------------===//
//
// Cheerp: The C++ compiler for the Web
//
// This file is distributed under the Apache License v2.0 with LLVM Exceptions.
// See LICENSE.TXT for details.
//
// Copyright 2024-2025 Leaning Technologies
//
//===----------------------------------------------------------------------===//

#ifndef CHEERP_THREAD_LOCAL_LOWERING_H
#define CHEERP_THREAD_LOCAL_LOWERING_H

#include "llvm/IR/PassManager.h"
#include "llvm/Cheerp/GlobalDepsAnalyzer.h"

namespace cheerp{

using namespace llvm;

class ThreadLocalLoweringInnerPass: public PassInfoMixin<ThreadLocalLoweringInnerPass> {
GlobalDepsAnalyzer& GDA;
public:
ThreadLocalLoweringInnerPass(GlobalDepsAnalyzer& GDA) : GDA(GDA)
{
}
PreservedAnalyses run(Function& F, FunctionAnalysisManager& FAM);
static bool isRequired() { return true;}
};

class ThreadLocalLoweringPass: public PassInfoMixin<ThreadLocalLoweringPass> {
public:
PreservedAnalyses run(Module& M, ModuleAnalysisManager& MAM);
static bool isRequired() { return true;}
};

}

#endif
1 change: 1 addition & 0 deletions llvm/lib/CheerpUtils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ add_llvm_component_library(LLVMCheerpUtils
FinalizeMemoryInfo.cpp
CheerpLowerAtomic.cpp
JsExport.cpp
ThreadLocalLowering.cpp
)

add_dependencies(LLVMCheerpUtils intrinsics_gen)
1 change: 1 addition & 0 deletions llvm/lib/CheerpUtils/PointerAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ PointerKindWrapper& PointerUsageVisitor::visitValue(PointerKindWrapper& ret, con
case Intrinsic::invariant_start:
return CacheAndReturn(visitValue(ret, intrinsic->getArgOperand(1), /*first*/ false));
case Intrinsic::stacksave:
case Intrinsic::threadlocal_address:
return CacheAndReturn(ret = PointerKindWrapper(RAW));
case Intrinsic::invariant_end:
case Intrinsic::vastart:
Expand Down
127 changes: 127 additions & 0 deletions llvm/lib/CheerpUtils/ThreadLocalLowering.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//===-- ThreadLocalLowering.cpp - Cheerp helper -------------------------===//
//
// Cheerp: The C++ compiler for the Web
//
// This file is distributed under the Apache License v2.0 with LLVM Exceptions.
// See LICENSE.TXT for details.
//
// Copyright 2024-2025 Leaning Technologies
//
//===----------------------------------------------------------------------===//

#include "llvm/Cheerp/ThreadLocalLowering.h"
#include "llvm/Cheerp/GlobalDepsAnalyzer.h"
#include "llvm/Cheerp/LinearMemoryHelper.h"
#include "llvm/Cheerp/InvokeWrapping.h"
#include "llvm/Cheerp/Registerize.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/IRBuilder.h"

using namespace llvm;

namespace cheerp
{

static Function* getOrCreateThreadLocalWrapper(Module* M, GlobalDepsAnalyzer& GDA)
{
Type* i8Ty = IntegerType::getInt8Ty(M->getContext());
Type* i8PtrTy = PointerType::get(i8Ty, 0);
Type* i32Ty = IntegerType::getInt32Ty(M->getContext());
Type* argTy[] = {i32Ty};
FunctionType* fTy = FunctionType::get(i8PtrTy,ArrayRef<Type*>(argTy, 1), false);
Function* wrapper = cast<Function>(M->getOrInsertFunction("__getThreadLocalAddress", fTy).getCallee());
if (!wrapper->empty())
return wrapper;

BasicBlock* entry = BasicBlock::Create(M->getContext(),"entry", wrapper);
IRBuilder<> Builder(entry);
// Get the thread local address.
Function* threadPointerIntrinsic = Intrinsic::getDeclaration(M, Intrinsic::cheerp_get_thread_pointer);
Value* threadPointer = Builder.CreateCall(threadPointerIntrinsic);
// Add the offset argument
Value* offset = wrapper->getArg(0);
Value* address = Builder.CreateAdd(threadPointer, offset);
// Bitcast to a pointer
address = Builder.CreateIntToPtr(address, i8PtrTy);
Builder.CreateRet(address);

wrapper->setSection("asmjs");
GDA.insertAsmJSExport(wrapper);
return wrapper;
}

bool replaceThreadLocalIntrinsicWithFunction(Function& F, GlobalDepsAnalyzer& GDA)
{
Module* M = F.getParent();
bool changed = false;
SmallVector<Instruction*, 8> deleteList;

for (BasicBlock& BB: F)
{
for (Instruction& I: BB)
{
if (isa<IntrinsicInst>(I))
{
IntrinsicInst& II = cast<IntrinsicInst>(I);
Intrinsic::ID intrId = II.getIntrinsicID();
if (intrId == Intrinsic::threadlocal_address)
{
IRBuilder<> Builder(&II);
// Replace call to intrinsic with function
// 1. Use an intrinsic that will be the offset for the threadlocal.
Type* argTy[] = {II.getOperand(0)->getType()};
Function* offsetIntrinsic = Intrinsic::getDeclaration(M, Intrinsic::cheerp_get_threadlocal_offset, argTy);
Value* offset = Builder.CreateCall(offsetIntrinsic, II.getOperand(0));
// 2. Pass this offset to the wasm function that will calculate the address from the thread pointer.
Function* newFunc = getOrCreateThreadLocalWrapper(M, GDA);
Value* newCall = Builder.CreateCall(newFunc, offset);
// 3. Bitcast return code from this function to required type.
Type* origType = II.getType();
if (origType != newCall->getType())
newCall = Builder.CreateBitCast(newCall, origType);
I.replaceAllUsesWith(newCall);
deleteList.push_back(&I);
changed = true;
}
}
}
}
for (Instruction* I: deleteList)
I->eraseFromParent();
return changed;
}

PreservedAnalyses ThreadLocalLoweringInnerPass::run(Function& F, FunctionAnalysisManager& FAM)
{
if (F.getSection() == "asmjs")
return PreservedAnalyses::all();

// Find calls to threadlocal.address intrinsic, replace with calls to function.
bool changed = replaceThreadLocalIntrinsicWithFunction(F, GDA);
if (!changed)
return PreservedAnalyses::all();
PreservedAnalyses PA;
PA.preserve<PointerAnalysis>();
PA.preserve<RegisterizeAnalysis>();
PA.preserve<GlobalDepsAnalysis>();
PA.preserve<DominatorTreeAnalysis>();
PA.preserve<InvokeWrappingAnalysis>();
return PA;
}

PreservedAnalyses ThreadLocalLoweringPass::run(Module& M, ModuleAnalysisManager& MAM)
{
FunctionPassManager FPM;

GlobalDepsAnalyzer& GDA = MAM.getResult<GlobalDepsAnalysis>(M);
FPM.addPass(ThreadLocalLoweringInnerPass(GDA));

ModulePassManager MPM;

MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
PreservedAnalyses PA = MPM.run(M, MAM);
return PA;
}

}
1 change: 1 addition & 0 deletions llvm/lib/Target/WebAssembly/CheerpWritePass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ bool CheerpWritePass::runOnModule(Module& M)
MPM.addPass(cheerp::InvokeWrappingPass());
if (isWasmTarget)
MPM.addPass(cheerp::AllocaLoweringPass());
MPM.addPass(cheerp::ThreadLocalLoweringPass());
MPM.addPass(cheerp::FFIWrappingPass());
MPM.addPass(createModuleToFunctionPassAdaptor(cheerp::FixIrreducibleControlFlowPass()));
MPM.addPass(createModuleToFunctionPassAdaptor(cheerp::PointerArithmeticToArrayIndexingPass()));
Expand Down

0 comments on commit 958511a

Please sign in to comment.