| author | bors <bors@rust-lang.org> 2026-06-22 07:44:55 UTC |
| committer | bors <bors@rust-lang.org> 2026-06-22 07:44:55 UTC |
| log | 9030e345fe92df1ccefd0a8cdf61a9a9a5b73cb0 |
| tree | 3a35128046784e903273acf0850c16fa27e60ab7 |
| parent | 942ac9ce4116d4ea784c9882659372b34978b1f8 |
| parent | 6d52e8249838939cab21347c0180d63ff5b5f3a4 |
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::{ |
| 22 | 22 | UpvarArgs, |
| 23 | 23 | }; |
| 24 | 24 | use rustc_middle::{bug, span_bug}; |
| 25 | use rustc_span::Span; | |
| 25 | use rustc_span::{DesugaringKind, Span}; | |
| 26 | 26 | use tracing::{debug, info, instrument, trace}; |
| 27 | 27 | |
| 28 | 28 | use crate::diagnostics::*; |
| ... | ... | @@ -72,18 +72,24 @@ impl<'tcx> ThirBuildCx<'tcx> { |
| 72 | 72 | |
| 73 | 73 | let mut attrs = ThinVec::new(); |
| 74 | 74 | |
| 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 | _ => (), | |
| 87 | 93 | } |
| 88 | 94 | } |
| 89 | 95 |
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 | |
| 2 | from/into these types, see `./from_lldb.py`""" | |
| 3 | ||
| 4 | import enum | |
| 5 | import json | |
| 6 | import os | |
| 7 | from dataclasses import asdict, dataclass, field, fields, is_dataclass | |
| 8 | from types import NoneType | |
| 9 | from typing import Any, Optional, get_origin, Final | |
| 10 | ||
| 11 | char = str | |
| 12 | Primitive = int | float | bool | char | |
| 13 | ByteSize = 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 | |
| 17 | JsonType = int | str | float | list["JsonType"] | bool | None | dict[str, "JsonType"] | |
| 18 | ||
| 19 | ||
| 20 | class 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 | ||
| 36 | def 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 | ||
| 47 | BLESS: 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 | |
| 49 | data.""" | |
| 50 | ||
| 51 | TARGET: Final[Target] = get_target() | |
| 52 | """Global constant set by `compiletest`. Determines which target the tests were run for, thus which | |
| 53 | set of test input we check.""" | |
| 54 | ||
| 55 | ||
| 56 | def 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 | ||
| 78 | def 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 \ | |
| 133 | changed 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) | |
| 141 | class 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) | |
| 151 | class 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) | |
| 176 | class 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) | |
| 200 | class 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) | |
| 234 | class 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) | |
| 246 | class 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 \ | |
| 287 | generated 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 | |
| 2 | defined in `./common.py`. | |
| 3 | ||
| 4 | We 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 | ||
| 11 | from struct import unpack, calcsize | |
| 12 | ||
| 13 | import lldb | |
| 14 | import lldb_lookup | |
| 15 | ||
| 16 | from .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 | ||
| 61 | def 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 | ||
| 85 | def 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 | ||
| 122 | def 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 | ||
| 136 | def field_from_lldb(field: lldb.SBTypeMember) -> Field: | |
| 137 | return Field(field.GetName(), field.GetType().GetName(), field.GetOffsetInBytes()) | |
| 138 | ||
| 139 | ||
| 140 | def 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 | ||
| 177 | def 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 | ||
| 190 | def 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 | ||
| 205 | def 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 | ||
| 256 | def 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 | ||
| 283 | def 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 ( |
| 39 | 39 | MSVCTupleSyntheticProvider, |
| 40 | 40 | ClangEncodedEnumSummaryProvider, |
| 41 | 41 | StructSummaryProvider, |
| 42 | # re-exports | |
| 43 | get_template_args as get_template_args, | |
| 44 | resolve_msvc_template_arg as resolve_msvc_template_arg, | |
| 42 | 45 | ) |
| 43 | 46 | from rust_types import ( |
| 44 | 47 | 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) { |
| 105 | 105 | extern "C" fn ret_packed(x: &Packed) -> Packed { |
| 106 | 106 | // CHECK: addi sp, sp, -16 |
| 107 | 107 | // 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) | |
| 119 | 119 | // CHECK-NEXT: flw fa0, 8(sp) |
| 120 | 120 | // CHECK-NEXT: lbu a0, 0(a0) |
| 121 | 121 | // CHECK-NEXT: addi sp, sp, 16 |
| ... | ... | @@ -127,17 +127,17 @@ extern "C" fn ret_packed(x: &Packed) -> Packed { |
| 127 | 127 | extern "C" fn call_packed(x: &Packed) { |
| 128 | 128 | // CHECK: addi sp, sp, -16 |
| 129 | 129 | // 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) | |
| 141 | 141 | // CHECK-NEXT: flw fa0, 8(sp) |
| 142 | 142 | // CHECK-NEXT: lbu a0, 0(a0) |
| 143 | 143 | // CHECK-NEXT: addi sp, sp, 16 |
tests/codegen-llvm/loop-attrs/unroll-loop-metadata.rs+59| ... | ... | @@ -2,6 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | #![crate_type = "lib"] |
| 4 | 4 | #![feature(loop_hints)] |
| 5 | #![feature(stmt_expr_attributes)] | |
| 5 | 6 | |
| 6 | 7 | // This test ensures that we emit the expected LLVM metadata for loop hint attributes. |
| 7 | 8 | // It does not test that loops are optimized as expected, because successful unrolling removes the |
| ... | ... | @@ -26,5 +27,63 @@ pub fn unroll_hint() { |
| 26 | 27 | } |
| 27 | 28 | } |
| 28 | 29 | |
| 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] | |
| 34 | pub 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] | |
| 49 | pub 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] | |
| 64 | pub 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 | ||
| 29 | 79 | // CHECK: ![[HINT]] = distinct !{![[HINT]], ![[INNER_HINT:[0-9]+]]} |
| 30 | 80 | // 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} |