CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
205 lines
7.0 KiB
Python
205 lines
7.0 KiB
Python
# DExTer : Debugging Experience Tester
|
|
# ~~~~~~ ~ ~~ ~ ~~
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
"""This file defines all of the Nodes used to create Dexter scripts. All Nodes must be registered with the yaml
|
|
constructor/representer in `setup_yaml_parser` before loading or printing any script.
|
|
"""
|
|
|
|
import abc
|
|
from dataclasses import dataclass
|
|
from typing import Any, Dict, Optional, Union
|
|
import yaml
|
|
from dex.dextIR.ValueIR import ValueIR
|
|
from dex.utils.Exceptions import Error
|
|
|
|
|
|
def setup_yaml_parser(loader):
|
|
reg_classes = [
|
|
Where,
|
|
Value,
|
|
DexRange,
|
|
]
|
|
for c in reg_classes:
|
|
c.register_yaml(loader)
|
|
|
|
|
|
class DexterNodeError(Error):
|
|
"""Class representing errors with Dexter node parsing."""
|
|
|
|
def __init__(self, node, msg):
|
|
super(DexterNodeError, self).__init__(msg)
|
|
self.msg = msg
|
|
self.node = node
|
|
|
|
def __str__(self):
|
|
return f"Error with node: {self.node}: {self.msg}"
|
|
|
|
|
|
###################
|
|
## Structural Nodes: These are used as keys in the Script, and collectively define Dexter's actions when running a test:
|
|
## how it steps and navigates through the debuggee program, and what information it collects from the
|
|
## debugger.
|
|
|
|
|
|
class Where:
|
|
"""One or more instances of this class define a range of steps in a debugging session. Any expects in the script
|
|
within scope of a "Where" will only be evaluated for the steps where the Where applies.
|
|
"""
|
|
|
|
def __init__(self, attributes: dict):
|
|
self.file: Optional[str] = attributes.pop("file", None)
|
|
self.function: Union[list[str], str, None] = attributes.pop("function", None)
|
|
self.lines: Union[int, DexRange, None] = attributes.pop("lines", None)
|
|
self.after_hit_count: Optional[int] = attributes.pop("after_hit_count", None)
|
|
self.for_hit_count: Optional[int] = attributes.pop("for_hit_count", None)
|
|
self.conditions: dict = attributes.pop("conditions", None)
|
|
if attributes:
|
|
raise DexterNodeError(
|
|
self, f"unexpected attributes {', '.join(attributes)}"
|
|
)
|
|
if (
|
|
not self.function
|
|
and not self.lines
|
|
and (self.for_hit_count or self.after_hit_count)
|
|
):
|
|
raise DexterNodeError(
|
|
self, "can't check hit counts without an explicit lines or function arg"
|
|
)
|
|
|
|
def __repr__(self):
|
|
elts = [
|
|
f"{name}={value}"
|
|
for name, value in self.get_attrs().items()
|
|
if value is not None
|
|
]
|
|
return f"Where(" + ", ".join(elts) + ")"
|
|
|
|
def get_attrs(self) -> Dict[str, Any]:
|
|
return {
|
|
"file": self.file,
|
|
"function": self.function,
|
|
"lines": self.lines,
|
|
"for_hit_count": self.for_hit_count,
|
|
"after_hit_count": self.after_hit_count,
|
|
"conditions": self.conditions,
|
|
}
|
|
|
|
@staticmethod
|
|
def constructor(loader: yaml.Loader, node):
|
|
return Where(loader.construct_mapping(node))
|
|
|
|
@staticmethod
|
|
def representer(dumper: yaml.Dumper, data: "Where"):
|
|
mapping = {
|
|
name: value for name, value in data.get_attrs().items() if value is not None
|
|
}
|
|
return dumper.represent_mapping("!where", mapping, flow_style=True)
|
|
|
|
@staticmethod
|
|
def register_yaml(loader):
|
|
yaml.add_constructor("!where", Where.constructor, loader)
|
|
yaml.add_representer(Where, Where.representer)
|
|
|
|
def get_lines(self) -> range:
|
|
"""Returns the range of line numbers that this Where references, returning an empty range if this Where does not
|
|
refer to any lines."""
|
|
if not self.lines:
|
|
return range(-1)
|
|
if isinstance(self.lines, int):
|
|
return range(self.lines, self.lines + 1)
|
|
assert isinstance(
|
|
self.lines, DexRange
|
|
), f"Invalid type for lines: {self.lines}: ({type(self.lines)})"
|
|
return self.lines.to_range()
|
|
|
|
|
|
###################
|
|
## Expect Nodes: These nodes define the expected outputs from the debugger - they are the only nodes that produce
|
|
## metrics, and map to an expected value in the script.
|
|
|
|
|
|
class Expect:
|
|
"""An expectation of some debugger state that will be compared to actual observed debugger state and generate one
|
|
or more metrics as a measurement of the difference.
|
|
Expects are largely evaluated independently, but may influence each other through the evaluation context.
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_variable_result(value: ValueIR) -> Optional[str]:
|
|
"""For Expects that extract actual results from ValueIR, this method returns that result from the given value,
|
|
excluding any subvalues (i.e. struct members), or None if there is no valid result for this ValueIR.
|
|
"""
|
|
|
|
@abc.abstractmethod
|
|
def get_watched_expr(self) -> str:
|
|
"""Returns the list of expressions that this Expect wants to evaluate."""
|
|
|
|
|
|
class Value(Expect):
|
|
def __init__(self, variable_name: str):
|
|
self.variable_name = variable_name
|
|
self.actual_values = None
|
|
|
|
@staticmethod
|
|
def get_variable_result(value: ValueIR) -> Optional[str]:
|
|
if value.could_evaluate and not (
|
|
value.is_irretrievable or value.is_optimized_away
|
|
):
|
|
return value.value
|
|
return None
|
|
|
|
def get_watched_expr(self) -> str:
|
|
return self.variable_name
|
|
|
|
def __repr__(self):
|
|
return f"Value({self.variable_name})"
|
|
|
|
@staticmethod
|
|
def constructor(loader: yaml.Loader, node):
|
|
return Value(loader.construct_scalar(node))
|
|
|
|
@staticmethod
|
|
def representer(dumper, data):
|
|
return dumper.represent_scalar("!value", data.variable_name)
|
|
|
|
@staticmethod
|
|
def register_yaml(loader):
|
|
yaml.add_constructor("!value", Value.constructor, loader)
|
|
yaml.add_representer(Value, Value.representer)
|
|
|
|
|
|
##############
|
|
## Utility Nodes: Can be used anywhere in a script as a form of syntactic sugar.
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DexRange:
|
|
start: int
|
|
stop: int
|
|
|
|
def __repr__(self) -> str:
|
|
return f"[{self.start} - {self.stop}]"
|
|
|
|
# We use an inclusive range in Dexter scripts, while python ranges are exclusive.
|
|
def to_range(self) -> range:
|
|
return range(self.start, self.stop + 1)
|
|
|
|
@staticmethod
|
|
def constructor(loader: yaml.Loader, node):
|
|
range_seq = loader.construct_sequence(node)
|
|
if len(range_seq) != 2 or not all(isinstance(elt, int) for elt in range_seq):
|
|
raise DexterNodeError(node, "range must have exactly 2 int elements")
|
|
return DexRange(range_seq[0], range_seq[1])
|
|
|
|
@staticmethod
|
|
def representer(dumper, data: "DexRange"):
|
|
return dumper.represent_sequence("!range", [data.start, data.stop])
|
|
|
|
@staticmethod
|
|
def register_yaml(loader):
|
|
yaml.add_constructor("!range", DexRange.constructor, loader)
|
|
yaml.add_representer(DexRange, DexRange.representer)
|