diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -151,6 +151,12 @@ along with tools such as Live++ or Recode. Microsoft Edit and Continue isn't currently supported. +- Add support for MSVC-compatible ``/JMC``/``/JMC-`` flag in clang-cl (supports + X86/X64/ARM/ARM64). ``/JMC`` could only be used when ``/Zi`` or ``/Z7`` is + turned on. With this addition, clang-cl can be used in Visual Studio for the + JustMyCode feature. Note, you may need to manually add ``/JMC`` as additional + compile options in the Visual Studio since it currently assumes clang-cl does not support ``/JMC``. + C Language Changes in Clang --------------------------- diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -144,6 +144,7 @@ CODEGENOPT(HotPatch, 1, 0) ///< Supports the Microsoft /HOTPATCH flag and ///< generates a 'patchable-function' attribute. +CODEGENOPT(JMCInstrument, 1, 0) ///< Set when -fjmc is enabled. CODEGENOPT(InstrumentForProfiling , 1, 0) ///< Set when -pg is enabled. CODEGENOPT(CallFEntry , 1, 0) ///< Set when -mfentry is enabled. CODEGENOPT(MNopMCount , 1, 0) ///< Set when -mnop-mcount is enabled. diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -631,4 +631,8 @@ "Only one offload target is supported in %0.">; def err_drv_invalid_or_unsupported_offload_target : Error< "Invalid or unsupported offload target: '%0'.">; + +def warn_drv_jmc_requires_debuginfo : Warning< + "/JMC requires debug info. Use '/Zi', '/Z7' or other debug options; option ignored">, + InGroup; } diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1441,6 +1441,9 @@ HelpText<"Do not elide types when printing diagnostics">, MarshallingInfoNegativeFlag>; def feliminate_unused_debug_symbols : Flag<["-"], "feliminate-unused-debug-symbols">, Group; +def fjmc : Flag<["-"], "fjmc">, Group,Flags<[CC1Option]>, + HelpText<"Enable just-my-code debugging">, + MarshallingInfoFlag>; defm eliminate_unused_debug_types : OptOutCC1FFlag<"eliminate-unused-debug-types", "Do not emit ", "Emit ", " debug info for defined but unused types">; def femit_all_decls : Flag<["-"], "femit-all-decls">, Group, Flags<[CC1Option]>, @@ -6324,6 +6327,10 @@ def _SLASH_imsvc : CLJoinedOrSeparate<"imsvc">, HelpText<"Add to system include search path, as if in %INCLUDE%">, MetaVarName<"">; +def _SLASH_JMC : CLFlag<"JMC">, + HelpText<"Enable just-my-code debugging">; +def _SLASH_JMC_ : CLFlag<"JMC-">, + HelpText<"Disable just-my-code debugging (default)">; def _SLASH_LD : CLFlag<"LD">, HelpText<"Create DLL">; def _SLASH_LDd : CLFlag<"LDd">, HelpText<"Create debug DLL">; def _SLASH_link : CLRemainingArgsJoined<"link">, @@ -6424,7 +6431,6 @@ def _SLASH_FC : CLIgnoredFlag<"FC">; def _SLASH_Fd : CLIgnoredJoined<"Fd">; def _SLASH_FS : CLIgnoredFlag<"FS">; -def _SLASH_JMC : CLIgnoredFlag<"JMC">; def _SLASH_kernel_ : CLIgnoredFlag<"kernel-">; def _SLASH_nologo : CLIgnoredFlag<"nologo">; def _SLASH_RTC : CLIgnoredJoined<"RTC">; diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -605,6 +605,10 @@ Options.EnableAIXExtendedAltivecABI = CodeGenOpts.EnableAIXExtendedAltivecABI; Options.XRayOmitFunctionIndex = CodeGenOpts.XRayOmitFunctionIndex; Options.LoopAlignment = CodeGenOpts.LoopAlignment; + Options.DebugStrictDwarf = CodeGenOpts.DebugStrictDwarf; + Options.ObjectFilenameForDebug = CodeGenOpts.ObjectFilenameForDebug; + Options.Hotpatch = CodeGenOpts.HotPatch; + Options.JMCInstrument = CodeGenOpts.JMCInstrument; switch (CodeGenOpts.getSwiftAsyncFramePointer()) { case CodeGenOptions::SwiftAsyncFramePointerKind::Auto: @@ -643,9 +647,6 @@ Entry.IgnoreSysRoot ? Entry.Path : HSOpts.Sysroot + Entry.Path); Options.MCOptions.Argv0 = CodeGenOpts.Argv0; Options.MCOptions.CommandLineArgs = CodeGenOpts.CommandLineArgs; - Options.DebugStrictDwarf = CodeGenOpts.DebugStrictDwarf; - Options.ObjectFilenameForDebug = CodeGenOpts.ObjectFilenameForDebug; - Options.Hotpatch = CodeGenOpts.HotPatch; return true; } diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7451,6 +7451,16 @@ } const Driver &D = getToolChain().getDriver(); + + // This controls whether or not we perform JustMyCode instrumentation. + if (Args.hasFlag(options::OPT__SLASH_JMC, options::OPT__SLASH_JMC_, + /*Default=*/false)) { + if (*EmitCodeView && *DebugInfoKind >= codegenoptions::DebugInfoConstructor) + CmdArgs.push_back("-fjmc"); + else + D.Diag(clang::diag::warn_drv_jmc_requires_debuginfo); + } + EHFlags EH = parseClangCLEHFlags(D, Args); if (!isNVPTX && (EH.Synch || EH.Asynch)) { if (types::isCXX(InputType)) diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c --- a/clang/test/Driver/cl-options.c +++ b/clang/test/Driver/cl-options.c @@ -486,7 +486,6 @@ // RUN: /GZ \ // RUN: /H \ // RUN: /homeparams \ -// RUN: /JMC \ // RUN: /kernel \ // RUN: /LN \ // RUN: /MP \ @@ -772,4 +771,14 @@ // FAKEDIR: "-libpath:/foo{{/|\\\\}}Lib{{/|\\\\}}10.0.12345.0{{/|\\\\}}ucrt // FAKEDIR: "-libpath:/foo{{/|\\\\}}Lib{{/|\\\\}}10.0.12345.0{{/|\\\\}}um +// RUN: %clang_cl /JMC /c -### -- %s 2>&1 | FileCheck %s --check-prefix JMCWARN +// JMCWARN: /JMC requires debug info. Use '/Zi', '/Z7' or other debug options; option ignored + +// RUN: %clang_cl /JMC /c -### -- %s 2>&1 | FileCheck %s --check-prefix NOJMC +// RUN: %clang_cl /JMC /Z7 /JMC- /c -### -- %s 2>&1 | FileCheck %s --check-prefix NOJMC +// NOJMC-NOT: -fjmc + +// RUN: %clang_cl /JMC /Z7 /c -### -- %s 2>&1 | FileCheck %s --check-prefix JMC +// JMC: -fjmc + void f() { } diff --git a/llvm/include/llvm/CodeGen/CommandFlags.h b/llvm/include/llvm/CodeGen/CommandFlags.h --- a/llvm/include/llvm/CodeGen/CommandFlags.h +++ b/llvm/include/llvm/CodeGen/CommandFlags.h @@ -140,6 +140,8 @@ unsigned getAlignLoops(); +bool getJMCInstrument(); + /// Create this object with static storage to register codegen-related command /// line options. struct RegisterCodeGenFlags { diff --git a/llvm/include/llvm/CodeGen/Passes.h b/llvm/include/llvm/CodeGen/Passes.h --- a/llvm/include/llvm/CodeGen/Passes.h +++ b/llvm/include/llvm/CodeGen/Passes.h @@ -554,6 +554,9 @@ /// When learning an eviction policy, extract score(reward) information, /// otherwise this does nothing FunctionPass *createRegAllocScoringPass(); + + /// JMC instrument pass. + ModulePass *createJMCInstrumenterPass(); } // End llvm namespace #endif diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -215,6 +215,7 @@ void initializeInterleavedLoadCombinePass(PassRegistry &); void initializeInternalizeLegacyPassPass(PassRegistry&); void initializeIntervalPartitionPass(PassRegistry&); +void initializeJMCInstrumenterPass(PassRegistry&); void initializeJumpThreadingPass(PassRegistry&); void initializeLCSSAVerificationPassPass(PassRegistry&); void initializeLCSSAWrapperPassPass(PassRegistry&); diff --git a/llvm/include/llvm/LinkAllPasses.h b/llvm/include/llvm/LinkAllPasses.h --- a/llvm/include/llvm/LinkAllPasses.h +++ b/llvm/include/llvm/LinkAllPasses.h @@ -123,6 +123,7 @@ (void) llvm::createInstSimplifyLegacyPass(); (void) llvm::createInstructionCombiningPass(); (void) llvm::createInternalizePass(); + (void) llvm::createJMCInstrumenterPass(); (void) llvm::createLCSSAPass(); (void) llvm::createLegacyDivergenceAnalysisPass(); (void) llvm::createLICMPass(); diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h --- a/llvm/include/llvm/Target/TargetOptions.h +++ b/llvm/include/llvm/Target/TargetOptions.h @@ -142,7 +142,7 @@ SupportsDebugEntryValues(false), EnableDebugEntryValues(false), ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false), XRayOmitFunctionIndex(false), DebugStrictDwarf(false), - Hotpatch(false), + Hotpatch(false), JMCInstrument(false), FPDenormalMode(DenormalMode::IEEE, DenormalMode::IEEE) {} /// DisableFramePointerElim - This returns true if frame pointer elimination @@ -345,6 +345,9 @@ /// Emit the hotpatch flag in CodeView debug. unsigned Hotpatch : 1; + /// Enable JustMyCode instrumentation. + unsigned JMCInstrument : 1; + /// Name of the stack usage file (i.e., .su file) if user passes /// -fstack-usage. If empty, it can be implied that -fstack-usage is not /// passed on the command line. diff --git a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp --- a/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp @@ -3355,14 +3355,18 @@ if (const auto *MemberDecl = dyn_cast_or_null( DIGV->getRawStaticDataMemberDeclaration())) Scope = MemberDecl->getScope(); + + const GlobalVariable *GV = CVGV.GVInfo.dyn_cast(); + // For Fortran, the scoping portion is elided in its name so that we can // reference the variable in the command line of the VS debugger. + // For JMC, the flag symbols do not need qualifiers. std::string QualifiedName = - (moduleIsInFortran()) ? std::string(DIGV->getName()) - : getFullyQualifiedName(Scope, DIGV->getName()); + (moduleIsInFortran() || (GV && GV->getSection() == ".msvcjmc")) + ? std::string(DIGV->getName()) + : getFullyQualifiedName(Scope, DIGV->getName()); - if (const GlobalVariable *GV = - CVGV.GVInfo.dyn_cast()) { + if (GV) { // DataSym record, see SymbolRecord.h for more info. Thread local data // happens to have the same format as global data. MCSymbol *GVSym = Asm->getSymbol(GV); diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -75,6 +75,7 @@ InterleavedAccessPass.cpp InterleavedLoadCombinePass.cpp IntrinsicLowering.cpp + JMCInstrumenter.cpp LatencyPriorityQueue.cpp LazyMachineBlockFrequencyInfo.cpp LexicalScopes.cpp diff --git a/llvm/lib/CodeGen/CodeGen.cpp b/llvm/lib/CodeGen/CodeGen.cpp --- a/llvm/lib/CodeGen/CodeGen.cpp +++ b/llvm/lib/CodeGen/CodeGen.cpp @@ -50,6 +50,7 @@ initializeIndirectBrExpandPassPass(Registry); initializeInterleavedLoadCombinePass(Registry); initializeInterleavedAccessPass(Registry); + initializeJMCInstrumenterPass(Registry); initializeLiveDebugValuesPass(Registry); initializeLiveDebugVariablesPass(Registry); initializeLiveIntervalsPass(Registry); diff --git a/llvm/lib/CodeGen/CommandFlags.cpp b/llvm/lib/CodeGen/CommandFlags.cpp --- a/llvm/lib/CodeGen/CommandFlags.cpp +++ b/llvm/lib/CodeGen/CommandFlags.cpp @@ -94,6 +94,7 @@ CGOPT(bool, XRayOmitFunctionIndex) CGOPT(bool, DebugStrictDwarf) CGOPT(unsigned, AlignLoops) +CGOPT(bool, JMCInstrument) codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() { #define CGBINDOPT(NAME) \ @@ -457,6 +458,12 @@ cl::desc("Default alignment for loops")); CGBINDOPT(AlignLoops); + static cl::opt JMCInstrument( + "enable-jmc-instrument", + cl::desc("Instrument functions with a call to __CheckForDebuggerJustMyCode"), + cl::init(false)); + CGBINDOPT(JMCInstrument); + #undef CGBINDOPT mc::RegisterMCTargetOptionsFlags(); @@ -531,6 +538,7 @@ Options.XRayOmitFunctionIndex = getXRayOmitFunctionIndex(); Options.DebugStrictDwarf = getDebugStrictDwarf(); Options.LoopAlignment = getAlignLoops(); + Options.JMCInstrument = getJMCInstrument(); Options.MCOptions = mc::InitMCTargetOptionsFromFlags(); diff --git a/llvm/lib/CodeGen/JMCInstrumenter.cpp b/llvm/lib/CodeGen/JMCInstrumenter.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/CodeGen/JMCInstrumenter.cpp @@ -0,0 +1,207 @@ +//===- JMCInstrumenter.cpp - JMC Instrumentation --------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// JMCInstrumenter pass: +// - add "/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default" to +// "llvm.linker.options" +// - create the dummy COMDAT function __JustMyCode_Default +// - instrument each function with a call to __CheckForDebuggerJustMyCode. The +// sole argument should be defined in .msvcjmc. Each flag is 1 byte initilized +// to 1. +// - (TODO) currently targeting MSVC, adds ELF debuggers support +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/IR/DIBuilder.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Support/DJB.h" +#include "llvm/Support/Path.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" + +using namespace llvm; + +#define DEBUG_TYPE "jmc-instrument" + +namespace { +struct JMCInstrumenter : public ModulePass { + static char ID; + JMCInstrumenter() : ModulePass(ID) { + initializeJMCInstrumenterPass(*PassRegistry::getPassRegistry()); + } + bool runOnModule(Module &M) override; +}; +char JMCInstrumenter::ID = 0; +} // namespace + +INITIALIZE_PASS( + JMCInstrumenter, DEBUG_TYPE, + "Instrument function entry with call to __CheckForDebuggerJustMyCode", + false, false) + +ModulePass *llvm::createJMCInstrumenterPass() { return new JMCInstrumenter(); } + +namespace { +const char CheckFunctionName[] = "__CheckForDebuggerJustMyCode"; + +std::string getFlagName(DISubprogram &SP, bool UseX86FastCall) { + // Best effort path normalization. This is to guarantee an unique flag symbol + // is produced for the same directory. Some builds may want to use relative + // paths, or paths with a specific prefix (see the -fdebug-compilation-dir + // flag), so only hash paths in debuginfo. Don't expand them to absolute + // paths. + SmallString<256> FilePath(SP.getDirectory()); + sys::path::append(FilePath, SP.getFilename()); + sys::path::native(FilePath); + sys::path::remove_dots(FilePath, /*remove_dot_dot=*/true); + + // The naming convention for the flag name is ___ with '.' in + // replaced with '@'. For example C:\file.any.c would have a flag + // __D032E919_file@any@c. The naming convention match MSVC's format however + // the match is not required to make JMC work. The hashing function used here + // is different from MSVC's. + + std::string Suffix; + for (auto C : sys::path::filename(FilePath)) + Suffix.push_back(C == '.' ? '@' : C); + + sys::path::remove_filename(FilePath); + return (UseX86FastCall ? "_" : "__") + + utohexstr(djbHash(FilePath), /*LowerCase=*/false, + /*Width=*/8) + + "_" + Suffix; +} + +void attachDebugInfo(GlobalVariable &GV, DISubprogram &SP) { + Module &M = *GV.getParent(); + DICompileUnit *CU = SP.getUnit(); + assert(CU); + DIBuilder DB(M, false, CU); + + auto *DType = + DB.createBasicType("unsigned char", 8, dwarf::DW_ATE_unsigned_char, + llvm::DINode::FlagArtificial); + + auto *DGVE = DB.createGlobalVariableExpression( + CU, GV.getName(), /*LinkageName=*/StringRef(), SP.getFile(), + /*LineNo=*/0, DType, /*IsLocalToUnit=*/true, /*IsDefined=*/true); + GV.addMetadata(LLVMContext::MD_dbg, *DGVE); + DB.finalize(); +} + +FunctionType *getCheckFunctionType(LLVMContext &Ctx) { + Type *VoidTy = Type::getVoidTy(Ctx); + PointerType *VoidPtrTy = Type::getInt8PtrTy(Ctx); + return FunctionType::get(VoidTy, VoidPtrTy, false); +} + +void createDefaultCheckFunction(Module &M, bool UseX86FastCall) { + LLVMContext &Ctx = M.getContext(); + const char *DefaultCheckFunctionName = + UseX86FastCall ? "_JustMyCode_Default" : "__JustMyCode_Default"; + // Create the function. + Function *DefaultCheckFunc = + Function::Create(getCheckFunctionType(Ctx), GlobalValue::ExternalLinkage, + DefaultCheckFunctionName, &M); + DefaultCheckFunc->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); + DefaultCheckFunc->addParamAttr(0, Attribute::NoUndef); + if (UseX86FastCall) + DefaultCheckFunc->addParamAttr(0, Attribute::InReg); + appendToUsed(M, {DefaultCheckFunc}); + Comdat *C = M.getOrInsertComdat(DefaultCheckFunctionName); + C->setSelectionKind(Comdat::Any); + DefaultCheckFunc->setComdat(C); + BasicBlock *EntryBB = BasicBlock::Create(Ctx, "", DefaultCheckFunc); + ReturnInst::Create(Ctx, EntryBB); + + // Add a linker option /alternatename to set the default implementation for + // the check function. + // https://devblogs.microsoft.com/oldnewthing/20200731-00/?p=104024 + std::string AltOption = std::string("/alternatename:") + CheckFunctionName + + "=" + DefaultCheckFunctionName; + llvm::Metadata *Ops[] = {llvm::MDString::get(Ctx, AltOption)}; + MDTuple *N = MDNode::get(Ctx, Ops); + M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(N); +} +} // namespace + +bool JMCInstrumenter::runOnModule(Module &M) { + bool Changed = false; + LLVMContext &Ctx = M.getContext(); + Triple ModuleTriple(M.getTargetTriple()); + bool UseX86FastCall = + ModuleTriple.isOSWindows() && ModuleTriple.getArch() == Triple::x86; + + Function *CheckFunction = nullptr; + DenseMap SavedFlags(8); + for (auto &F : M) { + if (F.isDeclaration()) + continue; + auto *SP = F.getSubprogram(); + if (!SP) + continue; + + Constant *&Flag = SavedFlags[SP]; + if (!Flag) { + std::string FlagName = getFlagName(*SP, UseX86FastCall); + IntegerType *FlagTy = Type::getInt8Ty(Ctx); + Flag = M.getOrInsertGlobal(FlagName, FlagTy, [&] { + // FIXME: Put the GV in comdat and have linkonce_odr linkage to save + // .msvcjmc section space? maybe not worth it. + GlobalVariable *GV = new GlobalVariable( + M, FlagTy, /*isConstant=*/false, GlobalValue::InternalLinkage, + ConstantInt::get(FlagTy, 1), FlagName); + GV->setSection(".msvcjmc"); + GV->setAlignment(Align(1)); + GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); + attachDebugInfo(*GV, *SP); + return GV; + }); + } + + if (!CheckFunction) { + assert(!M.getFunction(CheckFunctionName) && + "JMC instrument more than once?"); + FunctionCallee Fn = + M.getOrInsertFunction(CheckFunctionName, getCheckFunctionType(Ctx)); + CheckFunction = cast(Fn.getCallee()); + CheckFunction->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); + CheckFunction->addParamAttr(0, Attribute::NoUndef); + if (UseX86FastCall) { + CheckFunction->setCallingConv(CallingConv::X86_FastCall); + CheckFunction->addParamAttr(0, Attribute::InReg); + } + } + // FIXME: it would be nice to make CI scheduling boundary, although in + // practice it does not matter much. + auto *CI = CallInst::Create(CheckFunction, {Flag}, "", + &*F.begin()->getFirstInsertionPt()); + CI->addParamAttr(0, Attribute::NoUndef); + if (UseX86FastCall) { + CI->setCallingConv(CallingConv::X86_FastCall); + CI->addParamAttr(0, Attribute::InReg); + } + + Changed = true; + } + if (!Changed) + return false; + + createDefaultCheckFunction(M, UseX86FastCall); + return true; +} diff --git a/llvm/lib/CodeGen/TargetPassConfig.cpp b/llvm/lib/CodeGen/TargetPassConfig.cpp --- a/llvm/lib/CodeGen/TargetPassConfig.cpp +++ b/llvm/lib/CodeGen/TargetPassConfig.cpp @@ -1092,6 +1092,8 @@ addPass(createPreISelIntrinsicLoweringPass()); PM->add(createTargetTransformInfoWrapperPass(TM->getTargetIRAnalysis())); addIRPasses(); + if (TM->Options.JMCInstrument) + addPass(createJMCInstrumenterPass()); addCodeGenPrepare(); addPassesToHandleExceptions(); addISelPrepare(); diff --git a/llvm/lib/Transforms/JMC/CMakeLists.txt b/llvm/lib/Transforms/JMC/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/lib/Transforms/JMC/CMakeLists.txt @@ -0,0 +1,14 @@ +add_llvm_component_library(LLVMJMC + JMCInstrumenter.cpp + + ADDITIONAL_HEADER_DIRS + ${LLVM_MAIN_INCLUDE_DIR}/llvm/Transforms + + DEPENDS + intrinsics_gen + + LINK_COMPONENTS + Core + Support + TransformUtils + ) diff --git a/llvm/test/CodeGen/X86/jmc-instrument.ll b/llvm/test/CodeGen/X86/jmc-instrument.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/jmc-instrument.ll @@ -0,0 +1,29 @@ +; Check that the flag symbol is not full-qualified. +; RUN: llc < %s -enable-jmc-instrument | FileCheck %s + +; CHECK: _w1: +; CHECK: movl $__A85D9D03_x@c, %ecx +; CHECK: calll @__CheckForDebuggerJustMyCode@4 + +target datalayout = "e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32-a:0:32-S32" +target triple = "i386-pc-windows-msvc" + +define void @w1() #0 !dbg !10 { + ret void +} + +attributes #0 = { "target-cpu"="pentium4" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a\\\\") +!7 = !{i32 2, !"CodeView", i32 1} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{!"clang"} +!10 = distinct !DISubprogram(name: "w1", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!31 = !DISubroutineType(types: !32) +!32 = !{null} +!33 = !{} diff --git a/llvm/test/Instrumentation/JustMyCode/jmc-instrument-x86.ll b/llvm/test/Instrumentation/JustMyCode/jmc-instrument-x86.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Instrumentation/JustMyCode/jmc-instrument-x86.ll @@ -0,0 +1,53 @@ +; RUN: opt -jmc-instrument -S < %s | FileCheck %s + +; CHECK: $_JustMyCode_Default = comdat any + +; CHECK: @"_A85D9D03_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !0 +; CHECK: @llvm.used = appending global [1 x i8*] [i8* bitcast (void (i8*)* @_JustMyCode_Default to i8*)], section "llvm.metadata" + +; CHECK: define void @w1() #0 !dbg !10 { +; CHECK: call x86_fastcallcc void @__CheckForDebuggerJustMyCode(i8* inreg noundef @"_A85D9D03_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: declare x86_fastcallcc void @__CheckForDebuggerJustMyCode(i8* inreg noundef) unnamed_addr + +; CHECK: define void @_JustMyCode_Default(i8* inreg noundef %0) unnamed_addr comdat { +; CHECK: ret void +; CHECK: } + +; CHECK: !0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +; CHECK: !1 = distinct !DIGlobalVariable(name: "_A85D9D03_x@c", scope: !2, file: !3, type: !5, isLocal: true, isDefinition: true) +; CHECK: !2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +; CHECK: !3 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a\\\\") +; CHECK: !4 = !{!0} +; CHECK: !5 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char, flags: DIFlagArtificial) +; CHECK: !6 = !{i32 2, !"CodeView", i32 1} +; CHECK: !7 = !{i32 2, !"Debug Info Version", i32 3} +; CHECK: !8 = !{!"clang"} +; CHECK: !9 = !{!"/alternatename:__CheckForDebuggerJustMyCode=_JustMyCode_Default"} +; CHECK: !10 = distinct !DISubprogram(name: "w1", scope: !3, file: !3, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !13) +; CHECK: !11 = !DISubroutineType(types: !12) + +target datalayout = "e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32-a:0:32-S32" +target triple = "i386-pc-windows-msvc" + +define void @w1() #0 !dbg !10 { + ret void +} + +attributes #0 = { "target-cpu"="pentium4" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a\\\\") +!7 = !{i32 2, !"CodeView", i32 1} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{!"clang"} +!10 = distinct !DISubprogram(name: "w1", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!31 = !DISubroutineType(types: !32) +!32 = !{null} +!33 = !{} diff --git a/llvm/test/Instrumentation/JustMyCode/jmc-instrument.ll b/llvm/test/Instrumentation/JustMyCode/jmc-instrument.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Instrumentation/JustMyCode/jmc-instrument.ll @@ -0,0 +1,120 @@ +; RUN: opt -jmc-instrument -mtriple=x86_64-pc-windows-msvc -S < %s | FileCheck %s +; RUN: opt -jmc-instrument -mtriple=aarch64-pc-windows-msvc -S < %s | FileCheck %s +; RUN: opt -jmc-instrument -mtriple=arm-pc-windows-msvc -S < %s | FileCheck %s + +; CHECK: $__JustMyCode_Default = comdat any + +; CHECK: @"__7DF23CF5_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !0 +; CHECK: @"__A85D9D03_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !5 +; CHECK: @llvm.used = appending global [1 x i8*] [i8* bitcast (void (i8*)* @__JustMyCode_Default to i8*)], section "llvm.metadata" + +; CHECK: define void @l1() !dbg !13 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: define void @l2() !dbg !17 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: define void @w1() !dbg !19 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: define void @w2() !dbg !20 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: define void @w3() !dbg !22 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: define void @w4() !dbg !24 { +; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c") +; CHECK: ret void +; CHECK: } + +; CHECK: declare void @__CheckForDebuggerJustMyCode(i8* noundef) unnamed_addr + +; CHECK: define void @__JustMyCode_Default(i8* noundef %0) unnamed_addr comdat { +; CHECK: ret void +; CHECK: } + +; CHECK: !llvm.linker.options = !{!12} + +; CHECK: !0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression()) +; CHECK: !1 = distinct !DIGlobalVariable(name: "__7DF23CF5_x@c", scope: !2, file: !3, type: !8, isLocal: true, isDefinition: true) +; CHECK: !2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None) +; CHECK: !3 = !DIFile(filename: "a/x.c", directory: "/tmp") +; CHECK: !4 = !{!0, !5} +; CHECK: !5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression()) +; CHECK: !6 = distinct !DIGlobalVariable(name: "__A85D9D03_x@c", scope: !2, file: !7, type: !8, isLocal: true, isDefinition: true) +; CHECK: !7 = !DIFile(filename: "./x.c", directory: "C:\\\\a\\\\b\\\\") +; CHECK: !8 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char, flags: DIFlagArtificial) +; CHECK: !9 = !{i32 2, !"CodeView", i32 1} +; CHECK: !10 = !{i32 2, !"Debug Info Version", i32 3} +; CHECK: !11 = !{!"clang"} +; CHECK: !12 = !{!"/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default"} +; CHECK: !13 = distinct !DISubprogram(name: "f", scope: !3, file: !3, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !14 = !DISubroutineType(types: !15) +; CHECK: !15 = !{null} +; CHECK: !16 = !{} +; CHECK: !17 = distinct !DISubprogram(name: "f", scope: !18, file: !18, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !18 = !DIFile(filename: "x.c", directory: "/tmp/a") +; CHECK: !19 = distinct !DISubprogram(name: "f", scope: !7, file: !7, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !20 = distinct !DISubprogram(name: "f", scope: !21, file: !21, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !21 = !DIFile(filename: "./b\\x.c", directory: "C:\\\\a\\\\") +; CHECK: !22 = distinct !DISubprogram(name: "f", scope: !23, file: !23, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !23 = !DIFile(filename: "./b/x.c", directory: "C:\\\\a\\\\") +; CHECK: !24 = distinct !DISubprogram(name: "f", scope: !25, file: !25, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16) +; CHECK: !25 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a") + +; All use the same flag +define void @l1() !dbg !10 { + ret void +} +define void @l2() !dbg !11 { + ret void +} + +; All use the same flag +define void @w1() !dbg !12 { + ret void +} +define void @w2() !dbg !13 { + ret void +} +define void @w3() !dbg !14 { + ret void +} +define void @w4() !dbg !15 { + ret void +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!7, !8} +!llvm.ident = !{!9} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "a/x.c", directory: "/tmp") +!2 = !DIFile(filename: "x.c", directory: "/tmp/a") +!3 = !DIFile(filename: "./x.c", directory: "C:\\\\a\\\\b\\\\") +!4 = !DIFile(filename: "./b\\x.c", directory: "C:\\\\a\\\\") +!5 = !DIFile(filename: "./b/x.c", directory: "C:\\\\a\\\\") +!6 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a") +!7 = !{i32 2, !"CodeView", i32 1} +!8 = !{i32 2, !"Debug Info Version", i32 3} +!9 = !{!"clang"} +!10 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!11 = distinct !DISubprogram(name: "f", scope: !2, file: !2, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!12 = distinct !DISubprogram(name: "f", scope: !3, file: !3, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!13 = distinct !DISubprogram(name: "f", scope: !4, file: !4, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!14 = distinct !DISubprogram(name: "f", scope: !5, file: !5, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!15 = distinct !DISubprogram(name: "f", scope: !6, file: !6, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33) +!31 = !DISubroutineType(types: !32) +!32 = !{null} +!33 = !{} diff --git a/llvm/tools/opt/opt.cpp b/llvm/tools/opt/opt.cpp --- a/llvm/tools/opt/opt.cpp +++ b/llvm/tools/opt/opt.cpp @@ -498,7 +498,8 @@ "generic-to-nvvm", "expandmemcmp", "loop-reduce", "lower-amx-type", "pre-amx-config", "lower-amx-intrinsics", - "polyhedral-info", "replace-with-veclib"}; + "polyhedral-info", "replace-with-veclib", + "jmc-instrument"}; for (const auto &P : PassNamePrefix) if (Pass.startswith(P)) return true; @@ -572,6 +573,7 @@ initializeHardwareLoopsPass(Registry); initializeTypePromotionPass(Registry); initializeReplaceWithVeclibLegacyPass(Registry); + initializeJMCInstrumenterPass(Registry); #ifdef BUILD_EXAMPLES initializeExampleIRTransforms(Registry);