Stephen Tozer 3d08ade7bd
[ExtendLifetimes] Implement llvm.fake.use to extend variable lifetimes (#86149)
This patch is part of a set of patches that add an `-fextend-lifetimes`
flag to clang, which extends the lifetimes of local variables and
parameters for improved debuggability. In addition to that flag, the
patch series adds a pragma to selectively disable `-fextend-lifetimes`,
and an `-fextend-this-ptr` flag which functions as `-fextend-lifetimes`
for this pointers only. All changes and tests in these patches were
written by Wolfgang Pieb (@wolfy1961), while Stephen Tozer (@SLTozer)
has handled review and merging. The extend lifetimes flag is intended to
eventually be set on by `-Og`, as discussed in the RFC
here:

https://discourse.llvm.org/t/rfc-redefine-og-o1-and-add-a-new-level-of-og/72850

This patch implements a new intrinsic instruction in LLVM,
`llvm.fake.use` in IR and `FAKE_USE` in MIR, that takes a single operand
and has no effect other than "using" its operand, to ensure that its
operand remains live until after the fake use. This patch does not emit
fake uses anywhere; the next patch in this sequence causes them to be
emitted from the clang frontend, such that for each variable (or this) a
fake.use operand is inserted at the end of that variable's scope, using
that variable's value. This patch covers everything post-frontend, which
is largely just the basic plumbing for a new intrinsic/instruction,
along with a few steps to preserve the fake uses through optimizations
(such as moving them ahead of a tail call or translating them through
SROA).

Co-authored-by: Stephen Tozer <stephen.tozer@sony.com>
2024-08-29 17:53:32 +01:00

108 lines
4.8 KiB
Python

#!/usr/bin/python3
# Parsing dwarfdump's output to determine whether the location list for the
# parameter "b" covers all of the function. The script searches for information
# in the input file to determine the [prologue, epilogue) range for the
# function, the location list range for "b", and checks that the latter covers
# the entirety of the former.
import re
import sys
DebugInfoPattern = r"\.debug_info contents:"
DebugLinePattern = r"\.debug_line contents:"
ProloguePattern = r"^\s*0x([0-9a-f]+)\s.+prologue_end"
EpiloguePattern = r"^\s*0x([0-9a-f]+)\s.+epilogue_begin"
FormalPattern = r"^0x[0-9a-f]+:\s+DW_TAG_formal_parameter"
LocationPattern = r"DW_AT_location\s+\[DW_FORM_([a-z_]+)\](?:.*0x([a-f0-9]+))"
DebugLocPattern = r'\[0x([a-f0-9]+),\s+0x([a-f0-9]+)\) ".text": (.+)$'
SeenDebugInfo = False
SeenDebugLine = False
LocationRanges = None
PrologueEnd = None
EpilogueBegin = None
# The dwarfdump output should contain the DW_AT_location for "b" first, then the
# line table which should contain prologue_end and epilogue_begin entries.
with open(sys.argv[1], "r") as dwarf_dump_file:
dwarf_iter = iter(dwarf_dump_file)
for line in dwarf_iter:
if not SeenDebugInfo and re.match(DebugInfoPattern, line):
SeenDebugInfo = True
if not SeenDebugLine and re.match(DebugLinePattern, line):
SeenDebugLine = True
# Get the range of DW_AT_location for "b".
if LocationRanges is None:
if match := re.match(FormalPattern, line):
# Go until we either find DW_AT_location or reach the end of this entry.
location_match = None
while location_match is None:
if (line := next(dwarf_iter, "")) == "\n":
raise RuntimeError(
".debug_info output is missing DW_AT_location for 'b'"
)
location_match = re.search(LocationPattern, line)
# Variable has whole-scope location, represented by an empty tuple.
if location_match.group(1) == "exprloc":
LocationRanges = ()
continue
if location_match.group(1) != "sec_offset":
raise RuntimeError(
f"Unhandled form for DW_AT_location: DW_FORM_{location_match.group(1)}"
)
# Variable has location range list.
if (
debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, ""))
) is None:
raise RuntimeError(f"Invalid location range list for 'b'")
LocationRanges = (
int(debug_loc_match.group(1), 16),
int(debug_loc_match.group(2), 16),
)
while (
debug_loc_match := re.search(DebugLocPattern, next(dwarf_iter, ""))
) is not None:
match_loc_start = int(debug_loc_match.group(1), 16)
match_loc_end = int(debug_loc_match.group(2), 16)
match_expr = debug_loc_match.group(3)
if match_loc_start != LocationRanges[1]:
raise RuntimeError(
f"Location list for 'b' is discontinuous from [0x{LocationRanges[1]:x}, 0x{match_loc_start:x})"
)
if "stack_value" in match_expr:
raise RuntimeError(
f"Location list for 'b' contains a stack_value expression: {match_expr}"
)
LocationRanges = (LocationRanges[0], match_loc_end)
# Get the prologue_end address.
elif PrologueEnd is None:
if match := re.match(ProloguePattern, line):
PrologueEnd = int(match.group(1), 16)
# Get the epilogue_begin address.
elif EpilogueBegin is None:
if match := re.match(EpiloguePattern, line):
EpilogueBegin = int(match.group(1), 16)
break
if not SeenDebugInfo:
raise RuntimeError(".debug_info section not found.")
if not SeenDebugLine:
raise RuntimeError(".debug_line section not found.")
if LocationRanges is None:
raise RuntimeError(".debug_info output is missing parameter 'b'")
if PrologueEnd is None:
raise RuntimeError(".debug_line output is missing prologue_end")
if EpilogueBegin is None:
raise RuntimeError(".debug_line output is missing epilogue_begin")
if len(LocationRanges) == 2 and (
LocationRanges[0] > PrologueEnd or LocationRanges[1] < EpilogueBegin
):
raise RuntimeError(
f"""Location list for 'b' does not cover the whole function:")
Prologue to Epilogue = [0x{PrologueEnd:x}, 0x{EpilogueBegin:x})
Location range = [0x{LocationRanges[0]:x}, 0x{LocationRanges[1]:x})
"""
)