| """Classes shared by trace generators and TBMs.""" |
| |
| from __future__ import annotations |
| |
| import collections |
| import dataclasses |
| from dataclasses import dataclass |
| import json |
| import re |
| from typing import Any, Dict, List, Optional, Sequence, Union |
| |
| # Generated by `flatc`. |
| import FBInstruction.Instruction as FBInstr |
| |
| |
| @dataclass(slots=True, kw_only=True) |
| class Instruction: |
| """Class representing an instruction. |
| |
| This is an architecture-neutral representation of an instruction. |
| Architecture specific code involving RISC-V, Arm, x86, etc. should go in |
| trace generation and disassembly. |
| """ |
| |
| addr: int |
| opcode: int |
| mnemonic: str |
| operands: Sequence[str] |
| inputs: Sequence[str] |
| outputs: Sequence[str] |
| is_nop: bool |
| is_branch: bool |
| branch_target: Optional[Sequence[int]] |
| is_flush: bool |
| is_vctrl: bool |
| loads: List[int] |
| stores: List[int] |
| lmul: Optional[Union[int, float]] |
| sew: Optional[int] |
| vl: Optional[int] |
| |
| inputs_by_type_cache: Optional[Dict[str, Sequence[str]]] = None |
| outputs_by_type_cache: Optional[Dict[str, Sequence[str]]] = None |
| |
| def __eq__(self, other) -> bool: |
| return id(self) == id(other) |
| |
| def __hash__(self) -> int: |
| return id(self) |
| |
| def to_json(self): |
| # For efficiency, and to match the FB: |
| inputs_by_type_cache = self.inputs_by_type_cache |
| self.inputs_by_type_cache = None |
| outputs_by_type_cache = self.outputs_by_type_cache |
| self.outputs_by_type_cache = None |
| |
| res = json.dumps(dataclasses.asdict(self)) |
| |
| self.inputs_by_type_cache = inputs_by_type_cache |
| self.outputs_by_type_cache = outputs_by_type_cache |
| |
| return res |
| |
| @classmethod |
| def from_json(cls, s: str) -> Instruction: |
| """Parse JSON string to Instruction.""" |
| d = json.loads(s) |
| return cls(**d) |
| |
| def fb_build(self, builder) -> Any: |
| mnemonic = builder.CreateString(self.mnemonic) |
| |
| operands = [builder.CreateString(x) for x in self.operands] |
| FBInstr.StartOperandsVector(builder, len(operands)) |
| for x in reversed(operands): |
| builder.PrependUOffsetTRelative(x) |
| operands = builder.EndVector() |
| |
| inputs = [builder.CreateString(x) for x in self.inputs] |
| FBInstr.StartInputsVector(builder, len(inputs)) |
| for x in reversed(inputs): |
| builder.PrependUOffsetTRelative(x) |
| inputs = builder.EndVector() |
| |
| outputs = [builder.CreateString(x) for x in self.outputs] |
| FBInstr.StartOutputsVector(builder, len(outputs)) |
| for x in reversed(outputs): |
| builder.PrependUOffsetTRelative(x) |
| outputs = builder.EndVector() |
| |
| FBInstr.StartLoadsVector(builder, len(self.loads)) |
| for l in reversed(self.loads): |
| builder.PrependUint64(l) |
| loads = builder.EndVector() |
| |
| FBInstr.StartStoresVector(builder, len(self.stores)) |
| for s in reversed(self.stores): |
| builder.PrependUint64(s) |
| stores = builder.EndVector() |
| |
| FBInstr.Start(builder) |
| FBInstr.AddAddr(builder, self.addr) |
| FBInstr.AddOpcode(builder, self.opcode) |
| FBInstr.AddMnemonic(builder, mnemonic) |
| FBInstr.AddOperands(builder, operands) |
| FBInstr.AddInputs(builder, inputs) |
| FBInstr.AddOutputs(builder, outputs) |
| FBInstr.AddIsNop(builder, self.is_nop) |
| FBInstr.AddIsBranch(builder, self.is_branch) |
| FBInstr.AddBranchTarget( |
| builder, self.branch_target if self.branch_target else 0) |
| FBInstr.AddIsFlush(builder, self.is_flush) |
| FBInstr.AddIsVctrl(builder, self.is_vctrl) |
| FBInstr.AddLoads(builder, loads) |
| FBInstr.AddStores(builder, stores) |
| FBInstr.AddLmul(builder, |
| float(self.lmul) if self.lmul else 0.0) |
| FBInstr.AddSew(builder, self.sew if self.sew else 0) |
| FBInstr.AddVl(builder, self.vl if self.vl else -1) |
| return FBInstr.End(builder) |
| |
| @classmethod |
| def from_fb(cls, buf) -> Instruction: |
| operands = ([ |
| buf.Operands(i).decode("utf-8") for i in range(buf.OperandsLength()) |
| ] if not buf.OperandsIsNone() else []) |
| |
| inputs = ([ |
| buf.Inputs(i).decode("utf-8") for i in range(buf.InputsLength()) |
| ] if not buf.InputsIsNone() else []) |
| |
| outputs = ([ |
| buf.Outputs(i).decode("utf-8") for i in range(buf.OutputsLength()) |
| ] if not buf.OutputsIsNone() else []) |
| |
| loads = ([buf.Loads(i) for i in range(buf.LoadsLength())] |
| if not buf.LoadsIsNone() else []) |
| |
| stores = ([buf.Stores(i) for i in range(buf.StoresLength())] |
| if not buf.StoresIsNone() else []) |
| |
| branch_target = buf.BranchTarget() |
| if branch_target == 0: |
| branch_target = None |
| |
| lmul = buf.Lmul() |
| if lmul == 0.0: |
| lmul = None |
| elif lmul >= 1: |
| lmul = int(lmul) |
| |
| return cls(addr=buf.Addr(), |
| opcode=buf.Opcode(), |
| mnemonic=buf.Mnemonic().decode("utf-8"), |
| operands=operands, |
| inputs=inputs, |
| outputs=outputs, |
| is_nop=buf.IsNop(), |
| is_branch=buf.IsBranch(), |
| branch_target=branch_target, |
| is_flush=buf.IsFlush(), |
| is_vctrl=buf.IsVctrl(), |
| loads=loads, |
| stores=stores, |
| lmul=lmul, |
| sew=buf.Sew(), |
| vl=buf.Vl()) |
| |
| def __str__(self) -> str: |
| return f"(0x{self.addr:x}) {self.mnemonic} {','.join(self.operands)}" |
| |
| def max_emul(self) -> Union[int, float]: |
| """Compute the biggest emul of all the vector registers for the |
| instruction. |
| |
| Returns: |
| biggest emul. |
| """ |
| |
| if self.lmul is not None: |
| if (self.mnemonic.startswith("vw") or |
| self.mnemonic.startswith("vfw") or |
| self.mnemonic.startswith("vn") or |
| self.mnemonic.startswith("vfn")): |
| return 2 * self.lmul |
| |
| return self.lmul |
| |
| return 1 |
| |
| def inputs_by_type(self) -> Dict[str, Sequence[str]]: |
| if self.inputs_by_type_cache is None: |
| self.inputs_by_type_cache = sort_regs_by_type(self.inputs) |
| return self.inputs_by_type_cache |
| |
| def outputs_by_type(self) -> Dict[str, Sequence[str]]: |
| if self.outputs_by_type_cache is None: |
| self.outputs_by_type_cache = sort_regs_by_type(self.outputs) |
| return self.outputs_by_type_cache |
| |
| def conflicts_with(self, other) -> bool: |
| """Are there RAW/WAR/WAW conflicts between the instructions? |
| |
| NOTE: inputs and outputs don't include all the vector registers (e.g. |
| when LMUL > 1, vl8re8.v, vs8r.v), but this is not a problem here, |
| because only vector instructions access vector registers, and we don't |
| care if those are conflicting with each other, as they all go to the |
| same dispatch queue. |
| |
| Args: |
| other: the other instruction to check conflicts with. |
| |
| Returns: |
| True if the instructions have conflicts, otherwise False. |
| """ |
| |
| return (overlaps(self.inputs, other.outputs) or |
| overlaps(self.outputs, other.inputs) or |
| overlaps(self.outputs, other.outputs)) |
| |
| |
| def sort_regs_by_type(regs: Sequence[str]) -> Dict[str, Sequence[str]]: |
| res = {} |
| for reg in regs: |
| rf = register_type(reg) |
| res.setdefault(rf, collections.deque()).append(reg) |
| return res |
| |
| |
| def overlaps(xs: Sequence[str], ys: Sequence[str]) -> bool: |
| return any(x in ys for x in xs) |
| |
| |
| RE_VREG = re.compile(r"v\d+$") |
| RE_FREG = re.compile(r"f\d+$") |
| RE_XREG = re.compile(r"x\d+$") |
| |
| |
| def is_vector_register(r: str) -> bool: |
| """Test whether a register is a vector register.""" |
| # TODO(sflur): ideally this RISC-V specific code would be somewhere else |
| return bool(RE_VREG.match(r)) |
| |
| |
| def is_float_register(r: str) -> bool: |
| """Test whether a register is a floating point register.""" |
| # TODO(sflur): ideally this RISC-V specific code would be somewhere else |
| return bool(RE_FREG.match(r)) |
| |
| |
| def is_int_register(r: str) -> bool: |
| """Test whether a register is a floating point register.""" |
| # TODO(sflur): ideally this RISC-V specific code would be somewhere else |
| # note that this code assumes that ABI names like "ra" have been replaced |
| # with architectural names like "x1" |
| return bool(RE_XREG.match(r)) |
| |
| |
| def register_type(r: str) -> str: |
| """Assign a register to a register class. |
| |
| Args: |
| r: register id |
| |
| Returns: |
| - register file name |
| |
| Classes are |
| - "V": vector RF |
| - "F": float RF |
| - "X": general purpose (integer) RF |
| - "MISC: everything else |
| |
| The MISC class includes various status and control registers |
| and we might want to split it into finer divisions. |
| """ |
| |
| # TODO(sflur): instead of using hardcoded predicates this should be somehow |
| # configurable. There's already something in the uarch yaml file (see |
| # register_files), but I suspect using that will make things much slower. A |
| # posible solution is to hardcode some generic predicates (e.g. a |
| # letter+number) and configure those in the yaml. Also, I don't think a |
| # single config file should be used for gentrace and tbm, as gentrace |
| # config is more fixed. |
| |
| if is_int_register(r): |
| return "X" |
| |
| if is_vector_register(r): |
| return "V" |
| |
| if is_float_register(r): |
| return "F" |
| |
| return "MISC" |