authorbors <bors@rust-lang.org> 2026-06-22 07:44:55 UTC
committerbors <bors@rust-lang.org> 2026-06-22 07:44:55 UTC
log9030e345fe92df1ccefd0a8cdf61a9a9a5b73cb0
tree3a35128046784e903273acf0850c16fa27e60ab7
parent942ac9ce4116d4ea784c9882659372b34978b1f8
parent6d52e8249838939cab21347c0180d63ff5b5f3a4

Auto merge of #158240 - jhpratt:rollup-Hc6OaoP, r=jhpratt

Rollup of 3 pull requests Successful merges: - rust-lang/rust#157529 (Add `tests/debuginfo` data classes to define schema) - rust-lang/rust#158170 (parse attrs if a loop's parent hir node is a stmt) - rust-lang/rust#157832 (test: make riscv-float-struct-abi.rs robust against LLVM scheduling)

6 files changed, 745 insertions(+), 35 deletions(-)

compiler/rustc_mir_build/src/thir/cx/expr.rs+19-13
......@@ -22,7 +22,7 @@ use rustc_middle::ty::{
2222 UpvarArgs,
2323};
2424use rustc_middle::{bug, span_bug};
25use rustc_span::Span;
25use rustc_span::{DesugaringKind, Span};
2626use tracing::{debug, info, instrument, trace};
2727
2828use crate::diagnostics::*;
......@@ -72,18 +72,24 @@ impl<'tcx> ThirBuildCx<'tcx> {
7272
7373 let mut attrs = ThinVec::new();
7474
75 if let ExprKind::Loop { .. } = expr.kind {
76 // For loops defined with loop and while, the expr already has the attrs
77 if let hir::Node::Block(_) = self.tcx.parent_hir_node(hir_expr.hir_id) {
78 attrs = parsed_attrs(hir_expr.hir_id, self.tcx);
79 }
80
81 // For loop desugaring puts us pretty deep down the HIR tree
82 if let hir::Node::Arm(arm) = self.tcx.parent_hir_node(hir_expr.hir_id)
83 && let hir::Node::Expr(expr) = self.tcx.parent_hir_node(arm.hir_id)
84 && let hir::Node::Expr(expr) = self.tcx.parent_hir_node(expr.hir_id)
85 {
86 attrs = parsed_attrs(expr.hir_id, self.tcx);
75 if let hir::ExprKind::Loop(_, _, _, span) = hir_expr.kind {
76 match span.desugaring_kind() {
77 // `for` loop desugaring puts us pretty deep down the HIR tree
78 Some(DesugaringKind::ForLoop) => {
79 let arm = self.tcx.parent_hir_node(hir_expr.hir_id).expect_arm();
80 let expr = self.tcx.parent_hir_node(arm.hir_id).expect_expr();
81 std::assert_matches!(expr.kind, hir::ExprKind::Match(..));
82 // ignore async for loops
83 if let hir::Node::Expr(expr) = self.tcx.parent_hir_node(expr.hir_id) {
84 std::assert_matches!(expr.kind, hir::ExprKind::DropTemps(..));
85 attrs = parsed_attrs(expr.hir_id, self.tcx)
86 }
87 }
88 // For loops defined with `loop` and `while`, the expr already has the attrs
89 Some(DesugaringKind::WhileLoop) | None => {
90 attrs = parsed_attrs(hir_expr.hir_id, self.tcx);
91 }
92 _ => (),
8793 }
8894 }
8995
src/etc/lldb_batchmode/common.py created+328
......@@ -0,0 +1,328 @@
1"""Contains the class definitions outlining the schema of the test data. For LLDB conversion
2from/into these types, see `./from_lldb.py`"""
3
4import enum
5import json
6import os
7from dataclasses import asdict, dataclass, field, fields, is_dataclass
8from types import NoneType
9from typing import Any, Optional, get_origin, Final
10
11char = str
12Primitive = int | float | bool | char
13ByteSize = int
14
15# see: default json decoder docs https://docs.python.org/3/library/json.html#json.JSONDecoder
16# The types we're dealing with can only be: int, str, float, list, dict, bool, and None
17JsonType = int | str | float | list["JsonType"] | bool | None | dict[str, "JsonType"]
18
19
20class Target(enum.Enum):
21 """Due to the differences between PDB and DWARF debug info, we cannot guarantee their output
22 will be identical. Since LLDB can handle both, we need to conditionally select the correct
23 test data to use.
24
25 Additionally, since there are differences in the internals of some structs based on OS (e.g.
26 `PathBuf`/`OsString`), we need to be aware of whether we're on Windows or not.
27
28 A global var `TARGET` is set to the current variant upon `lldb_test.py`'s instantiation using an
29 env var passed from `compiletest` and is not expected to change afterwards."""
30
31 NonWindows = "non_windows"
32 WindowsGnu = "windows_gnu"
33 WindowsMsvc = "windows_msvc"
34
35
36def get_target() -> Target:
37 # set by compiletest when launching LLDB
38 t: str = os.environ["LLDB_BATCHMODE_TARGET_TRIPLE"]
39 if t.endswith("windows-msvc"):
40 return Target.WindowsMsvc
41 if t.endswith("windows-gnu") or t.endswith("windows-gnullvm"):
42 return Target.WindowsGnu
43
44 return Target.NonWindows
45
46
47BLESS: Final[bool] = os.environ["LLDB_BATCHMODE_BLESS_TEST_DATA"] == "1"
48"""Global constant set by `compiletest` that determines whether or not we are blessing the test
49data."""
50
51TARGET: Final[Target] = get_target()
52"""Global constant set by `compiletest`. Determines which target the tests were run for, thus which
53set of test input we check."""
54
55
56def annot_to_ty(annot: str) -> type[Any]:
57 """Fallback to resolve a string type annotation to its actual type (e.g. `"Variable"` ->
58 `Variable`). For types with generics, the generic is ignored."""
59
60 return {
61 "int": int,
62 "float": float,
63 "bool": bool,
64 "None": NoneType,
65 "list": list,
66 "dict": dict,
67 "str": str,
68 "ByteSize": int,
69 "TargetData": TargetData,
70 "Variable": Variable,
71 "Type": Type,
72 "Field": Field,
73 "Child": Child,
74 "BlessMetadata": BlessMetadata,
75 }.get(annot.split("[", 1)[0], type[Any])
76
77
78def from_dict(ty: type[Any], data: JsonType):
79 """Translates a dictionary into an instance of the given dataclass type (with possibly nested
80 dataclasses).
81
82 Relies on accurate type hints for the dataclass's fields, and the default `dataclass.__init__`
83 definition."""
84
85 # Optional isn't a constructor, so we have to "unwrap" it.
86 if get_origin(ty) is Optional:
87 ty = ty.__args__[0]
88
89 # recurse into lists
90 if isinstance(data, list):
91 # pulls the generic type from the list (e.g. `list[int]` -> `int`)
92 inner = ty.__args__[0]
93 if isinstance(inner, str):
94 inner = annot_to_ty(inner)
95
96 return [from_dict(inner, i) for i in data]
97
98 if get_origin(ty) is dict and ty.__args__[0] is str:
99 assert isinstance(data, dict)
100 val_ty = ty.__args__[1]
101 if isinstance(val_ty, str):
102 val_ty = annot_to_ty(val_ty)
103
104 if val_ty in [Variable, Child, Type, Field]:
105 return {k: from_dict(val_ty, data[k]) for k in data.keys()}
106
107 # map dict -> dataclass, recursing for each field
108 if is_dataclass(ty):
109 assert isinstance(data, dict)
110
111 field_types = {f.name: f.type for f in fields(ty)}
112
113 try:
114 field_map = {}
115
116 for f in data:
117 f_type = field_types[f]
118
119 # type annotations can be strings, so we need to resolve them to their actual type
120 if isinstance(f_type, str):
121 f_type = annot_to_ty(f_type)
122
123 field_map[f] = from_dict(f_type, data[f])
124
125 # if you've never seen this before, `**` is the splat operator. It expands a mapping
126 # type (in this case a dict) to keyword arguments. The ordering of the mapping does not
127 # matter, only that the mapping's keys match the functions keyword args, and
128 # `len(mapping)` == the number of keyword args.
129 return ty(**field_map)
130 except KeyError as e:
131 print(
132 f"Unable to convert dict to {ty}: Invalid field name {e}. If the test schema was \
133changed intentionally, use the `--bless` option to update test data to the new schema."
134 )
135
136 # for any other type, we don't need to do any processing
137 return data
138
139
140@dataclass(slots=True)
141class Field:
142 name: str
143 type: str
144 """The fully qualified name of the field's type. Full type information should be looked up
145 via `TargetData.types`"""
146
147 offset: ByteSize
148
149
150@dataclass(slots=True)
151class Type:
152 size: ByteSize
153 # When GDB support is added to the test framework, basic_type and type_class will probably be
154 # converted to a wrapper IntEnum that converts GDB's equivalent information to
155 basic_type: int
156 """The `lldb.eBasicType` value associated with this type. Tested due to our use of it in type
157 recognizer functions."""
158
159 type_class: int
160 """The `lldb.eTypeClass` value associated with thjs type. Tested due to our use of it in type
161 recognizer functions."""
162
163 fields: list[Field]
164 """Stored as a list due to our reliance on `SBType.GetFieldAtIndex()`"""
165
166 generic_params: list[str]
167 """Stored as a list due to our reliance on `SBType.GetTemplateArgumentType()` and the sequential
168 behavior of `lldb_providers.get_template_args`"""
169 # FIXME the only way we can look up static fields is by name (as of lldb 22), so we need a way
170 # to discover them. ATM only sum-type enums on MSVC use static fields, and those are fixed
171 # values, so it's not super urgent.
172 # static_fields: list[StaticField]
173
174
175@dataclass(slots=True)
176class Child:
177 """Similar to `Variable`, but carries less information since we primarily test top-level
178 values (and assume values of these child types have been tested thoroughly elsewhere).
179
180 Note that if the type has a synthetic provider (lldb) or pretty printer (gdb), the child names
181 and types can be set to anything at all, so we do need to test these separately from the
182 parent's type's fields."""
183
184 name: str
185 """The name used to access the child. If the parent object has a synthetic, the child name can
186 be overridden."""
187
188 type: str
189 """The fully qualified name of the child's type. Full type information should be looked up
190 via `TargetData.types`"""
191
192 value: Optional[Primitive]
193 children: list["Child"]
194 """Children are stored as a list because of our use of `GetChildAtIndex()`. Providers can also
195 dictate the order that children populate, so it's important to ensure that stays consistent too.
196 """
197
198
199@dataclass(slots=True)
200class Variable:
201 type: str
202 """The fully qualified name of the variable's type. Full type information should be looked up
203 via `TargetData.types`"""
204
205 pretty_type_name: Optional[str]
206 """Type names can be overridden by `SyntehticProvider.get_type_name()` in LLDB and by
207 `type_printer` in GDB"""
208
209 pretty_print: Optional[str]
210 """The string-result of pretty printing the value (`SBValue.GetSummary` for LLDB,
211 `pretty_printer.to_string` for GDB). `None` for aggregates with no summary provider."""
212
213 value: Optional[Primitive]
214 """`None` if the object does not have a primitive representation."""
215
216 synthetic: Optional[str]
217 """The class/function name of the synthetic provider (lldb) or pretty printer (gdb).
218 `None` if the object does not have a synthetic provider"""
219
220 summary: Optional[str]
221 """The function name of the summary provider. `None` if the object does not have a summary
222 provider, or if the test data is for GDB"""
223
224 format: Optional[int]
225 """The `lldb.eFormat` enum variant associated with this type (if applicable)."""
226
227 # Stored as a list instead of a dict because child order matters
228 children: list[Child]
229 """A list of children provided by the object. If the object has a synthetic provider, the
230 children are the result of the provider's `get_child_at_index` function"""
231
232
233@dataclass(slots=True)
234class BlessMetadata:
235 """
236 Contains additional context about the tools at the time the test data was generated
237 """
238
239 python_version: str = ""
240 debugger_version: str = ""
241 # FIXME (todo)
242 # feature_flags: str
243
244
245@dataclass(slots=True)
246class TargetData:
247 """
248 Top-level container for all test data.
249
250 Due to the differences between PDB and DWARF debug info, we cannot guarantee their output
251 will be identical. Since LLDB can handle both, we need to conditionally select the correct
252 test data to use.
253
254 Additionally, since there are differences in the internals of some structs based on OS (e.g.
255 `PathBuf`/`OsString`), we need to be aware of whether we're on Windows or not.
256
257 A global var `TARGET` is set to the current variant upon `lldb_batchmode`'s instantiation using
258 an env var passed from `compiletest` and is not expected to change afterwards.
259 """
260
261 bless_metadata: BlessMetadata = field(default_factory=BlessMetadata)
262 """Miscellaneous data included to make diagnosing issues easier. This data is not intended to be
263 tested against."""
264
265 types: dict[str, Type] = field(default_factory=dict)
266 """
267 A map of type names to types. Contains all types present in the test's variables, including the
268 types of fields and child objects.
269 """
270
271 # If we ever decide that it makes sense to check the same variable twice at the same breakpoint
272 # this will need to be converted to a list
273 breakpoints: list[dict[str, Variable]] = field(default_factory=list)
274 """Each element corresponds to one stopping point in the test. The element itself is a
275 dictionary mapping variable names to their respective test data."""
276
277 @staticmethod
278 def initialize() -> "TargetData":
279 result = TargetData()
280 path = os.environ["LLDB_BATCHMODE_INPUT_DATA_PATH"]
281 if not os.path.isfile(path):
282 if BLESS:
283 return result
284 else:
285 raise Exception(
286 f"Invalid input data path: '{path}'\nIf test data has not been \
287generated for this test yet, consider using the `--bless` option."
288 )
289
290 with open(path, "r") as f:
291 try:
292 result = from_dict(TargetData, json.load(f))
293 except json.decoder.JSONDecodeError:
294 print("Warning: Malformed input data, reverting to default")
295
296 return result
297
298 def save_blessing(self, metadata: BlessMetadata):
299 """Writes the entirety of `self` to the env var `LLDB_BATCHMODE_INPUT_DATA_PATH`, which is
300 set by `compiletest` before running `lldb_batchmode. Used to finalize changes made by one or
301 more `from_lldb.bless_variable` calls.
302
303 This function should be called exactly once, right before
304 `lldb_batchmode.runner.main` exits if the following conditions are met:
305
306 1. No other exceptions or error states occurred
307 2. `BLESS == True`
308 3. At least one `repr` pseudo-command was processed
309
310 This prevents us from saving incomplete data or invalid data. It also prevents us from
311 creating input data files for tests that do not need it.
312 """
313
314 self.bless_metadata = metadata
315 path = os.environ["LLDB_BATCHMODE_INPUT_DATA_PATH"]
316 # dumping directly to a file is somewhat unsafe. If the `Variable`/`Type` data ends up in a
317 # state that cannot be serialized correctly, the json ends up malformed, and we could end up
318 # overwriting valid test data with a complete mess. Since the in-memory data is typically
319 # completely valid, the testing logic will pass and make it seem like nothing is wrong.
320
321 # While we could rely on git to help revert the test file, it's better to just not allow it
322 # to save malformed json in the first place. Thus, we dump the JSON, re-read it to check
323 # for `JSONDecodeError`, and write it to the target file if no error occurred.
324 x = json.dumps(asdict(self), indent=" ")
325 _ = json.loads(x)
326
327 with open(path, "w") as f:
328 f.write(x)
src/etc/lldb_batchmode/from_lldb.py created+314
......@@ -0,0 +1,314 @@
1"""Contains LLDB conversion functions from LLDB's in-memory representations to the test classes
2defined in `./common.py`.
3
4We primarily interface with the following LLDB classes:
5
6* [`SBValue`](https://lldb.llvm.org/python_api/lldb.SBValue.html)
7* [`SBType`](https://lldb.llvm.org/python_api/lldb.SBType.html)
8* [`SBTypeMember`](https://lldb.llvm.org/python_api/lldb.SBTypeMember.html)
9"""
10
11from struct import unpack, calcsize
12
13import lldb
14import lldb_lookup
15
16from .common import (
17 TARGET,
18 Child,
19 Field,
20 Target,
21 TargetData,
22 Type,
23 Variable,
24)
25
26_UNSIGNED_INT_TYPES = {
27 lldb.eBasicTypeUnsignedChar,
28 lldb.eBasicTypeUnsignedShort,
29 lldb.eBasicTypeUnsignedInt,
30 lldb.eBasicTypeUnsignedLong,
31 lldb.eBasicTypeUnsignedLongLong,
32 lldb.eBasicTypeUnsignedInt128,
33}
34
35_FLOAT_TYPES = {
36 lldb.eBasicTypeHalf,
37 lldb.eBasicTypeFloat,
38 lldb.eBasicTypeDouble,
39 # FIXME: lldb added support for Float128 in 22.1, but python has no native
40 # support for it (even through `ctypes` or other alternatives). The best we
41 # can probably manage is comparing the raw bytes and/or trusting LLDB's output.
42 lldb.eBasicTypeFloat128,
43}
44
45_SIZE_TO_FLOAT_FMT = {
46 2: "e",
47 4: "f",
48 8: "d",
49}
50
51_SIZE_TO_INT_FMT = {
52 1: "b",
53 2: "h",
54 4: "l",
55 8: "q",
56 # python doesn't have native support for u128 so we manually reconstruct it from 2 64-bit ints
57 16: "qq",
58}
59
60
61def type_unpack_fmt(kind: int, size: int) -> str:
62 # we can't just map directly from lldb.eBasicType -> format string because lldb.eBasicType types
63 # aren't the same size on every target (even if targets have the same word size). e.g. On
64 # windows, isize = lldb.eBasicTypeLongLong, on linux with identical hardware,
65 # isize = lldb.eBasicTypeLong.
66 # Conversely, python's struct.unpack format specifiers ARE consistenly sized.
67
68 if kind in _FLOAT_TYPES:
69 return _SIZE_TO_FLOAT_FMT[size]
70
71 if kind == lldb.eBasicTypeBool:
72 return "?"
73
74 if kind == lldb.eBasicTypeChar32:
75 return "4s"
76
77 fmt = _SIZE_TO_INT_FMT[size]
78
79 if kind in _UNSIGNED_INT_TYPES:
80 fmt.upper()
81
82 return fmt
83
84
85def decode_primitive(valobj: lldb.SBValue) -> int | float | bool | str:
86 data: lldb.SBData = valobj.GetData()
87
88 type: lldb.SBType = valobj.GetType().GetCanonicalType()
89 kind = type.GetBasicType()
90
91 assert kind != lldb.eBasicTypeInvalid, f"{valobj.name} is not a primtive"
92
93 is_big_endian = data.GetByteOrder() == lldb.eByteOrderBig
94
95 buf = data.ReadRawData(lldb.SBError(), 0, data.GetByteSize())
96
97 if is_big_endian or kind == lldb.eBasicTypeChar32:
98 endian = ">"
99 else:
100 endian = "<"
101
102 format = endian + type_unpack_fmt(kind, type.GetByteSize())
103 # sanity check
104 assert calcsize(format) == data.GetByteSize()
105
106 got = unpack(format, buf)
107
108 if kind == lldb.eBasicTypeChar32:
109 got = got[0].decode("utf-32")
110 elif kind in [lldb.eBasicTypeInt128, lldb.eBasicTypeUnsignedInt128]:
111 # python doesn't have native support for u128 so we manually construct from 2 64-bit ints
112 hi = got[0] if is_big_endian else got[1]
113 lo = got[1] if is_big_endian else got[0]
114
115 got = lo | (hi << 64)
116 else:
117 got = got[0]
118
119 return got
120
121
122def get_summary_or_value(valobj: lldb.SBValue) -> str | None:
123 """`SBValue.GetSummary` only prints summaries from summary providers. It returns `None` if there
124 is no summary provider, rather than printing the default representation of the value. Often we
125 want any printable representation at all, so this function falls back to `SBValue.GetValue`.
126 That covers things like primitives and flat enums that typically don't have summary providers.
127 """
128
129 summary = valobj.GetSummary()
130 if summary is None:
131 return valobj.GetValue()
132
133 return summary
134
135
136def field_from_lldb(field: lldb.SBTypeMember) -> Field:
137 return Field(field.GetName(), field.GetType().GetName(), field.GetOffsetInBytes())
138
139
140def get_generics(ty: lldb.SBType, sbtarget: lldb.SBTarget) -> list[lldb.SBType]:
141 """Platform-agnostic equivalent to `SBType.template_args`. `SBType`'s template functions do not
142 work correctly with PDB debug info because PDB has no way to represent template parameters.
143
144 Due to the DWARF spec using
145 C++-centric terminology (e.g. `DW_TAG_template_type_parameter`), the following terms are
146 interchangable:
147
148 * template type param/arg <-> generic param
149 * template value param/arg <-> const generic param
150
151 The difference between "param" and "arg" is largely irrelevant for our purposes.
152 Pre-parameterized types (e.g. `Vec<T>`, which could be parameterized to `Vec<u8>`, LLDB calls
153 this "template specialization") are not reflected in the DWARF data at all, and are largely an
154 LLDB/clang implementation detail that isn't directly exposed to us.
155 """
156
157 name = ty.GetName()
158 # FIXME Rust doesn't output template *values* (the `10` in `ArrayVec<u8, 10>`), only
159 # template args (the `u8` in `ArrayVec<u8, 10>`). That means these can possibly have
160 # different results. That's not a big deal, I don't think anything in the std library uses
161 # template values at the moment.
162 # Eventually we can either change `get_template_args` to skip template values OR update
163 # rustc to output them for DWARF debug info. Also, since it's target-specific behavior, it
164 # shouldn't actually cause tests not to work.
165 if TARGET == Target.WindowsMsvc:
166 return [
167 lldb_lookup.resolve_msvc_template_arg(x, sbtarget)
168 for x in lldb_lookup.get_template_args(name)
169 ]
170 else:
171 return [
172 ty.GetTemplateArgumentType(i)
173 for i in range(ty.GetNumberOfTemplateArguments())
174 ]
175
176
177def type_from_lldb(ty: lldb.SBType, sbtarget: lldb.SBTarget) -> Type:
178 generic_types = get_generics(ty, sbtarget)
179 generics = [g.GetName() for g in generic_types]
180
181 return Type(
182 ty.GetByteSize(),
183 ty.GetBasicType(),
184 ty.GetTypeClass(),
185 [field_from_lldb(ty.GetFieldAtIndex(i)) for i in range(ty.GetNumberOfFields())],
186 generics,
187 )
188
189
190def child_from_lldb(child: lldb.SBValue) -> Child:
191 sbtype: lldb.SBType = child.GetType()
192
193 if not sbtype.IsPointerType() and sbtype.GetBasicType() != lldb.eBasicTypeInvalid:
194 value = decode_primitive(child)
195 else:
196 value = None
197
198 children = [
199 child_from_lldb(child.GetChildAtIndex(i)) for i in range(child.GetNumChildren())
200 ]
201
202 return Child(child.GetName(), child.GetType().GetName(), value, children)
203
204
205def variable_from_lldb(var: lldb.SBValue) -> Variable:
206 sbtype = var.GetType()
207 type_name = sbtype.GetName()
208
209 pretty_type_name = var.GetDisplayTypeName()
210
211 if pretty_type_name == type_name:
212 pretty_type_name = None
213
214 # We never want to store pointer values since they are expected to change from run to run.
215 # For now, we also only want to record values for primitives. In the future, we may support
216 # testing the `get_value` output from syntheticproviders, but the current visualizers do not
217 # implement this so it isn't urgent.
218 if not sbtype.IsPointerType() and sbtype.GetBasicType() != lldb.eBasicTypeInvalid:
219 value = decode_primitive(var)
220 else:
221 value = None
222
223 if (synth := var.GetTypeSynthetic()).IsValid():
224 synthetic = synth.GetData().strip()
225 else:
226 synthetic = None
227
228 if (summ := var.GetTypeSummary()).IsValid():
229 summary = summ.GetData().strip()
230 else:
231 summary = None
232
233 if (fmt := var.GetTypeFormat()).IsValid():
234 format = fmt.GetFormat()
235 else:
236 format = None
237
238 pretty_print = get_summary_or_value(var)
239
240 children = [
241 child_from_lldb(var.GetChildAtIndex(i)) for i in range(var.GetNumChildren())
242 ]
243
244 return Variable(
245 type_name,
246 pretty_type_name,
247 pretty_print,
248 value,
249 synthetic,
250 summary,
251 format,
252 children,
253 )
254
255
256def bless_variable(
257 target_data: TargetData, var_name: str, breakpoint_idx: int, frame: lldb.SBFrame
258):
259 """Updates the given `TargetData` with data generated from the given variable at the given
260 breakpoint. This function **does not** write to the input file. Please see
261 `TargetData.save_blessing` for more info on when and how to save the data.
262 """
263
264 valobj = frame.FindVariable(var_name)
265 if not valobj.IsValid():
266 # FIXME (todo) error handling
267 raise Exception(f"<bless error: Cannot find variable {var_name}>")
268
269 if len(target_data.breakpoints) <= breakpoint_idx:
270 target_data.breakpoints.append({})
271
272 var_data = variable_from_lldb(valobj)
273 target_data.breakpoints[breakpoint_idx][var_name] = var_data
274
275 bless_type(target_data, valobj.GetType(), valobj.GetTarget())
276
277 # We also need to bless the types of the valobj's children, as they may not appear in the type
278 # or fields.
279 for i in range(valobj.GetNumChildren()):
280 bless_type(target_data, valobj.GetChildAtIndex(i).GetType(), valobj.GetTarget())
281
282
283def bless_type(target_data: TargetData, type: lldb.SBType, sbtarget: lldb.SBTarget):
284 """Recursively adds the type and all types of its fields to the `target_data.types` mapping"""
285
286 t_name = type.GetName()
287 t_data = type_from_lldb(type, sbtarget)
288 if t_name in target_data.types:
289 # If the type already exists in the type map, we don't need to process any further. We do
290 # need to check that the type data is actually identical to its mapping before moving on.
291 # It shouldn't ever be different, but better safe than sorry.
292 assert target_data.types[t_name] == t_data
293 return
294
295 # We need to add this type first just in case the type contains itself.
296 target_data.types[t_name] = t_data
297
298 # For types we haven't seen, we need to recursively handle the types of all of the fields and
299 # generics
300 for i in range(type.GetNumberOfFields()):
301 field = type.GetFieldAtIndex(i)
302 f_type = field.GetType()
303 f_type_name = f_type.GetName()
304
305 if f_type_name not in target_data.types:
306 bless_type(target_data, f_type, sbtarget)
307
308 for generic in get_generics(type, sbtarget):
309 # FIXME the purpose of this check is to gracefully handle generic *values* (e.g. the `2` in
310 # `ArrayVec<u8, 2>`) that slipped through the msvc template arg handling. At some point,
311 # `lldb_providers.get_template_args` should be made to output whether or not a template arg
312 # is a value, but for now this should be fine.
313 if generic.IsValid():
314 bless_type(target_data, generic, sbtarget)
src/etc/lldb_lookup.py+3
......@@ -39,6 +39,9 @@ from lldb_providers import (
3939 MSVCTupleSyntheticProvider,
4040 ClangEncodedEnumSummaryProvider,
4141 StructSummaryProvider,
42 # re-exports
43 get_template_args as get_template_args,
44 resolve_msvc_template_arg as resolve_msvc_template_arg,
4245)
4346from rust_types import (
4447 ENUM_DISR_FIELD_NAME,
tests/assembly-llvm/riscv-float-struct-abi.rs+22-22
......@@ -105,17 +105,17 @@ extern "C" fn pass_packed(out: &mut Packed, x: Packed) {
105105extern "C" fn ret_packed(x: &Packed) -> Packed {
106106 // CHECK: addi sp, sp, -16
107107 // CHECK-NEXT: .cfi_def_cfa_offset 16
108 // CHECK-NEXT: lbu [[BYTE2:.*]], 2(a0)
109 // CHECK-NEXT: lbu [[BYTE1:.*]], 1(a0)
110 // CHECK-NEXT: lbu [[BYTE3:.*]], 3(a0)
111 // CHECK-NEXT: lbu [[BYTE4:.*]], 4(a0)
112 // CHECK-NEXT: slli [[SHIFTED2:.*]], [[BYTE2]], 8
113 // CHECK-NEXT: or [[BYTE12:.*]], [[SHIFTED2]], [[BYTE1]]
114 // CHECK-NEXT: slli [[SHIFTED3:.*]], [[BYTE3]], 16
115 // CHECK-NEXT: slli [[SHIFTED4:.*]], [[BYTE4]], 24
116 // CHECK-NEXT: or [[BYTE34:.*]], [[SHIFTED3]], [[SHIFTED4]]
117 // CHECK-NEXT: or [[VALUE:.*]], [[BYTE12]], [[BYTE34]]
118 // CHECK-NEXT: sw [[VALUE]], 8(sp)
108 // CHECK-DAG: lbu [[BYTE2:.*]], 2(a0)
109 // CHECK-DAG: lbu [[BYTE1:.*]], 1(a0)
110 // CHECK-DAG: lbu [[BYTE3:.*]], 3(a0)
111 // CHECK-DAG: lbu [[BYTE4:.*]], 4(a0)
112 // CHECK-DAG: slli [[SHIFTED2:.*]], [[BYTE2]], 8
113 // CHECK-DAG: or [[BYTE12:.*]], [[SHIFTED2]], [[BYTE1]]
114 // CHECK-DAG: slli [[SHIFTED3:.*]], [[BYTE3]], 16
115 // CHECK-DAG: slli [[SHIFTED4:.*]], [[BYTE4]], 24
116 // CHECK-DAG: or [[BYTE34:.*]], [[SHIFTED3]], [[SHIFTED4]]
117 // CHECK-DAG: or [[VALUE:.*]], [[BYTE12]], [[BYTE34]]
118 // CHECK: sw [[VALUE]], 8(sp)
119119 // CHECK-NEXT: flw fa0, 8(sp)
120120 // CHECK-NEXT: lbu a0, 0(a0)
121121 // CHECK-NEXT: addi sp, sp, 16
......@@ -127,17 +127,17 @@ extern "C" fn ret_packed(x: &Packed) -> Packed {
127127extern "C" fn call_packed(x: &Packed) {
128128 // CHECK: addi sp, sp, -16
129129 // CHECK-NEXT: .cfi_def_cfa_offset 16
130 // CHECK-NEXT: lbu [[BYTE2:.*]], 2(a0)
131 // CHECK-NEXT: lbu [[BYTE1:.*]], 1(a0)
132 // CHECK-NEXT: lbu [[BYTE3:.*]], 3(a0)
133 // CHECK-NEXT: lbu [[BYTE4:.*]], 4(a0)
134 // CHECK-NEXT: slli [[SHIFTED2:.*]], [[BYTE2]], 8
135 // CHECK-NEXT: or [[BYTE12:.*]], [[SHIFTED2]], [[BYTE1]]
136 // CHECK-NEXT: slli [[SHIFTED3:.*]], [[BYTE3]], 16
137 // CHECK-NEXT: slli [[SHIFTED4:.*]], [[BYTE4]], 24
138 // CHECK-NEXT: or [[BYTE34:.*]], [[SHIFTED3]], [[SHIFTED4]]
139 // CHECK-NEXT: or [[VALUE:.*]], [[BYTE12]], [[BYTE34]]
140 // CHECK-NEXT: sw [[VALUE]], 8(sp)
130 // CHECK-DAG: lbu [[BYTE2:.*]], 2(a0)
131 // CHECK-DAG: lbu [[BYTE1:.*]], 1(a0)
132 // CHECK-DAG: lbu [[BYTE3:.*]], 3(a0)
133 // CHECK-DAG: lbu [[BYTE4:.*]], 4(a0)
134 // CHECK-DAG: slli [[SHIFTED2:.*]], [[BYTE2]], 8
135 // CHECK-DAG: or [[BYTE12:.*]], [[SHIFTED2]], [[BYTE1]]
136 // CHECK-DAG: slli [[SHIFTED3:.*]], [[BYTE3]], 16
137 // CHECK-DAG: slli [[SHIFTED4:.*]], [[BYTE4]], 24
138 // CHECK-DAG: or [[BYTE34:.*]], [[SHIFTED3]], [[SHIFTED4]]
139 // CHECK-DAG: or [[VALUE:.*]], [[BYTE12]], [[BYTE34]]
140 // CHECK: sw [[VALUE]], 8(sp)
141141 // CHECK-NEXT: flw fa0, 8(sp)
142142 // CHECK-NEXT: lbu a0, 0(a0)
143143 // CHECK-NEXT: addi sp, sp, 16
tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs+59
......@@ -2,6 +2,7 @@
22
33#![crate_type = "lib"]
44#![feature(loop_hints)]
5#![feature(stmt_expr_attributes)]
56
67// This test ensures that we emit the expected LLVM metadata for loop hint attributes.
78// It does not test that loops are optimized as expected, because successful unrolling removes the
......@@ -26,5 +27,63 @@ pub fn unroll_hint() {
2627 }
2728}
2829
30// HIR for a `loop` statement is a bit different, make sure we still apply the metadata in that
31// case.
32
33#[no_mangle]
34pub fn unroll_full() {
35 // CHECK-LABEL: @unroll_full
36 // CHECK: !llvm.loop ![[FULL:[0-9]+]]
37 let mut i = 0;
38 let _return = (#[unroll(full)]
39 loop {
40 unsafe { maybe_has_side_effect() }
41 i += 1;
42 if i >= 10 {
43 break 1;
44 }
45 });
46}
47
48#[no_mangle]
49pub fn unroll_never() {
50 // CHECK-LABEL: @unroll_never
51 // CHECK: !llvm.loop ![[DISABLE:[0-9]+]]
52 let mut i = 0;
53 let _return = (1 + #[unroll(never)]
54 loop {
55 unsafe { maybe_has_side_effect() }
56 i += 1;
57 if i >= 10 {
58 break 1;
59 }
60 });
61}
62
63#[no_mangle]
64pub fn unroll_count() {
65 // CHECK-LABEL: @unroll_count
66 // CHECK: !llvm.loop ![[COUNT:[0-9]+]]
67 let mut i = 0;
68 #[unroll(5)]
69 loop {
70 unsafe { maybe_has_side_effect() }
71 i += 1;
72 if i >= 10 {
73 break;
74 }
75 }
76 unsafe { maybe_has_side_effect() }
77}
78
2979// CHECK: ![[HINT]] = distinct !{![[HINT]], ![[INNER_HINT:[0-9]+]]}
3080// CHECK: ![[INNER_HINT]] = !{!"llvm.loop.unroll.enable"}
81
82// CHECK: ![[FULL]] = distinct !{![[FULL]], ![[INNER_FULL:[0-9]+]]}
83// CHECK: ![[INNER_FULL]] = !{!"llvm.loop.unroll.full"}
84
85// CHECK: ![[DISABLE]] = distinct !{![[DISABLE]], ![[INNER_DISABLE:[0-9]+]]}
86// CHECK: ![[INNER_DISABLE]] = !{!"llvm.loop.unroll.disable"}
87
88// CHECK: ![[COUNT]] = distinct !{![[COUNT]], ![[INNER_COUNT:[0-9]+]]}
89// CHECK: ![[INNER_COUNT]] = !{!"llvm.loop.unroll.count", i32 5}