ychen created this revision.
ychen added reviewers: rnk, aganea, hans.
Herald added subscribers: ormris, dexonsmith, dang, pengfei, hiraditya, mgorny.
ychen requested review of this revision.
Herald added projects: clang, LLVM.
Herald added subscribers: llvm-commits, cfe-commits.
The introduction and some examples are on this page:
https://devblogs.microsoft.com/cppblog/announcing-jmc-stepping-in-visual-studio/
The `/JMC` flag enables these instrumentations:
- Insert at the beginning of every function immediately after the prologue with
a call to `void __fastcall __CheckForDebuggerJustMyCode(unsigned char
*JMC_flag)`. The argument for `__CheckForDebuggerJustMyCode` is the address of
a boolean global variable (the global variable is initialized to 1) with the
name convention `__<hash>_<filename>`. All such global variables are placed in
the `.msvcjmc` section.
- The `<hash>` part of `__<hash>_<filename>` has a one-to-one mapping with a
directory path. MSVC uses some unknown hashing function. Here I used DJB.
- Add a dummy/empty COMDAT function __JustMyCode_Default.
- Add "/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default" link
option via ".drectve" section. This is to prevent failure in case
`__CheckForDebuggerJustMyCode` is not provided during linking.
Implementation:
All the instrumentations are implemented in a IR codegen pass. The
pass is placed immediately before CodeGenPrepare pass. This is to not
interfere with mid-end optimizations and make the instrumentation
target-independent (I'm still working on a ELF port in a separate patch).
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D118428
Files:
clang/docs/ReleaseNotes.rst
clang/include/clang/Basic/CodeGenOptions.def
clang/include/clang/Basic/DiagnosticDriverKinds.td
clang/include/clang/Driver/Options.td
clang/lib/CodeGen/BackendUtil.cpp
clang/lib/Driver/ToolChains/Clang.cpp
clang/test/Driver/cl-options.c
llvm/include/llvm/CodeGen/CommandFlags.h
llvm/include/llvm/CodeGen/Passes.h
llvm/include/llvm/InitializePasses.h
llvm/include/llvm/LinkAllPasses.h
llvm/include/llvm/Target/TargetOptions.h
llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
llvm/lib/CodeGen/CMakeLists.txt
llvm/lib/CodeGen/CodeGen.cpp
llvm/lib/CodeGen/CommandFlags.cpp
llvm/lib/CodeGen/JMCInstrumenter.cpp
llvm/lib/CodeGen/TargetInstrInfo.cpp
llvm/lib/CodeGen/TargetPassConfig.cpp
llvm/test/CodeGen/X86/jmc-instrument-32bit.ll
llvm/test/CodeGen/X86/jmc-instrument.ll
llvm/tools/opt/opt.cpp
Index: llvm/tools/opt/opt.cpp
===================================================================
--- llvm/tools/opt/opt.cpp
+++ 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);
Index: llvm/test/CodeGen/X86/jmc-instrument.ll
===================================================================
--- /dev/null
+++ llvm/test/CodeGen/X86/jmc-instrument.ll
@@ -0,0 +1,148 @@
+; RUN: opt -jmc-instrument -S < %s | FileCheck %s
+; RUN: llc < %s -enable-jmc-instrument | FileCheck %s -check-prefix CODEGEN
+
+; 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 [3 x i8*] [i8* @"__7DF23CF5_x@c", i8* @"__A85D9D03_x@c", i8* bitcast (void (i8*)* @__JustMyCode_Default to i8*)], section "llvm.metadata"
+
+; CHECK: define void @l1() #0 !dbg !13 {
+; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c")
+; CHECK: ret void
+; CHECK: }
+
+; CHECK: define void @l2() #0 !dbg !17 {
+; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c")
+; CHECK: ret void
+; CHECK: }
+
+; CHECK: define void @w1() #0 !dbg !19 {
+; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
+; CHECK: ret void
+; CHECK: }
+
+; CHECK: define void @w2() #0 !dbg !20 {
+; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
+; CHECK: ret void
+; CHECK: }
+
+; CHECK: define void @w3() #0 !dbg !22 {
+; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
+; CHECK: ret void
+; CHECK: }
+
+; CHECK: define void @w4() #0 !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: attributes {{.*}} "jmc-instrumented"
+
+; 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")
+
+; CODEGEN: l1:
+; CODEGEN: leaq __7DF23CF5_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: l2:
+; CODEGEN: leaq __7DF23CF5_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: w1:
+; CODEGEN: leaq __A85D9D03_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: w2:
+; CODEGEN: leaq __A85D9D03_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: w3:
+; CODEGEN: leaq __A85D9D03_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: w4:
+; CODEGEN: leaq __A85D9D03_x@c(%rip), %rcx
+; CODEGEN: callq __CheckForDebuggerJustMyCode
+; CODEGEN: __JustMyCode_Default:
+; CODEGEN: retq
+
+
+target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc"
+
+; All use the same flag
+define void @l1() #0 !dbg !10 {
+ ret void
+}
+define void @l2() #0 !dbg !11 {
+ ret void
+}
+
+; All use the same flag
+define void @w1() #0 !dbg !12 {
+ ret void
+}
+define void @w2() #0 !dbg !13 {
+ ret void
+}
+define void @w3() #0 !dbg !14 {
+ ret void
+}
+define void @w4() #0 !dbg !15 {
+ ret void
+}
+
+attributes #0 = { "target-cpu"="x86-64" "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: "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 = !{}
Index: llvm/test/CodeGen/X86/jmc-instrument-32bit.ll
===================================================================
--- /dev/null
+++ llvm/test/CodeGen/X86/jmc-instrument-32bit.ll
@@ -0,0 +1,64 @@
+; RUN: opt -jmc-instrument -S < %s | FileCheck %s
+; RUN: llc < %s -enable-jmc-instrument | FileCheck %s -check-prefix CODEGEN
+
+; 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 [2 x i8*] [i8* @"_A85D9D03_x@c", 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: attributes {{.*}} "jmc-instrumented"
+
+; 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)
+
+; CODEGEN: _w1:
+; CODEGEN: movl $__A85D9D03_x@c, %ecx
+; CODEGEN: calll @__CheckForDebuggerJustMyCode@4
+; CODEGEN: __JustMyCode_Default:
+; CODEGEN: retl
+
+
+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"
+
+; All use the same flag
+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 = !{}
Index: llvm/lib/CodeGen/TargetPassConfig.cpp
===================================================================
--- llvm/lib/CodeGen/TargetPassConfig.cpp
+++ 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();
Index: llvm/lib/CodeGen/TargetInstrInfo.cpp
===================================================================
--- llvm/lib/CodeGen/TargetInstrInfo.cpp
+++ llvm/lib/CodeGen/TargetInstrInfo.cpp
@@ -1029,6 +1029,8 @@
if (MI.getOpcode() == TargetOpcode::INLINEASM_BR)
return true;
+ // if (MI.isCall() && callee is "__CheckForDebuggerJustMyCode");
+
// Don't attempt to schedule around any instruction that defines
// a stack-oriented pointer, as it's unlikely to be profitable. This
// saves compile time, because it doesn't require every single
Index: llvm/lib/CodeGen/JMCInstrumenter.cpp
===================================================================
--- /dev/null
+++ llvm/lib/CodeGen/JMCInstrumenter.cpp
@@ -0,0 +1,209 @@
+//===- 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
+// - (TODO) calls to __CheckForDebuggerJustMyCode should be a scheduling
+// boundary
+//
+//===----------------------------------------------------------------------===//
+
+#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/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 {
+
+std::string getFlagName(DISubprogram &SP, bool Is32Bit) {
+ // Best effort path normalization. This is to guarantee an unique flag symbol
+ // is produced for the same directory. Ideally maybe it's better to call
+ // `realpath` on Linux and `GetFullPathName()` on Windows
+ // (https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#path-normalization).
+ 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 __<hash>_<file name> with '.' in
+ // <file name> replaced with '@'. For example C:\file.any.c would have a flag
+ // __D032E919_file@any@c. The hashing function or the naming convention match
+ // MSVC's behavior however the match is not required to make JMC work.
+
+ std::string Suffix;
+ for (auto C : sys::path::filename(FilePath))
+ Suffix.push_back(C == '.' ? '@' : C);
+
+ sys::path::remove_filename(FilePath);
+ return (Is32Bit ? "_" : "__") +
+ 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, const char *CheckFunctionName,
+ bool Is32Bit) {
+ LLVMContext &Ctx = M.getContext();
+ const char *DefaultCheckFunctionName =
+ Is32Bit ? "_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 (Is32Bit)
+ 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 options /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);
+
+ // Micro-optimization: set the necessary function information here instead of
+ // when creating it.
+ Function *Decl = M.getFunction(CheckFunctionName);
+ assert(Decl);
+ Decl->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
+ Decl->addParamAttr(0, Attribute::NoUndef);
+ if (Is32Bit) {
+ Decl->setCallingConv(CallingConv::X86_FastCall);
+ Decl->addParamAttr(0, Attribute::InReg);
+ }
+}
+} // namespace
+
+bool JMCInstrumenter::runOnModule(Module &M) {
+ bool Changed = false;
+ const char *EntryAttr = "jmc-instrumented";
+ const char *CheckFunctionName = "__CheckForDebuggerJustMyCode";
+ bool Is32Bit = Triple(M.getTargetTriple()).getArch() == Triple::x86;
+
+ for (auto &F : M) {
+ if (F.isDeclaration())
+ continue;
+ auto *SP = F.getSubprogram();
+ if (!SP)
+ continue;
+
+ // If the attribute is specified, insert instrumentation and then "consume"
+ // the attribute so that it's not inserted again if the pass should happen
+ // to run later for some reason.
+ if (F.hasFnAttribute(EntryAttr))
+ continue;
+
+ LLVMContext &Ctx = F.getContext();
+ FunctionCallee Fn =
+ M.getOrInsertFunction(CheckFunctionName, getCheckFunctionType(Ctx));
+ // FIXME: caching the flag name to prevent repetitive hashing?
+ std::string FlagName = getFlagName(*SP, Is32Bit);
+ IntegerType *FlagTy = Type::getInt8Ty(Ctx);
+ Constant *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.
+ // Make it static linkage. One of the definition would win, the rest are
+ // dead in .msvcjmc section, but it is supposed be very cheap.
+ 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);
+ appendToUsed(M, {GV});
+ return GV;
+ });
+ auto *CI =
+ CallInst::Create(Fn, {Flag}, "", &*F.begin()->getFirstInsertionPt());
+ CI->addParamAttr(0, Attribute::NoUndef);
+ if (Is32Bit) {
+ CI->setCallingConv(CallingConv::X86_FastCall);
+ CI->addParamAttr(0, Attribute::InReg);
+ }
+
+ F.addFnAttr(EntryAttr);
+ Changed = true;
+ }
+ if (!Changed)
+ return false;
+
+ createDefaultCheckFunction(M, CheckFunctionName, Is32Bit);
+ return true;
+}
Index: llvm/lib/CodeGen/CommandFlags.cpp
===================================================================
--- llvm/lib/CodeGen/CommandFlags.cpp
+++ llvm/lib/CodeGen/CommandFlags.cpp
@@ -94,6 +94,7 @@
CGOPT(bool, XRayOmitFunctionIndex)
CGOPT(bool, DebugStrictDwarf)
CGOPT(unsigned, AlignLoops)
+CGOPT(bool, EnableJMCInstrument)
codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() {
#define CGBINDOPT(NAME) \
@@ -457,6 +458,12 @@
cl::desc("Default alignment for loops"));
CGBINDOPT(AlignLoops);
+ static cl::opt<bool> EnableJMCInstrument(
+ "enable-jmc-instrument",
+ cl::desc("Instrument functions with a call to __CheckForDebuggerJustMyCode"),
+ cl::init(false));
+ CGBINDOPT(EnableJMCInstrument);
+
#undef CGBINDOPT
mc::RegisterMCTargetOptionsFlags();
@@ -531,6 +538,7 @@
Options.XRayOmitFunctionIndex = getXRayOmitFunctionIndex();
Options.DebugStrictDwarf = getDebugStrictDwarf();
Options.LoopAlignment = getAlignLoops();
+ Options.JMCInstrument = getEnableJMCInstrument();
Options.MCOptions = mc::InitMCTargetOptionsFromFlags();
Index: llvm/lib/CodeGen/CodeGen.cpp
===================================================================
--- llvm/lib/CodeGen/CodeGen.cpp
+++ llvm/lib/CodeGen/CodeGen.cpp
@@ -50,6 +50,7 @@
initializeIndirectBrExpandPassPass(Registry);
initializeInterleavedLoadCombinePass(Registry);
initializeInterleavedAccessPass(Registry);
+ initializeJMCInstrumenterPass(Registry);
initializeLiveDebugValuesPass(Registry);
initializeLiveDebugVariablesPass(Registry);
initializeLiveIntervalsPass(Registry);
Index: llvm/lib/CodeGen/CMakeLists.txt
===================================================================
--- llvm/lib/CodeGen/CMakeLists.txt
+++ llvm/lib/CodeGen/CMakeLists.txt
@@ -75,6 +75,7 @@
InterleavedAccessPass.cpp
InterleavedLoadCombinePass.cpp
IntrinsicLowering.cpp
+ JMCInstrumenter.cpp
LatencyPriorityQueue.cpp
LazyMachineBlockFrequencyInfo.cpp
LexicalScopes.cpp
Index: llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
===================================================================
--- llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
+++ llvm/lib/CodeGen/AsmPrinter/CodeViewDebug.cpp
@@ -3355,14 +3355,18 @@
if (const auto *MemberDecl = dyn_cast_or_null<DIDerivedType>(
DIGV->getRawStaticDataMemberDeclaration()))
Scope = MemberDecl->getScope();
+
+ const GlobalVariable *GV = CVGV.GVInfo.dyn_cast<const GlobalVariable *>();
+
// 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<const GlobalVariable *>()) {
+ 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);
Index: llvm/include/llvm/Target/TargetOptions.h
===================================================================
--- llvm/include/llvm/Target/TargetOptions.h
+++ 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.
Index: llvm/include/llvm/LinkAllPasses.h
===================================================================
--- llvm/include/llvm/LinkAllPasses.h
+++ 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();
Index: llvm/include/llvm/InitializePasses.h
===================================================================
--- llvm/include/llvm/InitializePasses.h
+++ 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&);
Index: llvm/include/llvm/CodeGen/Passes.h
===================================================================
--- llvm/include/llvm/CodeGen/Passes.h
+++ 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
Index: llvm/include/llvm/CodeGen/CommandFlags.h
===================================================================
--- llvm/include/llvm/CodeGen/CommandFlags.h
+++ llvm/include/llvm/CodeGen/CommandFlags.h
@@ -140,6 +140,8 @@
unsigned getAlignLoops();
+bool getEnableJMCInstrument();
+
/// Create this object with static storage to register codegen-related command
/// line options.
struct RegisterCodeGenFlags {
Index: clang/test/Driver/cl-options.c
===================================================================
--- clang/test/Driver/cl-options.c
+++ 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,13 @@
// 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 '/Zi or /Z7'; option ignored
+
+// RUN: %clang_cl /JMC /c -### -- %s 2>&1 | FileCheck %s --check-prefix JMC
+// JMC-NOT: -fjmc
+
+// RUN: %clang_cl /JMC /Z7 /c -### -- %s 2>&1 | FileCheck %s --check-prefix JMCZ7
+// JMCZ7: -fjmc
+
void f() { }
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ clang/lib/Driver/ToolChains/Clang.cpp
@@ -7434,19 +7434,32 @@
CmdArgs.push_back(Args.MakeArgString(Twine(LangOptions::SSPStrong)));
}
+ const Driver &D = getToolChain().getDriver();
+
// Emit CodeView if -Z7 or -gline-tables-only are present.
+ bool EnabledZ7 = false;
if (Arg *DebugInfoArg = Args.getLastArg(options::OPT__SLASH_Z7,
options::OPT_gline_tables_only)) {
*EmitCodeView = true;
- if (DebugInfoArg->getOption().matches(options::OPT__SLASH_Z7))
+ if (DebugInfoArg->getOption().matches(options::OPT__SLASH_Z7)) {
+ EnabledZ7 = true;
*DebugInfoKind = codegenoptions::DebugInfoConstructor;
- else
+ } else {
*DebugInfoKind = codegenoptions::DebugLineTablesOnly;
+ }
} else {
*EmitCodeView = false;
}
- 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 (EnabledZ7)
+ CmdArgs.push_back("-fjmc");
+ else
+ D.Diag(clang::diag::warn_drv_jmc_requires_z7);
+ }
+
EHFlags EH = parseClangCLEHFlags(D, Args);
if (!isNVPTX && (EH.Synch || EH.Asynch)) {
if (types::isCXX(InputType))
Index: clang/lib/CodeGen/BackendUtil.cpp
===================================================================
--- clang/lib/CodeGen/BackendUtil.cpp
+++ 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;
}
Index: clang/include/clang/Driver/Options.td
===================================================================
--- clang/include/clang/Driver/Options.td
+++ clang/include/clang/Driver/Options.td
@@ -1441,6 +1441,9 @@
HelpText<"Do not elide types when printing diagnostics">,
MarshallingInfoNegativeFlag<DiagnosticOpts<"ElideType">>;
def feliminate_unused_debug_symbols : Flag<["-"], "feliminate-unused-debug-symbols">, Group<f_Group>;
+def fjmc : Flag<["-"], "fjmc">, Group<f_Group>,Flags<[CC1Option]>,
+ HelpText<"Enable native C/C++ Just My Code debugging">,
+ MarshallingInfoFlag<CodeGenOpts<"JMCInstrument">>;
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<f_Group>, Flags<[CC1Option]>,
@@ -6324,6 +6327,10 @@
def _SLASH_imsvc : CLJoinedOrSeparate<"imsvc">,
HelpText<"Add <dir> to system include search path, as if in %INCLUDE%">,
MetaVarName<"<dir>">;
+def _SLASH_JMC : CLFlag<"JMC">,
+ HelpText<"Enable native C/C++ Just My Code debugging">;
+def _SLASH_JMC_ : CLFlag<"JMC-">,
+ HelpText<"Disable native C/C++ 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">;
Index: clang/include/clang/Basic/DiagnosticDriverKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ 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_z7 : Warning<
+ "/JMC requires '/Zi or /Z7'; option ignored">,
+ InGroup<OptionIgnored>;
}
Index: clang/include/clang/Basic/CodeGenOptions.def
===================================================================
--- clang/include/clang/Basic/CodeGenOptions.def
+++ 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.
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -151,6 +151,13 @@
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, and
+ equivalent -cc1 flag ``-fjmc``. ``/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
---------------------------
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits