diff --git a/lib/IRGen/IRGenDebugInfo.cpp b/lib/IRGen/IRGenDebugInfo.cpp index d4ce0fbe47bce..0491f15ff50be 100644 --- a/lib/IRGen/IRGenDebugInfo.cpp +++ b/lib/IRGen/IRGenDebugInfo.cpp @@ -215,10 +215,11 @@ class IRGenDebugInfoImpl : public IRGenDebugInfo { /// Return false if we fail to create the right DW_OP_LLVM_fragment operand. bool handleFragmentDIExpr(const SILDIExprOperand &CurDIExprOp, - SmallVectorImpl &Operands); + llvm::DIExpression::FragmentInfo &Fragment); /// Return false if we fail to create the desired !DIExpression. bool buildDebugInfoExpression(const SILDebugVariable &VarInfo, - SmallVectorImpl &Operands); + SmallVectorImpl &Operands, + llvm::DIExpression::FragmentInfo &Fragment); /// Emit a dbg.declare at the current insertion point in Builder. void emitVariableDeclaration(IRBuilder &Builder, @@ -2522,7 +2523,8 @@ void IRGenDebugInfoImpl::emitOutlinedFunction(IRBuilder &Builder, } bool IRGenDebugInfoImpl::handleFragmentDIExpr( - const SILDIExprOperand &CurDIExprOp, SmallVectorImpl &Operands) { + const SILDIExprOperand &CurDIExprOp, + llvm::DIExpression::FragmentInfo &Fragment) { assert(CurDIExprOp.getOperator() == SILDIExprOperator::Fragment); // Expecting a VarDecl that points to a field in an struct auto DIExprArgs = CurDIExprOp.args(); @@ -2554,23 +2556,31 @@ bool IRGenDebugInfoImpl::handleFragmentDIExpr( uint64_t OffsetInBits = Offset->getUniqueInteger().getLimitedValue() * SizeOfByte; - // Translate to LLVM dbg intrinsic operands - Operands.push_back(llvm::dwarf::DW_OP_LLVM_fragment); - Operands.push_back(OffsetInBits); - Operands.push_back(SizeInBits); + // Translate to DW_OP_LLVM_fragment operands + Fragment = {SizeInBits, OffsetInBits}; return true; } bool IRGenDebugInfoImpl::buildDebugInfoExpression( - const SILDebugVariable &VarInfo, SmallVectorImpl &Operands) { + const SILDebugVariable &VarInfo, SmallVectorImpl &Operands, + llvm::DIExpression::FragmentInfo &Fragment) { assert(VarInfo.DIExpr && "SIL debug info expression not found"); +#ifndef NDEBUG + bool HasFragment = VarInfo.DIExpr.hasFragment(); +#endif + const auto &DIExpr = VarInfo.DIExpr; for (const SILDIExprOperand &ExprOperand : DIExpr.operands()) { switch (ExprOperand.getOperator()) { case SILDIExprOperator::Fragment: - if (!handleFragmentDIExpr(ExprOperand, Operands)) + assert(HasFragment && "Fragment must be the last part of a DIExpr"); +#ifndef NDEBUG + // Trigger the assert above if we have more than one fragment expression. + HasFragment = false; +#endif + if (!handleFragmentDIExpr(ExprOperand, Fragment)) return false; break; case SILDIExprOperator::Dereference: @@ -2677,24 +2687,54 @@ void IRGenDebugInfoImpl::emitVariableDeclaration( LocalVarCache.insert({Key, llvm::TrackingMDNodeRef(Var)}); } - auto appendDIExpression = - [&VarInfo, this](llvm::DIExpression *DIExpr) -> llvm::DIExpression * { - if (VarInfo.DIExpr) { - llvm::SmallVector Operands; - if (!buildDebugInfoExpression(VarInfo, Operands)) - return nullptr; - if (Operands.size()) - return llvm::DIExpression::append(DIExpr, Operands); - } - return DIExpr; - }; - // Running variables for the current/previous piece. bool IsPiece = Storage.size() > 1; uint64_t SizeOfByte = CI.getTargetInfo().getCharWidth(); unsigned AlignInBits = SizeOfByte; unsigned OffsetInBits = 0; unsigned SizeInBits = 0; + llvm::DIExpression::FragmentInfo Fragment = {0, 0}; + + auto appendDIExpression = + [&VarInfo, this](llvm::DIExpression *DIExpr, + llvm::DIExpression::FragmentInfo PieceFragment) + -> llvm::DIExpression * { + if (!VarInfo.DIExpr) { + if (!PieceFragment.SizeInBits) + return DIExpr; + + return llvm::DIExpression::createFragmentExpression( + DIExpr, PieceFragment.OffsetInBits, PieceFragment.SizeInBits) + .getValueOr(nullptr); + } + + llvm::SmallVector Operands; + llvm::DIExpression::FragmentInfo VarFragment = {0, 0}; + if (!buildDebugInfoExpression(VarInfo, Operands, VarFragment)) + return nullptr; + + if (!Operands.empty()) + DIExpr = llvm::DIExpression::append(DIExpr, Operands); + + // Add the fragment of the SIL variable. + if (VarFragment.SizeInBits) + DIExpr = llvm::DIExpression::createFragmentExpression( + DIExpr, VarFragment.OffsetInBits, VarFragment.SizeInBits) + .getValueOr(nullptr); + + if (!DIExpr) + return nullptr; + + // When the fragment of the SIL variable is further split into other + // fragments (PieceFragment), merge them into one DW_OP_LLVM_Fragment + // expression. + if (PieceFragment.SizeInBits) + return llvm::DIExpression::createFragmentExpression( + DIExpr, PieceFragment.OffsetInBits, PieceFragment.SizeInBits) + .getValueOr(nullptr); + + return DIExpr; + }; for (llvm::Value *Piece : Storage) { SmallVector Operands; @@ -2723,16 +2763,12 @@ void IRGenDebugInfoImpl::emitVariableDeclaration( } #endif - // Add the piece DWARF expression. - Operands.push_back(llvm::dwarf::DW_OP_LLVM_fragment); - Operands.push_back(OffsetInBits); - Operands.push_back(SizeInBits); + // Add the piece DW_OP_LLVM_fragment operands + Fragment.OffsetInBits = OffsetInBits; + Fragment.SizeInBits = SizeInBits; } llvm::DIExpression *DIExpr = DBuilder.createExpression(Operands); - // DW_OP_LLVM_fragment must be the last part of an DIExpr - // so we can't append more if IsPiece is true. - if (!IsPiece) - DIExpr = appendDIExpression(DIExpr); + DIExpr = appendDIExpression(DIExpr, Fragment); if (DIExpr) emitDbgIntrinsic( Builder, Piece, Var, DIExpr, DInstLine, DInstLoc.column, Scope, DS, @@ -2742,7 +2778,9 @@ void IRGenDebugInfoImpl::emitVariableDeclaration( // Emit locationless intrinsic for variables that were optimized away. if (Storage.empty()) { - if (auto *DIExpr = appendDIExpression(DBuilder.createExpression())) + llvm::DIExpression::FragmentInfo NoFragment = {0, 0}; + if (auto *DIExpr = + appendDIExpression(DBuilder.createExpression(), NoFragment)) emitDbgIntrinsic(Builder, llvm::ConstantInt::get(IGM.Int64Ty, 0), Var, DIExpr, DInstLine, DInstLoc.column, Scope, DS, Indirection == CoroDirectValue || diff --git a/test/IRGen/debug_fragment_merge.sil b/test/IRGen/debug_fragment_merge.sil new file mode 100644 index 0000000000000..e794007d99bb8 --- /dev/null +++ b/test/IRGen/debug_fragment_merge.sil @@ -0,0 +1,65 @@ +// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-ir -disable-llvm-optzns -O -g | %FileCheck %s + +// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC1:[0-9]+]] +// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC1]] +// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC1]] +// CHECK-DAG: llvm.dbg.value{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC1]] + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +protocol External { + func use(str: String) + func decode(_: T.Type) -> T +} + +struct Data { + @_hasStorage var a: String { get set } + @_hasStorage var b: String { get set } + init(a: String, b: String) +} + +sil_scope 9 { loc "debug_fragment_merge.swift":14:6 parent @$s20debug_fragment_merge4test4cond8externalySi_AA8External_ptYaF : $@convention(thin) (Int, @in_guaranteed any External) -> () } +sil_scope 10 { loc "debug_fragment_merge.swift":16:7 parent 9 } +sil_scope 11 { loc "debug_fragment_merge.swift":17:3 parent 10 } +sil_scope 12 { loc "debug_fragment_merge.swift":19:3 parent 11 } +sil_scope 13 { loc "debug_fragment_merge.swift":19:12 parent 12 } +sil_scope 14 { loc "debug_fragment_merge.swift":21:3 parent 11 } +sil_scope 15 { loc "debug_fragment_merge.swift":21:12 parent 14 } + +sil @$side_effect : $@convention(thin) (@owned String) -> () +sil @$side_effect_data : $@convention(thin) (@owned Data) -> () +sil @$cond : $@convention(thin) () -> (Builtin.Int1) + +// test(cond:external:), loc "debug_fragment_merge.swift":14:6, scope 9 +sil hidden @$s20debug_fragment_merge4test4cond8externalySi_AA8External_ptYaF : $@convention(thin) (Int, @in_guaranteed any External) -> () { +[%1: read v**, write v**, copy v**, destroy v**] +[global: read,write,copy,destroy,allocate,deinit_barrier] +bb0(%0 : $Int, %1 : $*any External): + br bb1 // cond_br %cond, bb1, bb2 + +bb1: // Preds: bb0 + %10 = open_existential_addr immutable_access %1 : $*any External to $*@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, loc "debug_fragment_merge.swift":19:28, scope 12 // users: %14, %14, %13 + %11 = alloc_stack $Data, loc * "debug_fragment_merge.swift":19:28, scope 13 // users: %17, %15, %21, %14 + %12 = metatype $@thick Data.Type, loc "debug_fragment_merge.swift":19:35, scope 13 // user: %14 + %13 = witness_method $@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, #External.decode : (Self) -> (T.Type) -> T, %10 : $*@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self : $@convention(witness_method: External) <τ_0_0 where τ_0_0 : External><τ_1_0> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out τ_1_0, loc "debug_fragment_merge.swift":19:28, scope 13 // type-defs: %10; user: %14 + %14 = apply %13<@opened("EDD72648-0EA0-11EE-9925-E91C6F300971", any External) Self, Data>(%11, %12, %10) : $@convention(witness_method: External) <τ_0_0 where τ_0_0 : External><τ_1_0> (@thick τ_1_0.Type, @in_guaranteed τ_0_0) -> @out τ_1_0, loc "debug_fragment_merge.swift":19:28, scope 13 // type-defs: %10 + %15 = struct_element_addr %11 : $*Data, #Data.a, loc "debug_fragment_merge.swift":19:28, scope 13 // user: %16 + %16 = load %15 : $*String, loc "debug_fragment_merge.swift":19:28, scope 13 // users: %19, %22 + %17 = struct_element_addr %11 : $*Data, #Data.b, loc "debug_fragment_merge.swift":19:28, scope 13 // user: %18 + %18 = load %17 : $*String, loc "debug_fragment_merge.swift":19:28, scope 13 // users: %22, %20 + debug_value %16 : $String, let, (name "data", loc "debug_fragment_merge.swift":16:7, scope 10), type $*Data, expr op_fragment:#Data.a, loc "debug_fragment_merge.swift":19:17, scope 12 // id: %19 + debug_value %18 : $String, let, (name "data", loc "debug_fragment_merge.swift":16:7, scope 10), type $*Data, expr op_fragment:#Data.b, loc "debug_fragment_merge.swift":19:17, scope 12 // id: %20 + dealloc_stack %11 : $*Data, loc "debug_fragment_merge.swift":19:44, scope 13 // id: %21 + br bb3(%18 : $String, %16 : $String), loc * "debug_fragment_merge.swift":19:44, scope 13 // id: %22 + +bb3(%36 : $String, %37 : $String): // Preds: bb2 bb1 + %side_effect_ref = function_ref @$side_effect : $@convention(thin) (@owned String) -> () + apply %side_effect_ref(%36) : $@convention(thin) (@owned String) -> () + apply %side_effect_ref(%37) : $@convention(thin) (@owned String) -> () + %44 = tuple () + return %44 : $() +} diff --git a/test/IRGen/debug_fragment_merge.swift b/test/IRGen/debug_fragment_merge.swift new file mode 100644 index 0000000000000..ae373691ac62f --- /dev/null +++ b/test/IRGen/debug_fragment_merge.swift @@ -0,0 +1,40 @@ +// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-sil -O -g | %FileCheck %s --check-prefix CHECK-SIL +// RUN: %target-swift-frontend -disable-availability-checking -primary-file %s -emit-ir -disable-llvm-optzns -O -g | %FileCheck %s + +protocol External { + func use(str: String); + func decode(_: T.Type) -> T +} + +struct Data { + var a: String + var b: String +} + +func test(cond: Int, external: External) async { + // CHECK-DAG: ![[VAR:[0-9]+]] = !DILocalVariable(name: "data", scope: !{{[0-9]+}}, file: !{{[0-9]+}}, line: [[# @LINE + 1]], type: !{{[0-9]+}}) + let data: Data + switch cond { + // CHECK-DAG: ![[LOC1:[0-9]+]] = !DILocation(line: [[# @LINE + 1]], column: 12, scope: !{{.*}}) + case 42: data = external.decode(Data.self) + // CHECK-DAG: ![[LOC2:[0-9]+]] = !DILocation(line: [[# @LINE + 1]], column: 12, scope: !{{.*}}) + default: data = external.decode(Data.self) + } + external.use(str: data.a) + external.use(str: data.b) +} + +// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.a +// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.b +// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.a +// CHECK-SIL: debug_value %{{.*}} : $String, let, (name "data", {{.*}}), type $*Data, expr op_fragment:#Data.b + +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR:[0-9]+]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC1]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC1]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC1]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC1]] +// +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 192, 64){{.*}} !dbg ![[LOC2]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 128, 64){{.*}} !dbg ![[LOC2]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 64, 64){{.*}} !dbg ![[LOC2]] +// CHECK-DAG: llvm.dbg.declare{{.*}} metadata ![[VAR]], metadata !DIExpression(DW_OP_LLVM_fragment, 0, 64){{.*}} !dbg ![[LOC2]]