mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-22 02:33:06 +01:00
[ORC] Update LLJIT to automatically run specially named initializer functions.
The GenericLLVMIRPlatformSupport class runs a transform on all LLVM IR added to the LLJIT instance to replace instances of llvm.global_ctors with a specially named function that runs the corresponing static initializers (See (GlobalCtorDtorScraper from lib/ExecutionEngine/Orc/LLJIT.cpp). This patch updates the GenericIRPlatform class to check for this specially named function in other materialization units that are added to the JIT and, if found, add the function to the initializer work queue. Doing this allows object files that were compiled from IR and cached to be reloaded in subsequent JIT sessions without their initializers being skipped. To enable testing this patch also updates the lli tool's -jit-kind=orc-lazy mode to respect the -enable-cache-manager and -object-cache-dir options, and modifies the CompileOnDemandLayer to rename extracted submodules to include a hash of the names of their symbol definitions. This allows a simple object caching scheme based on module names (which was already implemented in lli) to work with the lazy JIT.
This commit is contained in:
parent
46f1687baa
commit
b6c9039962
@ -7,9 +7,12 @@
|
|||||||
//===----------------------------------------------------------------------===//
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
|
#include "llvm/ExecutionEngine/Orc/CompileOnDemandLayer.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/Hashing.h"
|
||||||
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
|
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
|
||||||
#include "llvm/IR/Mangler.h"
|
#include "llvm/IR/Mangler.h"
|
||||||
#include "llvm/IR/Module.h"
|
#include "llvm/IR/Module.h"
|
||||||
|
#include "llvm/Support/FormatVariadic.h"
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
using namespace llvm::orc;
|
using namespace llvm::orc;
|
||||||
@ -294,29 +297,52 @@ void CompileOnDemandLayer::emitPartition(
|
|||||||
//
|
//
|
||||||
// FIXME: We apply this promotion once per partitioning. It's safe, but
|
// FIXME: We apply this promotion once per partitioning. It's safe, but
|
||||||
// overkill.
|
// overkill.
|
||||||
|
|
||||||
auto ExtractedTSM =
|
auto ExtractedTSM =
|
||||||
TSM.withModuleDo([&](Module &M) -> Expected<ThreadSafeModule> {
|
TSM.withModuleDo([&](Module &M) -> Expected<ThreadSafeModule> {
|
||||||
auto PromotedGlobals = PromoteSymbols(M);
|
auto PromotedGlobals = PromoteSymbols(M);
|
||||||
if (!PromotedGlobals.empty()) {
|
if (!PromotedGlobals.empty()) {
|
||||||
|
|
||||||
MangleAndInterner Mangle(ES, M.getDataLayout());
|
MangleAndInterner Mangle(ES, M.getDataLayout());
|
||||||
SymbolFlagsMap SymbolFlags;
|
SymbolFlagsMap SymbolFlags;
|
||||||
for (auto &GV : PromotedGlobals)
|
IRSymbolMapper::add(ES, *getManglingOptions(),
|
||||||
SymbolFlags[Mangle(GV->getName())] =
|
PromotedGlobals, SymbolFlags);
|
||||||
JITSymbolFlags::fromGlobalValue(*GV);
|
|
||||||
if (auto Err = R.defineMaterializing(SymbolFlags))
|
if (auto Err = R.defineMaterializing(SymbolFlags))
|
||||||
return std::move(Err);
|
return std::move(Err);
|
||||||
}
|
}
|
||||||
|
|
||||||
expandPartition(*GVsToExtract);
|
expandPartition(*GVsToExtract);
|
||||||
|
|
||||||
|
// Submodule name is given by hashing the names of the globals.
|
||||||
|
std::string SubModuleName;
|
||||||
|
{
|
||||||
|
std::vector<const GlobalValue*> HashGVs;
|
||||||
|
HashGVs.reserve(GVsToExtract->size());
|
||||||
|
for (auto *GV : *GVsToExtract)
|
||||||
|
HashGVs.push_back(GV);
|
||||||
|
llvm::sort(HashGVs, [](const GlobalValue *LHS, const GlobalValue *RHS) {
|
||||||
|
return LHS->getName() < RHS->getName();
|
||||||
|
});
|
||||||
|
hash_code HC(0);
|
||||||
|
for (auto *GV : HashGVs) {
|
||||||
|
assert(GV->hasName() && "All GVs to extract should be named by now");
|
||||||
|
auto GVName = GV->getName();
|
||||||
|
HC = hash_combine(HC, hash_combine_range(GVName.begin(), GVName.end()));
|
||||||
|
}
|
||||||
|
raw_string_ostream(SubModuleName)
|
||||||
|
<< ".submodule."
|
||||||
|
<< formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
|
||||||
|
static_cast<size_t>(HC))
|
||||||
|
<< ".ll";
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the requested partiton (plus any necessary aliases) and
|
// Extract the requested partiton (plus any necessary aliases) and
|
||||||
// put the rest back into the impl dylib.
|
// put the rest back into the impl dylib.
|
||||||
auto ShouldExtract = [&](const GlobalValue &GV) -> bool {
|
auto ShouldExtract = [&](const GlobalValue &GV) -> bool {
|
||||||
return GVsToExtract->count(&GV);
|
return GVsToExtract->count(&GV);
|
||||||
};
|
};
|
||||||
|
|
||||||
return extractSubModule(TSM, ".submodule", ShouldExtract);
|
return extractSubModule(TSM, SubModuleName , ShouldExtract);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ExtractedTSM) {
|
if (!ExtractedTSM) {
|
||||||
|
@ -107,12 +107,16 @@ private:
|
|||||||
/// llvm.global_ctors.
|
/// llvm.global_ctors.
|
||||||
class GlobalCtorDtorScraper {
|
class GlobalCtorDtorScraper {
|
||||||
public:
|
public:
|
||||||
GlobalCtorDtorScraper(GenericLLVMIRPlatformSupport &PS) : PS(PS) {}
|
|
||||||
|
GlobalCtorDtorScraper(GenericLLVMIRPlatformSupport &PS,
|
||||||
|
StringRef InitFunctionPrefix)
|
||||||
|
: PS(PS), InitFunctionPrefix(InitFunctionPrefix) {}
|
||||||
Expected<ThreadSafeModule> operator()(ThreadSafeModule TSM,
|
Expected<ThreadSafeModule> operator()(ThreadSafeModule TSM,
|
||||||
MaterializationResponsibility &R);
|
MaterializationResponsibility &R);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GenericLLVMIRPlatformSupport &PS;
|
GenericLLVMIRPlatformSupport &PS;
|
||||||
|
StringRef InitFunctionPrefix;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Generic IR Platform Support
|
/// Generic IR Platform Support
|
||||||
@ -125,12 +129,14 @@ public:
|
|||||||
// GenericLLVMIRPlatform &P) : P(P) {
|
// GenericLLVMIRPlatform &P) : P(P) {
|
||||||
GenericLLVMIRPlatformSupport(LLJIT &J) : J(J) {
|
GenericLLVMIRPlatformSupport(LLJIT &J) : J(J) {
|
||||||
|
|
||||||
|
MangleAndInterner Mangle(getExecutionSession(), J.getDataLayout());
|
||||||
|
InitFunctionPrefix = Mangle("__orc_init_func.");
|
||||||
|
|
||||||
getExecutionSession().setPlatform(
|
getExecutionSession().setPlatform(
|
||||||
std::make_unique<GenericLLVMIRPlatform>(*this));
|
std::make_unique<GenericLLVMIRPlatform>(*this));
|
||||||
|
|
||||||
setInitTransform(J, GlobalCtorDtorScraper(*this));
|
setInitTransform(J, GlobalCtorDtorScraper(*this, *InitFunctionPrefix));
|
||||||
|
|
||||||
MangleAndInterner Mangle(getExecutionSession(), J.getDataLayout());
|
|
||||||
SymbolMap StdInterposes;
|
SymbolMap StdInterposes;
|
||||||
|
|
||||||
StdInterposes[Mangle("__lljit.platform_support_instance")] =
|
StdInterposes[Mangle("__lljit.platform_support_instance")] =
|
||||||
@ -169,6 +175,18 @@ public:
|
|||||||
std::lock_guard<std::mutex> Lock(PlatformSupportMutex);
|
std::lock_guard<std::mutex> Lock(PlatformSupportMutex);
|
||||||
if (auto &InitSym = MU.getInitializerSymbol())
|
if (auto &InitSym = MU.getInitializerSymbol())
|
||||||
InitSymbols[&JD].add(InitSym);
|
InitSymbols[&JD].add(InitSym);
|
||||||
|
else {
|
||||||
|
// If there's no identified init symbol attached, but there is a symbol
|
||||||
|
// with the GenericIRPlatform::InitFunctionPrefix, then treat that as
|
||||||
|
// an init function. Add the symbol to both the InitSymbols map (which
|
||||||
|
// will trigger a lookup to materialize the module) and the InitFunctions
|
||||||
|
// map (which holds the names of the symbols to execute).
|
||||||
|
for (auto &KV : MU.getSymbols())
|
||||||
|
if ((*KV.first).startswith(*InitFunctionPrefix)) {
|
||||||
|
InitSymbols[&JD].add(KV.first);
|
||||||
|
InitFunctions[&JD].add(KV.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
return Error::success();
|
return Error::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,6 +405,7 @@ private:
|
|||||||
|
|
||||||
std::mutex PlatformSupportMutex;
|
std::mutex PlatformSupportMutex;
|
||||||
LLJIT &J;
|
LLJIT &J;
|
||||||
|
SymbolStringPtr InitFunctionPrefix;
|
||||||
DenseMap<JITDylib *, SymbolLookupSet> InitSymbols;
|
DenseMap<JITDylib *, SymbolLookupSet> InitSymbols;
|
||||||
DenseMap<JITDylib *, SymbolLookupSet> InitFunctions;
|
DenseMap<JITDylib *, SymbolLookupSet> InitFunctions;
|
||||||
DenseMap<JITDylib *, SymbolLookupSet> DeInitFunctions;
|
DenseMap<JITDylib *, SymbolLookupSet> DeInitFunctions;
|
||||||
@ -415,7 +434,7 @@ GlobalCtorDtorScraper::operator()(ThreadSafeModule TSM,
|
|||||||
|
|
||||||
std::string InitFunctionName;
|
std::string InitFunctionName;
|
||||||
raw_string_ostream(InitFunctionName)
|
raw_string_ostream(InitFunctionName)
|
||||||
<< "__orc_init." << M.getModuleIdentifier();
|
<< InitFunctionPrefix << M.getModuleIdentifier();
|
||||||
|
|
||||||
MangleAndInterner Mangle(PS.getExecutionSession(), M.getDataLayout());
|
MangleAndInterner Mangle(PS.getExecutionSession(), M.getDataLayout());
|
||||||
auto InternedName = Mangle(InitFunctionName);
|
auto InternedName = Mangle(InitFunctionName);
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
; RUN: rm -rf %t
|
||||||
|
; RUN: mkdir -p %t
|
||||||
|
; RUN: lli -jit-kind=orc-lazy -enable-cache-manager -object-cache-dir=%t %s
|
||||||
|
; RUN: lli -jit-kind=orc-lazy -enable-cache-manager -object-cache-dir=%t %s
|
||||||
|
;
|
||||||
|
; Verify that LLJIT Platforms respect static initializers in cached objects.
|
||||||
|
; This IR file contains a static initializer that must execute for main to exit
|
||||||
|
; with value zero. The first execution will populate an object cache for the
|
||||||
|
; second. The initializer in the cached objects must also be successfully run
|
||||||
|
; for the test to pass.
|
||||||
|
|
||||||
|
@HasError = global i8 1, align 1
|
||||||
|
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @resetHasError, i8* null }]
|
||||||
|
|
||||||
|
define void @resetHasError() {
|
||||||
|
entry:
|
||||||
|
store i8 0, i8* @HasError, align 1
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32 @main(i32 %argc, i8** %argv) #2 {
|
||||||
|
entry:
|
||||||
|
%0 = load i8, i8* @HasError, align 1
|
||||||
|
%tobool = trunc i8 %0 to i1
|
||||||
|
%conv = zext i1 %tobool to i32
|
||||||
|
ret i32 %conv
|
||||||
|
}
|
||||||
|
|
@ -274,6 +274,7 @@ public:
|
|||||||
SmallString<128> dir(sys::path::parent_path(CacheName));
|
SmallString<128> dir(sys::path::parent_path(CacheName));
|
||||||
sys::fs::create_directories(Twine(dir));
|
sys::fs::create_directories(Twine(dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::error_code EC;
|
std::error_code EC;
|
||||||
raw_fd_ostream outfile(CacheName, EC, sys::fs::OF_None);
|
raw_fd_ostream outfile(CacheName, EC, sys::fs::OF_None);
|
||||||
outfile.write(Obj.getBufferStart(), Obj.getBufferSize());
|
outfile.write(Obj.getBufferStart(), Obj.getBufferSize());
|
||||||
@ -306,14 +307,16 @@ private:
|
|||||||
size_t PrefixLength = Prefix.length();
|
size_t PrefixLength = Prefix.length();
|
||||||
if (ModID.substr(0, PrefixLength) != Prefix)
|
if (ModID.substr(0, PrefixLength) != Prefix)
|
||||||
return false;
|
return false;
|
||||||
std::string CacheSubdir = ModID.substr(PrefixLength);
|
|
||||||
|
std::string CacheSubdir = ModID.substr(PrefixLength);
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
// Transform "X:\foo" => "/X\foo" for convenience.
|
// Transform "X:\foo" => "/X\foo" for convenience.
|
||||||
if (isalpha(CacheSubdir[0]) && CacheSubdir[1] == ':') {
|
if (isalpha(CacheSubdir[0]) && CacheSubdir[1] == ':') {
|
||||||
CacheSubdir[1] = CacheSubdir[0];
|
CacheSubdir[1] = CacheSubdir[0];
|
||||||
CacheSubdir[0] = '/';
|
CacheSubdir[0] = '/';
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CacheName = CacheDir + CacheSubdir;
|
CacheName = CacheDir + CacheSubdir;
|
||||||
size_t pos = CacheName.rfind('.');
|
size_t pos = CacheName.rfind('.');
|
||||||
CacheName.replace(pos, CacheName.length() - pos, ".o");
|
CacheName.replace(pos, CacheName.length() - pos, ".o");
|
||||||
@ -777,30 +780,56 @@ Error loadDylibs() {
|
|||||||
|
|
||||||
static void exitOnLazyCallThroughFailure() { exit(1); }
|
static void exitOnLazyCallThroughFailure() { exit(1); }
|
||||||
|
|
||||||
|
Expected<orc::ThreadSafeModule>
|
||||||
|
loadModule(StringRef Path, orc::ThreadSafeContext TSCtx) {
|
||||||
|
SMDiagnostic Err;
|
||||||
|
auto M = parseIRFile(Path, Err, *TSCtx.getContext());
|
||||||
|
if (!M) {
|
||||||
|
std::string ErrMsg;
|
||||||
|
{
|
||||||
|
raw_string_ostream ErrMsgStream(ErrMsg);
|
||||||
|
Err.print("lli", ErrMsgStream);
|
||||||
|
}
|
||||||
|
return make_error<StringError>(std::move(ErrMsg), inconvertibleErrorCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnableCacheManager)
|
||||||
|
M->setModuleIdentifier("file:" + M->getModuleIdentifier());
|
||||||
|
|
||||||
|
return orc::ThreadSafeModule(std::move(M), std::move(TSCtx));
|
||||||
|
}
|
||||||
|
|
||||||
int runOrcLazyJIT(const char *ProgName) {
|
int runOrcLazyJIT(const char *ProgName) {
|
||||||
// Start setting up the JIT environment.
|
// Start setting up the JIT environment.
|
||||||
|
|
||||||
// Parse the main module.
|
// Parse the main module.
|
||||||
orc::ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());
|
orc::ThreadSafeContext TSCtx(std::make_unique<LLVMContext>());
|
||||||
SMDiagnostic Err;
|
auto MainModule = ExitOnErr(loadModule(InputFile, TSCtx));
|
||||||
auto MainModule = parseIRFile(InputFile, Err, *TSCtx.getContext());
|
|
||||||
if (!MainModule)
|
// Get TargetTriple and DataLayout from the main module if they're explicitly
|
||||||
reportError(Err, ProgName);
|
// set.
|
||||||
|
Optional<Triple> TT;
|
||||||
|
Optional<DataLayout> DL;
|
||||||
|
MainModule.withModuleDo([&](Module &M) {
|
||||||
|
if (!M.getTargetTriple().empty())
|
||||||
|
TT = Triple(M.getTargetTriple());
|
||||||
|
if (!M.getDataLayout().isDefault())
|
||||||
|
DL = M.getDataLayout();
|
||||||
|
});
|
||||||
|
|
||||||
Triple TT(MainModule->getTargetTriple());
|
|
||||||
orc::LLLazyJITBuilder Builder;
|
orc::LLLazyJITBuilder Builder;
|
||||||
|
|
||||||
Builder.setJITTargetMachineBuilder(
|
Builder.setJITTargetMachineBuilder(
|
||||||
MainModule->getTargetTriple().empty()
|
TT ? orc::JITTargetMachineBuilder(*TT)
|
||||||
? ExitOnErr(orc::JITTargetMachineBuilder::detectHost())
|
: ExitOnErr(orc::JITTargetMachineBuilder::detectHost()));
|
||||||
: orc::JITTargetMachineBuilder(TT));
|
|
||||||
|
TT = Builder.getJITTargetMachineBuilder()->getTargetTriple();
|
||||||
|
if (DL)
|
||||||
|
Builder.setDataLayout(DL);
|
||||||
|
|
||||||
if (!MArch.empty())
|
if (!MArch.empty())
|
||||||
Builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName(MArch);
|
Builder.getJITTargetMachineBuilder()->getTargetTriple().setArchName(MArch);
|
||||||
|
|
||||||
if (!MainModule->getDataLayout().isDefault())
|
|
||||||
Builder.setDataLayout(MainModule->getDataLayout());
|
|
||||||
|
|
||||||
Builder.getJITTargetMachineBuilder()
|
Builder.getJITTargetMachineBuilder()
|
||||||
->setCPU(getCPUStr())
|
->setCPU(getCPUStr())
|
||||||
.addFeatures(getFeatureList())
|
.addFeatures(getFeatureList())
|
||||||
@ -815,11 +844,34 @@ int runOrcLazyJIT(const char *ProgName) {
|
|||||||
pointerToJITTargetAddress(exitOnLazyCallThroughFailure));
|
pointerToJITTargetAddress(exitOnLazyCallThroughFailure));
|
||||||
Builder.setNumCompileThreads(LazyJITCompileThreads);
|
Builder.setNumCompileThreads(LazyJITCompileThreads);
|
||||||
|
|
||||||
|
// If the object cache is enabled then set a custom compile function
|
||||||
|
// creator to use the cache.
|
||||||
|
std::unique_ptr<LLIObjectCache> CacheManager;
|
||||||
|
if (EnableCacheManager) {
|
||||||
|
|
||||||
|
CacheManager = std::make_unique<LLIObjectCache>(ObjectCacheDir);
|
||||||
|
|
||||||
|
Builder.setCompileFunctionCreator(
|
||||||
|
[&](orc::JITTargetMachineBuilder JTMB)
|
||||||
|
-> Expected<std::unique_ptr<orc::IRCompileLayer::IRCompiler>> {
|
||||||
|
if (LazyJITCompileThreads > 0)
|
||||||
|
return std::make_unique<orc::ConcurrentIRCompiler>(std::move(JTMB),
|
||||||
|
CacheManager.get());
|
||||||
|
|
||||||
|
auto TM = JTMB.createTargetMachine();
|
||||||
|
if (!TM)
|
||||||
|
return TM.takeError();
|
||||||
|
|
||||||
|
return std::make_unique<orc::TMOwningSimpleCompiler>(std::move(*TM),
|
||||||
|
CacheManager.get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Set up LLJIT platform.
|
// Set up LLJIT platform.
|
||||||
{
|
{
|
||||||
LLJITPlatform P = Platform;
|
LLJITPlatform P = Platform;
|
||||||
if (P == LLJITPlatform::DetectHost) {
|
if (P == LLJITPlatform::DetectHost) {
|
||||||
if (TT.isOSBinFormatMachO())
|
if (TT->isOSBinFormatMachO())
|
||||||
P = LLJITPlatform::MachO;
|
P = LLJITPlatform::MachO;
|
||||||
else
|
else
|
||||||
P = LLJITPlatform::GenericIR;
|
P = LLJITPlatform::GenericIR;
|
||||||
@ -871,8 +923,7 @@ int runOrcLazyJIT(const char *ProgName) {
|
|||||||
})));
|
})));
|
||||||
|
|
||||||
// Add the main module.
|
// Add the main module.
|
||||||
ExitOnErr(
|
ExitOnErr(J->addLazyIRModule(std::move(MainModule)));
|
||||||
J->addLazyIRModule(orc::ThreadSafeModule(std::move(MainModule), TSCtx)));
|
|
||||||
|
|
||||||
// Create JITDylibs and add any extra modules.
|
// Create JITDylibs and add any extra modules.
|
||||||
{
|
{
|
||||||
@ -894,16 +945,13 @@ int runOrcLazyJIT(const char *ProgName) {
|
|||||||
|
|
||||||
for (auto EMItr = ExtraModules.begin(), EMEnd = ExtraModules.end();
|
for (auto EMItr = ExtraModules.begin(), EMEnd = ExtraModules.end();
|
||||||
EMItr != EMEnd; ++EMItr) {
|
EMItr != EMEnd; ++EMItr) {
|
||||||
auto M = parseIRFile(*EMItr, Err, *TSCtx.getContext());
|
auto M = ExitOnErr(loadModule(*EMItr, TSCtx));
|
||||||
if (!M)
|
|
||||||
reportError(Err, ProgName);
|
|
||||||
|
|
||||||
auto EMIdx = ExtraModules.getPosition(EMItr - ExtraModules.begin());
|
auto EMIdx = ExtraModules.getPosition(EMItr - ExtraModules.begin());
|
||||||
assert(EMIdx != 0 && "ExtraModule should have index > 0");
|
assert(EMIdx != 0 && "ExtraModule should have index > 0");
|
||||||
auto JDItr = std::prev(IdxToDylib.lower_bound(EMIdx));
|
auto JDItr = std::prev(IdxToDylib.lower_bound(EMIdx));
|
||||||
auto &JD = *JDItr->second;
|
auto &JD = *JDItr->second;
|
||||||
ExitOnErr(
|
ExitOnErr(J->addLazyIRModule(JD, std::move(M)));
|
||||||
J->addLazyIRModule(JD, orc::ThreadSafeModule(std::move(M), TSCtx)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto EAItr = ExtraArchives.begin(), EAEnd = ExtraArchives.end();
|
for (auto EAItr = ExtraArchives.begin(), EAEnd = ExtraArchives.end();
|
||||||
|
Loading…
Reference in New Issue
Block a user