Skip to content

Commit 9835aa0

Browse files
Merge pull request #9403 from felipepiovezan/felipe/entry_val_as_cfa
[lldb][swift] Evaluate entry_value(async_reg) in terms of CFA
2 parents 67f1fdc + bbc1484 commit 9835aa0

File tree

3 files changed

+121
-113
lines changed

3 files changed

+121
-113
lines changed

lldb/source/Expression/DWARFExpression.cpp

Lines changed: 76 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,74 @@ bool DWARFExpression::LinkThreadLocalStorage(
533533
return true;
534534
}
535535

536+
/// Returns true if \c opcodes contains the opcode for the register used for the
537+
/// Swift Async Context (x22 for aarch64, r14 for x86-64). It must also not
538+
/// contain any other opcodes.
539+
static bool IsAsyncRegCtxExpr(DataExtractor &opcodes,
540+
llvm::Triple::ArchType triple) {
541+
offset_t offset = 0;
542+
const uint8_t op_async_ctx_reg = opcodes.GetU8(&offset);
543+
if (opcodes.BytesLeft(offset) != 0)
544+
return false;
545+
546+
// FIXME: could this be exposed through the ABI/Language plugins?
547+
return (triple == llvm::Triple::x86_64 && op_async_ctx_reg == DW_OP_reg14) ||
548+
(triple == llvm::Triple::aarch64 && op_async_ctx_reg == DW_OP_reg22);
549+
}
550+
551+
/// If \c opcodes contain the location of asynchronous contexts in Swift,
552+
/// evaluates DW_OP_call_frame_cfa, returns its result, and updates
553+
/// \c current_offset. Otherwise, does nothing. This is possible because, for
554+
/// async frames, the language unwinder treats the asynchronous context as the
555+
/// CFA of the frame.
556+
static llvm::Expected<Value> SwiftAsyncEvaluate_DW_OP_entry_value(
557+
ExecutionContext &exe_ctx, StackFrame &current_frame,
558+
const DWARFUnit *dwarf_cu, Function &func, const DataExtractor &opcodes,
559+
offset_t &current_offset) {
560+
auto func_name = func.GetMangled().GetMangledName();
561+
if (!SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(func_name))
562+
return llvm::createStringError(
563+
"SwiftAsyncEvaluate_DW_OP_entry_value: not an async function");
564+
565+
offset_t new_offset = current_offset;
566+
const uint32_t subexpr_len = opcodes.GetULEB128(&new_offset);
567+
const void *subexpr_data = opcodes.GetData(&new_offset, subexpr_len);
568+
if (!subexpr_data)
569+
return llvm::createStringError(
570+
"SwiftAsyncEvaluate_DW_OP_entry_value: failed to extract subexpr");
571+
572+
DataExtractor subexpr_extractor(
573+
subexpr_data, subexpr_len, opcodes.GetByteOrder(),
574+
opcodes.GetAddressByteSize(), opcodes.getTargetByteSize());
575+
if (!IsAsyncRegCtxExpr(subexpr_extractor,
576+
exe_ctx.GetTargetRef().GetArchitecture().GetMachine()))
577+
return llvm::createStringError("SwiftAsyncEvaluate_DW_OP_entry_value: "
578+
"missing async context register opcode");
579+
580+
// Q funclets require an extra level of indirection.
581+
if (SwiftLanguageRuntime::IsSwiftAsyncAwaitResumePartialFunctionSymbol(
582+
func_name)) {
583+
const uint8_t maybe_op_deref = opcodes.GetU8(&new_offset);
584+
if (maybe_op_deref != DW_OP_deref)
585+
return llvm::createStringError("SwiftAsyncEvaluate_DW_OP_entry_value: "
586+
"missing DW_OP_deref in Q funclet");
587+
}
588+
589+
static const uint8_t cfa_opcode = DW_OP_call_frame_cfa;
590+
DataExtractor cfa_expr_data(&cfa_opcode, 1, opcodes.GetByteOrder(),
591+
opcodes.GetAddressByteSize(),
592+
opcodes.getTargetByteSize());
593+
DWARFExpressionList cfa_expr(func.CalculateSymbolContextModule(),
594+
cfa_expr_data, dwarf_cu);
595+
llvm::Expected<Value> maybe_result = cfa_expr.Evaluate(
596+
&exe_ctx, current_frame.GetRegisterContext().get(), LLDB_INVALID_ADDRESS,
597+
/*initial_value_ptr=*/nullptr,
598+
/*object_address_ptr=*/nullptr);
599+
if (maybe_result)
600+
current_offset = new_offset;
601+
return maybe_result;
602+
}
603+
536604
static llvm::Error
537605
Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
538606
ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
@@ -630,20 +698,19 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
630698
if (!current_func)
631699
return llvm::createStringError("no current function");
632700

701+
if (llvm::Expected<Value> result = SwiftAsyncEvaluate_DW_OP_entry_value(
702+
*exe_ctx, *current_frame, dwarf_cu, *current_func, opcodes,
703+
opcode_offset)) {
704+
stack.push_back(*result);
705+
return llvm::Error::success();
706+
} else
707+
LLDB_LOG_ERROR(log, result.takeError(), "{0}");
708+
633709
CallEdge *call_edge = nullptr;
634710
ModuleList &modlist = target.GetImages();
635711
ExecutionContext parent_exe_ctx = *exe_ctx;
636712
parent_exe_ctx.SetFrameSP(parent_frame);
637713
Function *parent_func = nullptr;
638-
#ifdef LLDB_ENABLE_SWIFT
639-
// Swift async function arguments are represented relative to a
640-
// DW_OP_entry_value that fetches the async context register. This
641-
// register is known to the unwinder and can always be restored
642-
// therefore it is not necessary to match up a call site parameter
643-
// with it.
644-
auto fn_name = current_func->GetMangled().GetMangledName().GetStringRef();
645-
if (!SwiftLanguageRuntime::IsAnySwiftAsyncFunctionSymbol(fn_name)) {
646-
#endif
647714

648715
parent_func =
649716
parent_frame->GetSymbolContext(eSymbolContextFunction).function;
@@ -678,25 +745,16 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
678745
if (!call_edge)
679746
return llvm::createStringError("no unambiguous edge from parent "
680747
"to current function");
681-
#ifdef LLDB_ENABLE_SWIFT
682-
}
683-
#endif
684748

685749
// 3. Attempt to locate the DW_OP_entry_value expression in the set of
686750
// available call site parameters. If found, evaluate the corresponding
687751
// parameter in the context of the parent frame.
688752
const uint32_t subexpr_len = opcodes.GetULEB128(&opcode_offset);
689-
#ifdef LLDB_ENABLE_SWIFT
690-
lldb::offset_t subexpr_offset = opcode_offset;
691-
#endif
692753
const void *subexpr_data = opcodes.GetData(&opcode_offset, subexpr_len);
693754
if (!subexpr_data)
694755
return llvm::createStringError("subexpr could not be read");
695756

696757
const CallSiteParameter *matched_param = nullptr;
697-
#ifdef LLDB_ENABLE_SWIFT
698-
if (call_edge) {
699-
#endif
700758
for (const CallSiteParameter &param : call_edge->GetCallSiteParameters()) {
701759
DataExtractor param_subexpr_extractor;
702760
if (!param.LocationInCallee.GetExpressionData(param_subexpr_extractor))
@@ -722,25 +780,10 @@ Evaluate_DW_OP_entry_value(std::vector<Value> &stack, const DWARFUnit *dwarf_cu,
722780
}
723781
if (!matched_param)
724782
return llvm::createStringError("no matching call site param found");
725-
#ifdef LLDB_ENABLE_SWIFT
726-
}
727-
std::optional<DWARFExpressionList> subexpr;
728-
if (!matched_param) {
729-
auto *ctx_func = parent_func ? parent_func : current_func;
730-
subexpr.emplace(ctx_func->CalculateSymbolContextModule(),
731-
DataExtractor(opcodes, subexpr_offset, subexpr_len),
732-
dwarf_cu);
733-
}
734-
#endif
735783

736784
// TODO: Add support for DW_OP_push_object_address within a DW_OP_entry_value
737785
// subexpresion whenever llvm does.
738-
#ifdef LLDB_ENABLE_SWIFT
739-
const DWARFExpressionList &param_expr =
740-
matched_param ? matched_param->LocationInCaller : *subexpr;
741-
#else
742786
const DWARFExpressionList &param_expr = matched_param->LocationInCaller;
743-
#endif
744787

745788
llvm::Expected<Value> maybe_result = param_expr.Evaluate(
746789
&parent_exe_ctx, parent_frame->GetRegisterContext().get(),

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,11 +2538,6 @@ struct AsyncUnwindRegisterNumbers {
25382538
uint32_t async_ctx_regnum;
25392539
uint32_t fp_regnum;
25402540
uint32_t pc_regnum;
2541-
/// A register to use as a marker to indicate how the async context is passed
2542-
/// to the function (indirectly, or not). This needs to be communicated to the
2543-
/// frames below us as they need to react differently. There is no good way to
2544-
/// expose this, so we set another dummy register to communicate this state.
2545-
uint32_t dummy_regnum;
25462541

25472542
RegisterKind GetRegisterKind() const { return lldb::eRegisterKindDWARF; }
25482543
};
@@ -2556,15 +2551,13 @@ GetAsyncUnwindRegisterNumbers(llvm::Triple::ArchType triple) {
25562551
regnums.async_ctx_regnum = dwarf_r14_x86_64;
25572552
regnums.fp_regnum = dwarf_rbp_x86_64;
25582553
regnums.pc_regnum = dwarf_rip_x86_64;
2559-
regnums.dummy_regnum = dwarf_r15_x86_64;
25602554
return regnums;
25612555
}
25622556
case llvm::Triple::aarch64: {
25632557
AsyncUnwindRegisterNumbers regnums;
25642558
regnums.async_ctx_regnum = arm64_dwarf::x22;
25652559
regnums.fp_regnum = arm64_dwarf::fp;
25662560
regnums.pc_regnum = arm64_dwarf::pc;
2567-
regnums.dummy_regnum = arm64_dwarf::x23;
25682561
return regnums;
25692562
}
25702563
default:
@@ -2810,18 +2803,11 @@ SwiftLanguageRuntime::GetRuntimeUnwindPlan(ProcessSP process_sp,
28102803
// The CFA of a funclet is its own async context.
28112804
row->GetCFAValue().SetIsConstant(*async_ctx);
28122805

2813-
// The value of the async register in the parent frame is the entry value of
2814-
// the async register in the current frame. This mimics a function call, as
2815-
// if the parent funclet had called the current funclet.
2816-
row->SetRegisterLocationToIsConstant(regnums->async_ctx_regnum, *async_reg,
2806+
// The value of the async register in the parent frame (which is the
2807+
// continuation funclet) is the async context of this frame.
2808+
row->SetRegisterLocationToIsConstant(regnums->async_ctx_regnum, *async_ctx,
28172809
/*can_replace=*/false);
28182810

2819-
// The parent frame needs to know how to interpret the value it is given for
2820-
// its own async register. A dummy register is used to communicate that.
2821-
if (!indirect_context)
2822-
row->SetRegisterLocationToIsConstant(regnums->dummy_regnum, 0,
2823-
/*can_replace=*/false);
2824-
28252811
if (std::optional<addr_t> pc_after_prologue =
28262812
TrySkipVirtualParentProlog(*async_ctx, *process_sp))
28272813
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,
@@ -2854,59 +2840,13 @@ UnwindPlanSP SwiftLanguageRuntime::GetFollowAsyncContextUnwindPlan(
28542840
if (!regnums)
28552841
return UnwindPlanSP();
28562842

2857-
const bool is_indirect =
2858-
regctx->ReadRegisterAsUnsigned(regnums->dummy_regnum, (uint64_t)-1ll) ==
2859-
(uint64_t)-1ll;
2860-
// In the general case, the async register setup by the frame above us
2861-
// should be dereferenced twice to get our context, except when the frame
2862-
// above us is an async frame on the OS stack that takes its context directly
2863-
// (see discussion in GetRuntimeUnwindPlan()). The availability of
2864-
// dummy_regnum is used as a marker for this situation.
2865-
if (!is_indirect) {
2866-
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
2867-
row->SetRegisterLocationToSame(regnums->async_ctx_regnum, false);
2868-
} else {
2869-
static const uint8_t async_dwarf_expression_x86_64[] = {
2870-
llvm::dwarf::DW_OP_regx, dwarf_r14_x86_64, // DW_OP_regx, reg
2871-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2872-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2873-
};
2874-
static const uint8_t async_dwarf_expression_arm64[] = {
2875-
llvm::dwarf::DW_OP_regx, arm64_dwarf::x22, // DW_OP_regx, reg
2876-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2877-
llvm::dwarf::DW_OP_deref, // DW_OP_deref
2878-
};
2879-
2880-
const unsigned expr_size = sizeof(async_dwarf_expression_x86_64);
2881-
static_assert(sizeof(async_dwarf_expression_x86_64) ==
2882-
sizeof(async_dwarf_expression_arm64),
2883-
"Expressions of different sizes");
2884-
2885-
const uint8_t *expression = nullptr;
2886-
if (arch.GetMachine() == llvm::Triple::x86_64)
2887-
expression = async_dwarf_expression_x86_64;
2888-
else if (arch.GetMachine() == llvm::Triple::aarch64)
2889-
expression = async_dwarf_expression_arm64;
2890-
else
2891-
llvm_unreachable("Unsupported architecture");
2892-
2893-
// Note how the register location gets the same expression pointer with a
2894-
// different size. We just skip the trailing deref for it.
2895-
assert(expression[expr_size - 1] == llvm::dwarf::DW_OP_deref &&
2896-
"Should skip a deref");
2897-
row->GetCFAValue().SetIsDWARFExpression(expression, expr_size);
2898-
row->SetRegisterLocationToIsDWARFExpression(
2899-
regnums->async_ctx_regnum, expression, expr_size - 1, false);
2900-
}
2901-
2902-
// Suppose this is unwinding frame #2 of a call stack. The value given for
2903-
// the async register has two possible values, depending on what frame #1
2904-
// expects:
2905-
// 1. The CFA of frame #1, direct ABI, dereferencing it once produces CFA of
2906-
// Frame #2.
2907-
// 2. The CFA of frame #0, indirect ABI, dereferencing it twice produces CFA
2908-
// of Frame #2.
2909-
const unsigned num_indirections = 1 + is_indirect;
2843+
row->GetCFAValue().SetIsRegisterDereferenced(regnums->async_ctx_regnum);
2844+
// The value of the async register in the parent frame (which is the
2845+
// continuation funclet) is the async context of this frame.
2846+
row->SetRegisterLocationToIsCFAPlusOffset(regnums->async_ctx_regnum,
2847+
/*offset*/ 0, false);
2848+
2849+
const unsigned num_indirections = 1;
29102850
if (std::optional<addr_t> pc_after_prologue = TrySkipVirtualParentProlog(
29112851
GetAsyncContext(regctx), *process_sp, num_indirections))
29122852
row->SetRegisterLocationToIsConstant(regnums->pc_regnum, *pc_after_prologue,

lldb/test/API/lang/swift/async/frame/variables_multiple_frames/TestSwiftAsyncFrameVarMultipleFrames.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,63 @@
44
import lldbsuite.test.lldbutil as lldbutil
55

66

7+
@skipIf(archs=no_match(["arm64", "arm64e", "x86_64"]))
78
class TestCase(lldbtest.TestBase):
89

910
mydir = lldbtest.TestBase.compute_mydir(__file__)
1011

12+
def read_ptr_from_memory(self, process, addr):
13+
error = lldb.SBError()
14+
value = process.ReadPointerFromMemory(addr, error)
15+
self.assertSuccess(error, "Failed to read memory")
16+
return value
17+
1118
# Check that the CFA chain is correctly built
1219
def check_cfas(self, async_frames, process):
1320
async_cfas = list(map(lambda frame: frame.GetCFA(), async_frames))
1421
expected_cfas = [async_cfas[0]]
1522
# The CFA chain ends in nullptr.
1623
while expected_cfas[-1] != 0:
17-
error = lldb.SBError()
18-
expected_cfas.append(
19-
process.ReadPointerFromMemory(expected_cfas[-1], error)
20-
)
21-
self.assertSuccess(error, "Managed to read cfa memory")
24+
expected_cfas.append(self.read_ptr_from_memory(process, expected_cfas[-1]))
2225

2326
self.assertEqual(async_cfas, expected_cfas[:-1])
2427

2528
def check_pcs(self, async_frames, process, target):
2629
for idx, frame in enumerate(async_frames[:-1]):
2730
# Read the continuation pointer from the second field of the CFA.
28-
error = lldb.SBError()
29-
continuation_ptr = process.ReadPointerFromMemory(
30-
frame.GetCFA() + target.addr_size, error
31+
continuation_ptr = self.read_ptr_from_memory(
32+
process, frame.GetCFA() + target.addr_size
3133
)
32-
self.assertSuccess(error, "Managed to read context memory")
3334

3435
# The PC of the previous frame should be the continuation pointer
3536
# with the funclet's prologue skipped.
3637
parent_frame = async_frames[idx + 1]
3738
prologue_to_skip = parent_frame.GetFunction().GetPrologueByteSize()
3839
self.assertEqual(continuation_ptr + prologue_to_skip, parent_frame.GetPC())
3940

41+
def check_async_regs_one_frame(self, frame, process):
42+
async_reg_name = "r14" if self.getArchitecture() == "x86_64" else "x22"
43+
44+
cfa = frame.GetCFA()
45+
is_indirect = "await resume" in frame.GetFunctionName()
46+
async_register = frame.FindRegister(async_reg_name).GetValueAsUnsigned()
47+
48+
if is_indirect:
49+
deref_async_reg = self.read_ptr_from_memory(process, async_register)
50+
self.assertEqual(deref_async_reg, cfa)
51+
else:
52+
self.assertEqual(async_register, cfa)
53+
54+
def check_async_regs(self, async_frames, process):
55+
for frame in async_frames:
56+
# The frames from the implicit main function don't have a demangled
57+
# name, so we can't test whether they are a Q funclet or not.
58+
if "Main" in frame.GetFunctionName():
59+
break
60+
self.check_async_regs_one_frame(frame, process)
4061

4162
def check_variables(self, async_frames, expected_values):
42-
for (frame, expected_value) in zip(async_frames, expected_values):
63+
for frame, expected_value in zip(async_frames, expected_values):
4364
myvar = frame.FindVariable("myvar")
4465
lldbutil.check_variable(self, myvar, False, value=expected_value)
4566

@@ -58,6 +79,7 @@ def test(self):
5879
self.check_cfas(async_frames, process)
5980
self.check_pcs(async_frames, process, target)
6081
self.check_variables(async_frames, ["222", "333", "444", "555"])
82+
self.check_async_regs(async_frames, process)
6183

6284
target.DeleteAllBreakpoints()
6385
target.BreakpointCreateBySourceRegex("breakpoint2", source_file)
@@ -68,6 +90,7 @@ def test(self):
6890
self.check_cfas(async_frames, process)
6991
self.check_pcs(async_frames, process, target)
7092
self.check_variables(async_frames, ["111", "222", "333", "444", "555"])
93+
self.check_async_regs(async_frames, process)
7194

7295
# Now stop at the Q funclet right after the await to ASYNC___1
7396
target.DeleteAllBreakpoints()
@@ -77,6 +100,7 @@ def test(self):
77100
self.check_cfas(async_frames, process)
78101
self.check_pcs(async_frames, process, target)
79102
self.check_variables(async_frames, ["222", "333", "444", "555"])
103+
self.check_async_regs(async_frames, process)
80104

81105
target.DeleteAllBreakpoints()
82106
target.BreakpointCreateBySourceRegex("breakpoint3", source_file)
@@ -85,3 +109,4 @@ def test(self):
85109
self.check_cfas(async_frames, process)
86110
self.check_pcs(async_frames, process, target)
87111
self.check_variables(async_frames, ["222", "333", "444", "555"])
112+
self.check_async_regs(async_frames, process)

0 commit comments

Comments
 (0)