From 416ffa21df71de205e55e8631014c411fdb90659 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sat, 24 Oct 2026 23:50:48 +0530 Subject: [PATCH 01/10] feat: Phase 1 golden RV32I ISS and riscv-tests port Python reference model (decode/encode/disasm, ISS with RVFI-lite retire records, CLI), TinyCPU memory-map linker script and riscv-tests environment, sw Makefile, five asm example programs with pinned output and instret, GNU objdump cross-check, generated web/src/lib/isa.ts, and CI steps. The ISS passes all 40 rv32ui-p-* tests. --- .github/workflows/rtl.yml | 26 ++- .gitignore | 3 +- .gitmodules | 3 + .tools/riscv-gcc.sh | 13 ++ AGENTS.md | 12 +- CLAUDE.md | 12 +- CONTEXT.md | 19 +- README.md | 6 +- docs/PLAN.md | 4 +- scripts/env.sh | 1 + sim/golden/{.gitkeep => __init__.py} | 0 sim/golden/cli.py | 48 ++++ sim/golden/decode.py | 283 ++++++++++++++++++++++++ sim/golden/gen_isa_ts.py | 66 ++++++ sim/golden/iss.py | 314 +++++++++++++++++++++++++++ sim/golden/test_decode.py | 125 +++++++++++ sim/golden/test_iss.py | 296 +++++++++++++++++++++++++ sim/golden/test_programs.py | 58 +++++ sim/golden/test_riscv_tests.py | 40 ++++ sim/golden/test_vs_gnu.py | 48 ++++ sim/programs/.gitkeep | 0 sim/programs/common.h | 70 ++++++ sim/programs/expected.json | 8 + sim/programs/fib.S | 19 ++ sim/programs/hello.S | 12 + sim/programs/memcpy.S | 46 ++++ sim/programs/primes.S | 53 +++++ sim/programs/sort.S | 39 ++++ sw/.gitkeep | 0 sw/Makefile | 51 +++++ sw/crt0.S | 17 ++ sw/env/tinycpu/riscv_test.h | 51 +++++ sw/link.ld | 38 ++++ sw/riscv-tests | 1 + web/src/lib/isa.ts | 69 ++++++ 35 files changed, 1841 insertions(+), 10 deletions(-) create mode 100644 .gitmodules create mode 100755 .tools/riscv-gcc.sh rename sim/golden/{.gitkeep => __init__.py} (100%) create mode 100644 sim/golden/cli.py create mode 100644 sim/golden/decode.py create mode 100644 sim/golden/gen_isa_ts.py create mode 100644 sim/golden/iss.py create mode 100644 sim/golden/test_decode.py create mode 100644 sim/golden/test_iss.py create mode 100644 sim/golden/test_programs.py create mode 100644 sim/golden/test_riscv_tests.py create mode 100644 sim/golden/test_vs_gnu.py delete mode 100644 sim/programs/.gitkeep create mode 100644 sim/programs/common.h create mode 100644 sim/programs/expected.json create mode 100644 sim/programs/fib.S create mode 100644 sim/programs/hello.S create mode 100644 sim/programs/memcpy.S create mode 100644 sim/programs/primes.S create mode 100644 sim/programs/sort.S delete mode 100644 sw/.gitkeep create mode 100644 sw/Makefile create mode 100644 sw/crt0.S create mode 100644 sw/env/tinycpu/riscv_test.h create mode 100644 sw/link.ld create mode 160000 sw/riscv-tests create mode 100644 web/src/lib/isa.ts diff --git a/.github/workflows/rtl.yml b/.github/workflows/rtl.yml index a0b9060..9e9dcea 100644 --- a/.github/workflows/rtl.yml +++ b/.github/workflows/rtl.yml @@ -2,9 +2,9 @@ name: RTL CI on: push: - paths: ['rtl/**', 'sim/**', 'sw/**', '.github/workflows/rtl.yml'] + paths: ['rtl/**', 'sim/**', 'sw/**', '.tools/riscv-gcc.sh', '.gitmodules', '.github/workflows/rtl.yml'] pull_request: - paths: ['rtl/**', 'sim/**', 'sw/**', '.github/workflows/rtl.yml'] + paths: ['rtl/**', 'sim/**', 'sw/**', '.tools/riscv-gcc.sh', '.gitmodules', '.github/workflows/rtl.yml'] jobs: rtl-ci: @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: true # sw/riscv-tests # cocotb 2.x needs Verilator >= 5.036; the ubuntu-24.04 apt package is older, # so build from source and cache by version tag. @@ -44,6 +46,26 @@ jobs: - name: Install Python deps run: pip install -r sim/requirements.txt + - name: Cache RISC-V GCC + uses: actions/cache@v4 + with: + path: .tools/riscv-gcc + key: riscv-gcc-xpack-15.2.0-1-linux-x64 + + - name: Install RISC-V GCC + run: bash .tools/riscv-gcc.sh + + - name: Build riscv-tests and programs + run: | + export PATH="$PWD/.tools/riscv-gcc/bin:$PATH" + make -C sw all + + # Golden ISS: unit tests, official rv32ui-p-* compliance, example programs, GNU cross-check. + - name: Golden ISS tests + run: python -m pytest sim/golden -q + env: + REQUIRE_RISCV_TESTS: '1' # a missing build is a failure, not a skip + - name: Lint RTL run: verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv diff --git a/.gitignore b/.gitignore index 498e77b..69e8882 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,7 @@ web/public/spike/ # Toolchain checkouts .emsdk/ -sw/riscv-tests/build/ +sw/build/ # OS / editor cruft .DS_Store @@ -45,3 +45,4 @@ sw/riscv-tests/build/ .tools/* !.tools/setup.sh !.tools/emsdk.sh +!.tools/riscv-gcc.sh diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5547317 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sw/riscv-tests"] + path = sw/riscv-tests + url = https://github.com/riscv-software-src/riscv-tests.git diff --git a/.tools/riscv-gcc.sh b/.tools/riscv-gcc.sh new file mode 100755 index 0000000..4d0d941 --- /dev/null +++ b/.tools/riscv-gcc.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Downloads the prebuilt xPack riscv-none-elf-gcc into .tools/riscv-gcc (no root needed). +set -euo pipefail +cd "$(dirname "$0")" +VER=15.2.0-1 +URL="https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v${VER}/xpack-riscv-none-elf-gcc-${VER}-linux-x64.tar.gz" +if [ ! -x riscv-gcc/bin/riscv-none-elf-gcc ]; then + curl -sL "$URL" -o riscv-gcc.tar.gz + rm -rf riscv-gcc && mkdir riscv-gcc + tar -xzf riscv-gcc.tar.gz -C riscv-gcc --strip-components=1 + rm riscv-gcc.tar.gz +fi +echo RISCV_GCC_DONE diff --git a/AGENTS.md b/AGENTS.md index ea0d7fa..d4c62c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,8 @@ Pipeline: `rtl/*.sv` -> Verilator C++ -> Emscripten -> `web/public/tiny_cpu.wasm Toolchains are project-local (no root needed). Bootstrap once, then source the env in every shell: ```bash -bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 + Emscripten +bash .tools/setup.sh && bash .tools/emsdk.sh && bash .tools/riscv-gcc.sh # Verilator 5.036, Emscripten, RISC-V GCC +git submodule update --init --depth 1 # sw/riscv-tests python3 -m venv .venv && .venv/bin/pip install -r sim/requirements.txt source scripts/env.sh ``` @@ -24,6 +25,15 @@ source scripts/env.sh Note: `.tools/setup.sh` builds Verilator via CMake, which leaves `PACKAGE_STRING` empty; cocotb parses it, so the script patches `src/config_package.h.in` first. Never export `VERILATOR_ROOT`. +### Software images and golden model (Phase 1) +```bash +make -C sw all # rv32ui riscv-tests + sim/programs -> sw/build/{riscv-tests,programs}/ +python -m pytest sim/golden -q # ISS unit tests, rv32ui-p-*, programs, GNU cross-check +cd sim && python -m golden.cli run ../sw/build/programs/fib --trace-last 10 +cd sim && python -m golden.gen_isa_ts # regenerate web/src/lib/isa.ts after editing decode.py +``` +Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (CI does). + ### RTL lint ```bash verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv # Phase 0; becomes rtl/*.sv diff --git a/CLAUDE.md b/CLAUDE.md index 79cb8a3..6b4027f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,7 +16,8 @@ Pipeline: `rtl/*.sv` -> Verilator C++ -> Emscripten -> `web/public/tiny_cpu.wasm Toolchains are project-local (no root needed). Bootstrap once, then source the env in every shell: ```bash -bash .tools/setup.sh && bash .tools/emsdk.sh # Verilator 5.036 + Emscripten +bash .tools/setup.sh && bash .tools/emsdk.sh && bash .tools/riscv-gcc.sh # Verilator 5.036, Emscripten, RISC-V GCC +git submodule update --init --depth 1 # sw/riscv-tests python3 -m venv .venv && .venv/bin/pip install -r sim/requirements.txt source scripts/env.sh ``` @@ -24,6 +25,15 @@ source scripts/env.sh Note: `.tools/setup.sh` builds Verilator via CMake, which leaves `PACKAGE_STRING` empty; cocotb parses it, so the script patches `src/config_package.h.in` first. Never export `VERILATOR_ROOT`. +### Software images and golden model (Phase 1) +```bash +make -C sw all # rv32ui riscv-tests + sim/programs -> sw/build/{riscv-tests,programs}/ +python -m pytest sim/golden -q # ISS unit tests, rv32ui-p-*, programs, GNU cross-check +cd sim && python -m golden.cli run ../sw/build/programs/fib --trace-last 10 +cd sim && python -m golden.gen_isa_ts # regenerate web/src/lib/isa.ts after editing decode.py +``` +Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (CI does). + ### RTL lint ```bash verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv # Phase 0; becomes rtl/*.sv diff --git a/CONTEXT.md b/CONTEXT.md index b13bcc6..5919610 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -11,7 +11,18 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on ## Status - Phase 0 (scaffold + toolchain spike): DONE. Verified locally: Verilator lint, cocotb (4 tests), WASM spike with two Verilated models in one module (Node + browser), web lint/typecheck/build. -- Next: Phase 1 (Python golden ISS + riscv-tests port). +- Phase 1 (golden ISS + riscv-tests port): DONE. `sim/golden/` (decode, encode, disasm, ISS, CLI) passes all 40 + rv32ui-p-* tests, 5 asm programs with pinned console output and instret, and a decode/encode cross-check against + GNU objdump. `web/src/lib/isa.ts` is generated from `decode.py`. +- Next: Phase 2 (building blocks + single-cycle core). + +## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) +- Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. +- Image format: `NAME.imem.bin` from 0x0, `NAME.dmem.bin` from 0x10000 (raw little-endian bytes; `.rodata` is in dmem). +- Program pass/fail: write to TOHOST; 1 = PASS, riscv-tests FAIL = (testnum<<1)|1. Faults (illegal insn, bus fault, + misaligned jump) raise in the ISS; the RTL's behaviour there is undefined, so tests never rely on it. +- `ecall`/`ebreak`/TOHOST halt after the instruction retires. Only `csrrs rd, , x0` exists. +- Single-cycle core must retire exactly `instret` instructions in `instret` cycles (`sim/programs/expected.json`). ## Gotchas found in Phase 0 - Emscripten lacks pthread affinity APIs; `verilated_threads.cpp` is still required, so link `wasm/emscripten_stubs.cpp`. @@ -37,3 +48,9 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Web: Astro + React islands + Tailwind, TypeScript strict. WASM is loaded only inside `client:only="react"` islands, never during SSR. - `docs/STATE_SCHEMA.md` and `web/src/lib/state-schema.ts` must stay in sync (from Phase 5). + +## Gotchas found in Phase 1 +- No RISC-V target in Emscripten's clang: `.tools/riscv-gcc.sh` downloads the xPack `riscv-none-elf-gcc` (15.2.0-1). +- Build with `-march=rv32i_zicsr_zifencei`; `sw/Makefile` needs `.SECONDARY:` or make deletes the `.imem.bin` intermediates. +- Harvard => `.rodata` must live in DMEM (see `sw/link.ld`), never in IMEM. +- Many rv32ui tests just `#include` the rv64ui source; `sw/env/tinycpu/riscv_test.h` defines `RVTEST_RV64U` for that. diff --git a/README.md b/README.md index 5250e47..e5847a2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 0 (scaffold + toolchain spike).** The CPU itself starts in Phase 1-2. +> **Status: Phase 1 done (golden model).** A Python RV32I ISS passes all 40 official `rv32ui-p-*` tests; the RTL starts in Phase 2. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the @@ -35,8 +35,8 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl | Path | Contents | |---|---| | `rtl/` | SystemVerilog (`spike/counter.sv` is the Phase 0 toolchain check) | -| `sim/` | cocotb tests, golden ISS (Phase 1) | -| `sw/` | linker script, crt0, riscv-tests port (Phase 1) | +| `sim/` | golden ISS (`sim/golden/`), cocotb tests, asm programs (`sim/programs/`) | +| `sw/` | linker script, crt0, riscv-tests port + Makefile | | `wasm/` | Verilator -> Emscripten harness | | `web/` | Astro + React visualizer | | `fpga/` | Yosys/nextpnr flows (Phase 9) | diff --git a/docs/PLAN.md b/docs/PLAN.md index 25e5e7c..24a4ec0 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -91,6 +91,8 @@ rvfi_mem_addr, rvfi_mem_rmask, rvfi_mem_wmask, rvfi_mem_rdata, rvfi_mem_wdata, rvfi_halt ``` +Phase 1 additions (implemented in `sim/golden/iss.py`): `pc_wdata` (next PC), `misaligned` (load/store was forced into alignment) and `cycle_dependent` (rd value depends on timing: `rdcycle`/`rdtime`/CYCLE MMIO; lockstep masks it). `mem_addr` is the word-aligned address, masks are byte lanes within it, and `mem_rdata`/`mem_wdata` are lane-positioned words. + --- ## 3. Repository layout (mirrors tiny-tpu) @@ -197,7 +199,7 @@ Four layers. Each phase below says which layers must be green. readable diff with the disassembly of the last 8 retired instructions. 3. **Official compliance:** all `rv32ui-p-*` tests from `riscv-tests` on **both** cores, using a custom `env/tinycpu` (our `link.ld`, with `RVTEST_PASS/FAIL` writing `TOHOST`). - Excluded: `fence_i`, `ma_data`. Expected: 37–39 tests pass, depending on the riscv-tests revision. + Excluded: `fence_i`, `ma_data`. Verified in Phase 1: 40 tests (42 sources minus those two) on riscv-tests `3df703c`; the largest, `ld_st`, is 3732 B and fits the 4 KiB IMEM. 4. **Random programs:** `test_random.py` generates N programs (seeded) of 200–2000 instructions, weighted toward hazards: back-to-back RAW, load-use, branch after load, store after load, writes to x0, JALR to computed targets. Every program runs in lockstep, with diff --git a/scripts/env.sh b/scripts/env.sh index 07c2e75..dcb8ff0 100644 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -6,5 +6,6 @@ _ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" unset VERILATOR_ROOT # Verilator derives it from its own location; setting it breaks it export PATH="$_ROOT/.tools/verilator-install/bin:$_ROOT/.venv/bin:$PATH" +[ -d "$_ROOT/.tools/riscv-gcc/bin" ] && export PATH="$_ROOT/.tools/riscv-gcc/bin:$PATH" [ -f "$_ROOT/.tools/emsdk/emsdk_env.sh" ] && source "$_ROOT/.tools/emsdk/emsdk_env.sh" >/dev/null 2>&1 unset _ROOT diff --git a/sim/golden/.gitkeep b/sim/golden/__init__.py similarity index 100% rename from sim/golden/.gitkeep rename to sim/golden/__init__.py diff --git a/sim/golden/cli.py b/sim/golden/cli.py new file mode 100644 index 0000000..599cd61 --- /dev/null +++ b/sim/golden/cli.py @@ -0,0 +1,48 @@ +"""Run a built image on the golden ISS. + + python -m golden.cli run ../sw/build/programs/fib # prints console + summary + python -m golden.cli run ../sw/build/programs/fib --trace # + every retired instruction + python -m golden.cli run --trace-last 20 # only the last 20 retires + + is the path without `.imem.bin` / `.dmem.bin` (see sw/Makefile). Exit status is 0 on PASS. +""" + +from __future__ import annotations + +import argparse +import sys + +from golden.iss import Iss, IssError + + +def main(argv: list[str] | None = None) -> int: + ap = argparse.ArgumentParser(prog="golden.cli", description=__doc__.split("\n")[0]) + sub = ap.add_subparsers(dest="cmd", required=True) + run = sub.add_parser("run", help="run an image to halt") + run.add_argument("stem") + run.add_argument("--max", type=int, default=5_000_000, help="instruction limit") + run.add_argument("--trace", action="store_true", help="print every retire record") + run.add_argument("--trace-last", type=int, metavar="N", help="print the last N retire records") + args = ap.parse_args(argv) + + iss = Iss.from_build(args.stem) + trace = [] + try: + trace = iss.run(args.max) + except (IssError, TimeoutError) as exc: + print(f"ERROR: {exc}", file=sys.stderr) + return 2 + finally: + if args.trace: + print(*trace, sep="\n") + elif args.trace_last: + print(*trace[-args.trace_last :], sep="\n") + + print(iss.console_text, end="" if iss.console_text.endswith("\n") else "\n") + status = "PASS" if iss.passed else f"FAIL(tohost={iss.tohost})" + print(f"[{status}] {iss.instret} instructions, halted by {iss.halt_reason}", file=sys.stderr) + return 0 if iss.passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/sim/golden/decode.py b/sim/golden/decode.py new file mode 100644 index 0000000..dcb8f4d --- /dev/null +++ b/sim/golden/decode.py @@ -0,0 +1,283 @@ +"""RV32I decode tables, encoder and disassembler. Single source of truth for the ISA. + +`web/src/lib/isa.ts` is generated from INSNS by gen_isa_ts.py, and the RTL decoder tests +(Phase 2) derive their expected control bundles from here, so Python, TypeScript and +SystemVerilog all agree on one opcode table. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +M32 = 0xFFFFFFFF + +REG_NAMES = ( + "zero ra sp gp tp t0 t1 t2 s0 s1 a0 a1 a2 a3 a4 a5 a6 a7 " + "s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 t3 t4 t5 t6" +).split() + +# Formats. SH = shift-immediate (shamt in the rs2 field), CSR = Zicntr read, SYS = ecall/ebreak. +FORMATS = ("R", "I", "S", "B", "U", "J", "SH", "FENCE", "CSR", "SYS") + +# CSRs the core implements (read-only counters, Zicntr subset). +CSR_NAMES = { + 0xC00: "cycle", + 0xC01: "time", + 0xC02: "instret", + 0xC80: "cycleh", + 0xC81: "timeh", + 0xC82: "instreth", +} + + +@dataclass(frozen=True) +class InsnDef: + name: str + fmt: str + mask: int + match: int + + +def _def(name: str, fmt: str, opcode: int, f3: int | None = None, f7: int | None = None) -> InsnDef: + mask, match = 0x7F, opcode + if f3 is not None: + mask |= 0x7 << 12 + match |= f3 << 12 + if f7 is not None: + mask |= 0x7F << 25 + match |= f7 << 25 + return InsnDef(name, fmt, mask, match) + + +INSNS: tuple[InsnDef, ...] = ( + _def("lui", "U", 0x37), + _def("auipc", "U", 0x17), + _def("jal", "J", 0x6F), + _def("jalr", "I", 0x67, 0), + _def("beq", "B", 0x63, 0), + _def("bne", "B", 0x63, 1), + _def("blt", "B", 0x63, 4), + _def("bge", "B", 0x63, 5), + _def("bltu", "B", 0x63, 6), + _def("bgeu", "B", 0x63, 7), + _def("lb", "I", 0x03, 0), + _def("lh", "I", 0x03, 1), + _def("lw", "I", 0x03, 2), + _def("lbu", "I", 0x03, 4), + _def("lhu", "I", 0x03, 5), + _def("sb", "S", 0x23, 0), + _def("sh", "S", 0x23, 1), + _def("sw", "S", 0x23, 2), + _def("addi", "I", 0x13, 0), + _def("slti", "I", 0x13, 2), + _def("sltiu", "I", 0x13, 3), + _def("xori", "I", 0x13, 4), + _def("ori", "I", 0x13, 6), + _def("andi", "I", 0x13, 7), + _def("slli", "SH", 0x13, 1, 0x00), + _def("srli", "SH", 0x13, 5, 0x00), + _def("srai", "SH", 0x13, 5, 0x20), + _def("add", "R", 0x33, 0, 0x00), + _def("sub", "R", 0x33, 0, 0x20), + _def("sll", "R", 0x33, 1, 0x00), + _def("slt", "R", 0x33, 2, 0x00), + _def("sltu", "R", 0x33, 3, 0x00), + _def("xor", "R", 0x33, 4, 0x00), + _def("srl", "R", 0x33, 5, 0x00), + _def("sra", "R", 0x33, 5, 0x20), + _def("or", "R", 0x33, 6, 0x00), + _def("and", "R", 0x33, 7, 0x00), + _def("fence", "FENCE", 0x0F, 0), + InsnDef("ecall", "SYS", 0xFFFFFFFF, 0x00000073), + InsnDef("ebreak", "SYS", 0xFFFFFFFF, 0x00100073), + _def("csrrs", "CSR", 0x73, 2), +) + +BY_NAME = {d.name: d for d in INSNS} +LOADS = {"lb", "lh", "lw", "lbu", "lhu"} +STORES = {"sb", "sh", "sw"} +BRANCHES = {"beq", "bne", "blt", "bge", "bltu", "bgeu"} + + +def sext(value: int, bits: int) -> int: + value &= (1 << bits) - 1 + return value - (1 << bits) if value & (1 << (bits - 1)) else value + + +def s32(value: int) -> int: + return sext(value, 32) + + +@dataclass(frozen=True) +class Decoded: + name: str + fmt: str + rd: int + rs1: int + rs2: int + imm: int # signed Python int; shamt for SH; unused (0) for R/FENCE/SYS + csr: int + insn: int + + +def imm_i(insn: int) -> int: + return sext(insn >> 20, 12) + + +def imm_s(insn: int) -> int: + return sext(((insn >> 25) << 5) | ((insn >> 7) & 0x1F), 12) + + +def imm_b(insn: int) -> int: + raw = ( + ((insn >> 31) << 12) + | (((insn >> 7) & 1) << 11) + | (((insn >> 25) & 0x3F) << 5) + | (((insn >> 8) & 0xF) << 1) + ) + return sext(raw, 13) + + +def imm_u(insn: int) -> int: + return s32(insn & 0xFFFFF000) + + +def imm_j(insn: int) -> int: + raw = ( + ((insn >> 31) << 20) + | (((insn >> 12) & 0xFF) << 12) + | (((insn >> 20) & 1) << 11) + | (((insn >> 21) & 0x3FF) << 1) + ) + return sext(raw, 21) + + +def lookup(insn: int) -> InsnDef | None: + insn &= M32 + for d in INSNS: + if insn & d.mask == d.match: + return d + return None + + +def decode(insn: int) -> Decoded | None: + """Decode a 32-bit word; None if it is not a valid RV32I (+Zicntr read) instruction.""" + insn &= M32 + d = lookup(insn) + if d is None: + return None + rd, rs1, rs2 = (insn >> 7) & 31, (insn >> 15) & 31, (insn >> 20) & 31 + imm = csr = 0 + f = d.fmt + if f == "R": + pass + elif f == "I": + imm, rs2 = imm_i(insn), 0 + elif f == "SH": + imm, rs2 = rs2, 0 + elif f == "S": + imm, rd = imm_s(insn), 0 + elif f == "B": + imm, rd = imm_b(insn), 0 + elif f == "U": + imm, rs1, rs2 = imm_u(insn), 0, 0 + elif f == "J": + imm, rs1, rs2 = imm_j(insn), 0, 0 + elif f == "CSR": + csr, rs2 = (insn >> 20) & 0xFFF, 0 + else: # FENCE, SYS: no architectural operands for this core + rd = rs1 = rs2 = 0 + return Decoded(d.name, f, rd, rs1, rs2, imm, csr, insn) + + +# ---------------------------------------------------------------- encoder + + +def _check_imm(name: str, imm: int, bits: int, align: int = 1) -> None: + lo, hi = -(1 << (bits - 1)), (1 << (bits - 1)) - 1 + if not lo <= imm <= hi or imm % align: + raise ValueError(f"{name}: immediate {imm} out of range/alignment ({bits}-bit, x{align})") + + +def encode(name: str, rd: int = 0, rs1: int = 0, rs2: int = 0, imm: int = 0, csr: int = 0) -> int: + """Encode one instruction. `imm` is signed (U-type: the 20-bit field value << 12 as signed + or unsigned, B/J: byte offsets). For SH the shift amount is `imm`.""" + d = BY_NAME[name] + for r in (rd, rs1, rs2): + if not 0 <= r < 32: + raise ValueError(f"{name}: register {r} out of range") + w, f = d.match, d.fmt + if f == "R": + w |= (rd << 7) | (rs1 << 15) | (rs2 << 20) + elif f == "I": + _check_imm(name, imm, 12) + w |= (rd << 7) | (rs1 << 15) | ((imm & 0xFFF) << 20) + elif f == "SH": + if not 0 <= imm < 32: + raise ValueError(f"{name}: shamt {imm} out of range") + w |= (rd << 7) | (rs1 << 15) | (imm << 20) + elif f == "S": + _check_imm(name, imm, 12) + w |= ((imm & 0x1F) << 7) | (rs1 << 15) | (rs2 << 20) | (((imm >> 5) & 0x7F) << 25) + elif f == "B": + _check_imm(name, imm, 13, 2) + w |= ( + (((imm >> 11) & 1) << 7) + | (((imm >> 1) & 0xF) << 8) + | (rs1 << 15) + | (rs2 << 20) + | (((imm >> 5) & 0x3F) << 25) + | (((imm >> 12) & 1) << 31) + ) + elif f == "U": + w |= (rd << 7) | (imm & 0xFFFFF000) + elif f == "J": + _check_imm(name, imm, 21, 2) + w |= ( + (rd << 7) + | (((imm >> 12) & 0xFF) << 12) + | (((imm >> 11) & 1) << 20) + | (((imm >> 1) & 0x3FF) << 21) + | (((imm >> 20) & 1) << 31) + ) + elif f == "CSR": + w |= (rd << 7) | (rs1 << 15) | ((csr & 0xFFF) << 20) + # FENCE / SYS: fixed encodings (fence = plain `fence` with zero fields) + return w & M32 + + +# ---------------------------------------------------------------- disassembler + + +def disasm(insn: int, pc: int | None = None) -> str: + """ABI-name disassembly. With `pc`, branch/jump targets are shown as absolute addresses.""" + x = decode(insn) + if x is None: + return f".word 0x{insn & M32:08x}" + r = REG_NAMES + n, f = x.name, x.fmt + + def target() -> str: + return f"0x{(pc + x.imm) & M32:x}" if pc is not None else f"{x.imm:+d}" + + if f == "R": + return f"{n} {r[x.rd]}, {r[x.rs1]}, {r[x.rs2]}" + if f == "SH": + return f"{n} {r[x.rd]}, {r[x.rs1]}, {x.imm}" + if f == "I": + if n in LOADS: + return f"{n} {r[x.rd]}, {x.imm}({r[x.rs1]})" + if n == "jalr": + return f"{n} {r[x.rd]}, {x.imm}({r[x.rs1]})" + return f"{n} {r[x.rd]}, {r[x.rs1]}, {x.imm}" + if f == "S": + return f"{n} {r[x.rs2]}, {x.imm}({r[x.rs1]})" + if f == "B": + return f"{n} {r[x.rs1]}, {r[x.rs2]}, {target()}" + if f == "U": + return f"{n} {r[x.rd]}, 0x{(x.imm & M32) >> 12:x}" + if f == "J": + return f"{n} {r[x.rd]}, {target()}" + if f == "CSR": + return f"{n} {r[x.rd]}, {CSR_NAMES.get(x.csr, hex(x.csr))}, {r[x.rs1]}" + return n diff --git a/sim/golden/gen_isa_ts.py b/sim/golden/gen_isa_ts.py new file mode 100644 index 0000000..2a47d60 --- /dev/null +++ b/sim/golden/gen_isa_ts.py @@ -0,0 +1,66 @@ +"""Generate web/src/lib/isa.ts from decode.py so the TypeScript assembler/disassembler and the +Python golden model share one opcode table. + + python -m golden.gen_isa_ts # rewrite the file + python -m golden.gen_isa_ts --check # exit 1 if it is stale (used by pytest/CI) +""" + +from __future__ import annotations + +import sys +from pathlib import Path + +from golden.decode import CSR_NAMES, FORMATS, INSNS, REG_NAMES + +OUT = Path(__file__).resolve().parents[2] / "web" / "src" / "lib" / "isa.ts" + + +def render() -> str: + rows = "\n".join( + f' {{ name: "{d.name}", fmt: "{d.fmt}", mask: 0x{d.mask:08x}, match: 0x{d.match:08x} }},' + for d in INSNS + ) + regs = ", ".join(f'"{r}"' for r in REG_NAMES) + csrs = "\n".join(f' 0x{k:03x}: "{v}",' for k, v in CSR_NAMES.items()) + fmts = " | ".join(f'"{f}"' for f in FORMATS) + return f"""// GENERATED by sim/golden/gen_isa_ts.py from sim/golden/decode.py - do not edit. +// Regenerate: (cd sim && python -m golden.gen_isa_ts) + +export type InsnFormat = {fmts}; + +export interface InsnDef {{ + name: string; + fmt: InsnFormat; + /** An instruction word w matches when (w & mask) === match. */ + mask: number; + match: number; +}} + +export const INSNS: readonly InsnDef[] = [ +{rows} +]; + +/** ABI register names, indexed by register number. */ +export const REG_NAMES: readonly string[] = [{regs}]; + +/** Implemented read-only counter CSRs (Zicntr subset). */ +export const CSR_NAMES: Readonly> = {{ +{csrs} +}}; +""" + + +def main(argv: list[str]) -> int: + text = render() + if "--check" in argv: + if not OUT.exists() or OUT.read_text() != text: + print(f"{OUT} is stale: run `python -m golden.gen_isa_ts` from sim/", file=sys.stderr) + return 1 + return 0 + OUT.write_text(text) + print(f"wrote {OUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/sim/golden/iss.py b/sim/golden/iss.py new file mode 100644 index 0000000..71f134a --- /dev/null +++ b/sim/golden/iss.py @@ -0,0 +1,314 @@ +"""Golden RV32I instruction-set simulator for TinyCPU. + +This is the reference the RTL is checked against (docs/PLAN.md section 5). It models the +architecture, not the pipeline: one instruction retires per step() and produces one +RVFI-lite `Retire` record. Keep it small and obviously correct. + +Memory map (docs/PLAN.md section 2.1), Harvard: + IMEM 0x0000_0000 4 KiB fetch only (the data port cannot read or write it) + DMEM 0x0001_0000 4 KiB byte-addressable, LB/LH/LW/LBU/LHU/SB/SH/SW + CONSOLE 0x0002_0000 write: lane-0 byte is sent to the console + TOHOST 0x0002_0004 SW only: halts the core. 1 = PASS, anything else = FAIL code + CYCLE 0x0002_0008 read-only cycle counter + +Deliberate behaviours the RTL must match: + * Misaligned loads/stores are NOT supported: the address is forced into alignment and the + retire record carries misaligned=True. Misaligned jump/branch targets raise. + * ecall / ebreak / a TOHOST write halt the core; the halting instruction still retires. + * Cycle-dependent values (CYCLE MMIO, rdcycle/rdtime) return the retired-instruction count + here, which is NOT what a pipelined core returns. The lockstep harness masks the + destination value of those instructions (see `Retire.cycle_dependent`). +""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Callable + +from golden.decode import ( + BRANCHES, + CSR_NAMES, + LOADS, + M32, + STORES, + Decoded, + decode, + disasm, + s32, +) + +IMEM_BASE, IMEM_SIZE = 0x0000_0000, 4096 +DMEM_BASE, DMEM_SIZE = 0x0001_0000, 4096 +CONSOLE_ADDR = 0x0002_0000 +TOHOST_ADDR = 0x0002_0004 +CYCLE_ADDR = 0x0002_0008 +MMIO_BASE, MMIO_END = CONSOLE_ADDR, CYCLE_ADDR + 4 + +PASS_CODE = 1 + + +class IssError(Exception): + """The program did something the core does not define (trap-less design: this is fatal).""" + + def __init__(self, reason: str, pc: int, insn: int | None = None): + self.reason, self.pc, self.insn = reason, pc, insn + detail = f" insn=0x{insn:08x} ({disasm(insn, pc)})" if insn is not None else "" + super().__init__(f"{reason} at pc=0x{pc:08x}{detail}") + + +class IllegalInstruction(IssError): + pass + + +class BusFault(IssError): + pass + + +class MisalignedFetch(IssError): + pass + + +@dataclass +class Retire: + """RVFI-lite retire record (docs/PLAN.md section 2.4). + + mem_addr is the word-aligned address; masks are byte lanes within that word; mem_rdata and + mem_wdata are lane-positioned 32-bit words (unselected lanes are zero), exactly what the + LSU datapath carries. + """ + + order: int + pc: int + insn: int + rd_addr: int # 0 when no register is written (including writes to x0) + rd_wdata: int # 0 when rd_addr == 0 + pc_wdata: int # address of the next instruction + mem_addr: int = 0 + mem_rmask: int = 0 + mem_wmask: int = 0 + mem_rdata: int = 0 + mem_wdata: int = 0 + misaligned: bool = False + halt: bool = False + halt_reason: str = "" # "", "ecall", "ebreak", "tohost"; informational, not compared + cycle_dependent: bool = False # rd_wdata/mem_rdata depend on timing; lockstep must mask them + + def asdict(self) -> dict: + return asdict(self) + + def __str__(self) -> str: + s = f"#{self.order:<5} pc=0x{self.pc:08x} {disasm(self.insn, self.pc):<28}" + if self.rd_addr: + s += f" x{self.rd_addr}=0x{self.rd_wdata:08x}" + if self.mem_rmask: + s += f" R[0x{self.mem_addr:08x}/{self.mem_rmask:04b}]=0x{self.mem_rdata:08x}" + if self.mem_wmask: + s += f" W[0x{self.mem_addr:08x}/{self.mem_wmask:04b}]=0x{self.mem_wdata:08x}" + return s + (f" HALT({self.halt_reason})" if self.halt else "") + + +_SIZE = {"lb": 1, "lbu": 1, "sb": 1, "lh": 2, "lhu": 2, "sh": 2, "lw": 4, "sw": 4} + + +class Iss: + def __init__( + self, + imem: bytes = b"", + dmem: bytes = b"", + on_console: Callable[[int], None] | None = None, + ): + self.imem = bytearray(IMEM_SIZE) + self.dmem = bytearray(DMEM_SIZE) + self.on_console = on_console + self.console = bytearray() + self.regs = [0] * 32 + self.pc = IMEM_BASE + self.instret = 0 + self.halted = False + self.halt_reason = "" + self.tohost: int | None = None + self.load(imem, dmem) + + # ---------------------------------------------------------------- loading + def load(self, imem: bytes, dmem: bytes) -> None: + if len(imem) > IMEM_SIZE or len(dmem) > DMEM_SIZE: + raise ValueError(f"image too large: imem {len(imem)}/{IMEM_SIZE}, dmem {len(dmem)}/{DMEM_SIZE}") + self.imem[:] = bytes(imem).ljust(IMEM_SIZE, b"\0") + self.dmem[:] = bytes(dmem).ljust(DMEM_SIZE, b"\0") + + @classmethod + def from_files(cls, imem_path: str | Path, dmem_path: str | Path | None = None, **kw) -> "Iss": + dmem = Path(dmem_path).read_bytes() if dmem_path and Path(dmem_path).exists() else b"" + return cls(Path(imem_path).read_bytes(), dmem, **kw) + + @classmethod + def from_build(cls, stem: str | Path, **kw) -> "Iss": + """Load `.imem.bin` + `.dmem.bin` as produced by sw/Makefile.""" + stem = str(stem) + return cls.from_files(stem + ".imem.bin", stem + ".dmem.bin", **kw) + + # ---------------------------------------------------------------- results + @property + def passed(self) -> bool: + return self.tohost == PASS_CODE + + @property + def console_text(self) -> str: + return self.console.decode("latin-1") + + # ---------------------------------------------------------------- memory + def _fetch(self, pc: int) -> int: + if pc & 3: + raise MisalignedFetch("misaligned fetch", pc) + if not IMEM_BASE <= pc < IMEM_BASE + IMEM_SIZE: + raise BusFault("fetch outside IMEM", pc) + return int.from_bytes(self.imem[pc : pc + 4], "little") + + def _load(self, name: str, addr: int, pc: int, insn: int) -> tuple[int, int, int, int, bool]: + """-> (value, word_addr, rmask, lane_positioned_rdata, misaligned).""" + size = _SIZE[name] + aligned = addr & ~(size - 1) + word_addr, off = aligned & ~3, aligned & 3 + mask = ((1 << size) - 1) << off + if DMEM_BASE <= word_addr < DMEM_BASE + DMEM_SIZE: + o = word_addr - DMEM_BASE + word = int.from_bytes(self.dmem[o : o + 4], "little") + elif word_addr == CYCLE_ADDR: + word = self.instret & M32 + else: + raise BusFault(f"load from unmapped/unreadable address 0x{addr:08x}", pc, insn) + lanes = word & sum(0xFF << (8 * i) for i in range(4) if mask >> i & 1) + raw = (lanes >> (8 * off)) & ((1 << (8 * size)) - 1) + if name in ("lb", "lh"): + raw = (raw - (1 << (8 * size))) & M32 if raw >> (8 * size - 1) else raw + return raw, word_addr, mask, lanes, aligned != addr + + def _store(self, name: str, addr: int, value: int, pc: int, insn: int) -> tuple[int, int, int, bool]: + """-> (word_addr, wmask, lane_positioned_wdata, misaligned).""" + size = _SIZE[name] + aligned = addr & ~(size - 1) + word_addr, off = aligned & ~3, aligned & 3 + mask = ((1 << size) - 1) << off + wdata = ((value & ((1 << (8 * size)) - 1)) << (8 * off)) & M32 + if DMEM_BASE <= word_addr < DMEM_BASE + DMEM_SIZE: + o = word_addr - DMEM_BASE + for i in range(4): + if mask >> i & 1: + self.dmem[o + i] = (wdata >> (8 * i)) & 0xFF + elif word_addr == CONSOLE_ADDR: + if mask & 1: + ch = wdata & 0xFF + self.console.append(ch) + if self.on_console: + self.on_console(ch) + elif word_addr == TOHOST_ADDR: + if mask != 0xF: + raise BusFault("TOHOST accepts word stores only", pc, insn) + self.tohost = wdata + self.halted, self.halt_reason = True, "tohost" + else: + raise BusFault(f"store to unmapped/read-only address 0x{addr:08x}", pc, insn) + return word_addr, mask, wdata, aligned != addr + + # ---------------------------------------------------------------- execute + def step(self) -> Retire: + if self.halted: + raise RuntimeError("core is halted") + pc = self.pc + insn = self._fetch(pc) + x = decode(insn) + if x is None: + raise IllegalInstruction("illegal instruction", pc, insn) + r = self._execute(x, pc) + self.pc = r.pc_wdata + self.instret += 1 + return r + + def _execute(self, x: Decoded, pc: int) -> Retire: + regs, n = self.regs, x.name + a, b = regs[x.rs1], regs[x.rs2] + nxt = (pc + 4) & M32 + rd_val: int | None = None + ret = Retire(self.instret, pc, x.insn, 0, 0, nxt) + + if n == "lui": + rd_val = x.imm & M32 + elif n == "auipc": + rd_val = (pc + x.imm) & M32 + elif n == "jal": + rd_val, nxt = nxt, (pc + x.imm) & M32 + elif n == "jalr": + rd_val, nxt = nxt, ((a + x.imm) & M32) & ~1 + elif n in BRANCHES: + taken = { + "beq": a == b, + "bne": a != b, + "blt": s32(a) < s32(b), + "bge": s32(a) >= s32(b), + "bltu": a < b, + "bgeu": a >= b, + }[n] + if taken: + nxt = (pc + x.imm) & M32 + elif n in LOADS: + val, ret.mem_addr, ret.mem_rmask, ret.mem_rdata, ret.misaligned = self._load( + n, (a + x.imm) & M32, pc, x.insn + ) + ret.cycle_dependent = ret.mem_addr == CYCLE_ADDR + rd_val = val + elif n in STORES: + ret.mem_addr, ret.mem_wmask, ret.mem_wdata, ret.misaligned = self._store( + n, (a + x.imm) & M32, b, pc, x.insn + ) + if self.halted: + ret.halt, ret.halt_reason = True, "tohost" + elif n in ("addi", "slti", "sltiu", "xori", "ori", "andi", "slli", "srli", "srai"): + rd_val = _alu(n[:-1] if n != "sltiu" else "sltu", a, x.imm & M32) + elif n in ("add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and"): + rd_val = _alu(n, a, b) + elif n == "fence": + pass + elif n in ("ecall", "ebreak"): + self.halted, self.halt_reason = True, n + ret.halt, ret.halt_reason = True, n + elif n == "csrrs": + if x.rs1 != 0 or x.csr not in CSR_NAMES: + raise IllegalInstruction("only read-only Zicntr csrrs (rs1=x0) is implemented", pc, x.insn) + rd_val = self.instret & M32 if x.csr in (0xC00, 0xC01, 0xC02) else 0 + ret.cycle_dependent = x.csr in (0xC00, 0xC01) + else: # pragma: no cover - decode() only returns names handled above + raise IllegalInstruction(f"unhandled {n}", pc, x.insn) + + if nxt & 3: + raise MisalignedFetch(f"misaligned jump/branch target 0x{nxt:08x}", pc, x.insn) + if rd_val is not None and x.rd != 0: + regs[x.rd] = rd_val & M32 + ret.rd_addr, ret.rd_wdata = x.rd, rd_val & M32 + ret.pc_wdata = nxt + return ret + + def run(self, max_steps: int = 1_000_000) -> list[Retire]: + """Run to halt; returns every retire record. Raises TimeoutError past max_steps.""" + trace: list[Retire] = [] + while not self.halted: + if len(trace) >= max_steps: + raise TimeoutError(f"no halt after {max_steps} instructions (pc=0x{self.pc:08x})") + trace.append(self.step()) + return trace + + +def _alu(op: str, a: int, b: int) -> int: + sh = b & 31 + return { + "add": (a + b) & M32, + "sub": (a - b) & M32, + "sll": (a << sh) & M32, + "slt": int(s32(a) < s32(b)), + "sltu": int(a < b), + "xor": a ^ b, + "srl": a >> sh, + "sra": (s32(a) >> sh) & M32, + "or": a | b, + "and": a & b, + }[op] diff --git a/sim/golden/test_decode.py b/sim/golden/test_decode.py new file mode 100644 index 0000000..5e4513d --- /dev/null +++ b/sim/golden/test_decode.py @@ -0,0 +1,125 @@ +import random + +import pytest + +from golden.decode import ( + BY_NAME, + INSNS, + REG_NAMES, + decode, + disasm, + encode, + imm_b, + imm_i, + imm_j, + imm_s, + imm_u, + lookup, +) + + +def rand_fields(rng, d): + a = dict(rd=rng.randrange(32), rs1=rng.randrange(32), rs2=rng.randrange(32)) + imm = { + "I": lambda: rng.randint(-2048, 2047), + "SH": lambda: rng.randrange(32), + "S": lambda: rng.randint(-2048, 2047), + "B": lambda: rng.randrange(-2048, 2048) * 2, + "U": lambda: rng.randrange(1 << 20) << 12, + "J": lambda: rng.randrange(-(1 << 19), 1 << 19) * 2, + }.get(d.fmt, lambda: 0)() + return a, imm + + +def test_table_has_every_rv32i_instruction(): + names = {d.name for d in INSNS} + # 38 RV32I base (37 + fence) + ecall/ebreak + the Zicntr csrrs read + assert len(INSNS) == 41 and len(names) == 41 + assert {"lui", "auipc", "jal", "jalr", "sltiu", "srai", "sra", "fence", "ecall"} <= names + + +def test_no_two_definitions_overlap(): + for i, a in enumerate(INSNS): + for b in INSNS[i + 1 :]: + common = a.mask & b.mask + assert a.match & common != b.match & common, f"{a.name} overlaps {b.name}" + + +@pytest.mark.parametrize("d", INSNS, ids=lambda d: d.name) +def test_encode_decode_roundtrip(d): + rng = random.Random(42) + for _ in range(300): + a, imm = rand_fields(rng, d) + w = encode(d.name, imm=imm, csr=0xC00, **a) + x = decode(w) + assert x is not None and x.name == d.name and x.insn == w + assert lookup(w) is BY_NAME[d.name] + if d.fmt in ("I", "SH", "S", "B", "U", "J"): + assert x.imm == (imm if d.fmt != "U" else (imm if imm < 1 << 31 else imm - (1 << 32))) + if d.fmt in ("R", "I", "SH", "U", "J", "CSR"): + assert x.rd == a["rd"] + if d.fmt in ("R", "I", "SH", "S", "B", "CSR"): + assert x.rs1 == a["rs1"] + if d.fmt in ("R", "S", "B"): + assert x.rs2 == a["rs2"] + + +def test_immediate_extremes(): + assert imm_i(encode("addi", imm=-2048)) == -2048 + assert imm_i(encode("addi", imm=2047)) == 2047 + assert imm_s(encode("sw", imm=-1)) == -1 + assert imm_b(encode("beq", imm=-4096)) == -4096 and imm_b(encode("beq", imm=4094)) == 4094 + assert imm_j(encode("jal", imm=-(1 << 20))) == -(1 << 20) + assert imm_j(encode("jal", imm=(1 << 20) - 2)) == (1 << 20) - 2 + assert imm_u(encode("lui", imm=0xFFFFF000)) == -4096 + + +@pytest.mark.parametrize( + "name,kw", + [ + ("addi", dict(imm=2048)), + ("addi", dict(imm=-2049)), + ("beq", dict(imm=3)), # odd offset + ("jal", dict(imm=1 << 20)), + ("slli", dict(imm=32)), + ("add", dict(rd=32)), + ], +) +def test_encode_rejects_bad_operands(name, kw): + with pytest.raises(ValueError): + encode(name, **kw) + + +def test_known_encodings(): + # Reference words produced by GNU as (riscv-none-elf-gcc 15.2). + assert encode("addi", 1, 0, imm=5) == 0x00500093 + assert encode("add", 3, 1, 2) == 0x002081B3 + assert encode("sub", 3, 1, 2) == 0x402081B3 + assert encode("lw", 5, 2, imm=8) == 0x00812283 + assert encode("sw", 0, 2, 5, imm=8) == 0x00512423 + assert encode("lui", 1, imm=0x12345000) == 0x123450B7 + assert encode("jal", 0, imm=0) == 0x0000006F + assert encode("ecall") == 0x00000073 and encode("ebreak") == 0x00100073 + + +def test_illegal_words_do_not_decode(): + for w in (0x00000000, 0xFFFFFFFF, 0x0000007F, 0x40001033, 0x00100073 | 1 << 7): + assert decode(w) is None, hex(w) + + +def test_disassembly(): + assert disasm(0x00500093) == "addi ra, zero, 5" + assert disasm(encode("lw", 10, 2, imm=-4)) == "lw a0, -4(sp)" + assert disasm(encode("sw", 0, 2, 10, imm=12)) == "sw a0, 12(sp)" + assert disasm(encode("beq", 0, 1, 2, imm=-8), pc=0x20) == "beq ra, sp, 0x18" + assert disasm(encode("lui", 3, imm=0x12345000)) == "lui gp, 0x12345" + assert disasm(encode("slli", 1, 2, imm=3)) == "slli ra, sp, 3" + assert disasm(0) == ".word 0x00000000" + assert disasm(encode("csrrs", 5, 0, csr=0xC00)) == "csrrs t0, cycle, zero" + assert len(REG_NAMES) == 32 + + +def test_generated_isa_ts_is_up_to_date(): + from golden.gen_isa_ts import OUT, render + + assert OUT.exists() and OUT.read_text() == render(), "run: cd sim && python -m golden.gen_isa_ts" diff --git a/sim/golden/test_iss.py b/sim/golden/test_iss.py new file mode 100644 index 0000000..0edeb1b --- /dev/null +++ b/sim/golden/test_iss.py @@ -0,0 +1,296 @@ +"""ISS unit tests: instruction semantics, memory lanes, MMIO, faults and retire records.""" + +import pytest + +from golden.decode import encode as e +from golden.iss import ( + CONSOLE_ADDR, + CYCLE_ADDR, + DMEM_BASE, + TOHOST_ADDR, + BusFault, + IllegalInstruction, + Iss, + MisalignedFetch, +) + +M32 = 0xFFFFFFFF + + +def words(*ws): + return b"".join((w & M32).to_bytes(4, "little") for w in ws) + + +def li(rd, value): + """lui+addi pair loading an arbitrary 32-bit constant.""" + value &= M32 + lo = ((value & 0xFFF) ^ 0x800) - 0x800 + return [e("lui", rd, imm=(value - lo) & M32), e("addi", rd, rd, imm=lo)] + + +EXIT = [e("lui", 31, imm=TOHOST_ADDR & ~0xFFF), e("addi", 30, 0, imm=1), e("sw", 0, 31, 30, imm=4)] + + +def run(*prog, dmem=b"", expect_pass=True): + iss = Iss(words(*prog, *EXIT), dmem) + trace = iss.run(10_000) + assert iss.passed is expect_pass + return iss, trace + + +def reg(iss, n): + return iss.regs[n] + + +# ------------------------------------------------------------------ ALU + + +@pytest.mark.parametrize( + "op,a,b,want", + [ + ("add", 0xFFFFFFFF, 1, 0), + ("add", 0x7FFFFFFF, 1, 0x80000000), + ("sub", 0, 1, 0xFFFFFFFF), + ("sub", 0x80000000, 1, 0x7FFFFFFF), + ("sll", 1, 31, 0x80000000), + ("sll", 1, 32, 1), # shift amount is masked to 5 bits + ("srl", 0x80000000, 31, 1), + ("sra", 0x80000000, 31, 0xFFFFFFFF), + ("sra", 0x7FFFFFFF, 31, 0), + ("slt", 0xFFFFFFFF, 0, 1), # -1 < 0 signed + ("sltu", 0xFFFFFFFF, 0, 0), + ("slt", 0x80000000, 0x7FFFFFFF, 1), + ("sltu", 0x80000000, 0x7FFFFFFF, 0), + ("xor", 0xF0F0F0F0, 0xFFFF0000, 0x0F0FF0F0), + ("or", 0xF0F0F0F0, 0x0F0F0F0F, 0xFFFFFFFF), + ("and", 0xF0F0F0F0, 0xFFFF0000, 0xF0F00000), + ], +) +def test_register_alu(op, a, b, want): + iss, _ = run(*li(1, a), *li(2, b), e(op, 3, 1, 2)) + assert reg(iss, 3) == want + + +@pytest.mark.parametrize( + "op,a,imm,want", + [ + ("addi", 5, -6, M32), + ("slti", 0xFFFFFFFE, -1, 1), + ("sltiu", 0, -1, 1), # immediate sign-extends, then compares unsigned + ("sltiu", 0xFFFFFFFF, -1, 0), + ("xori", 0xFF, -1, 0xFFFFFF00), + ("ori", 0x0F, -16, 0xFFFFFFFF), + ("andi", 0xFFFFFFFF, -1, 0xFFFFFFFF), + ("slli", 3, 4, 48), + ("srli", 0x80000000, 4, 0x08000000), + ("srai", 0x80000000, 4, 0xF8000000), + ], +) +def test_immediate_alu(op, a, imm, want): + iss, _ = run(*li(1, a), e(op, 3, 1, imm=imm)) + assert reg(iss, 3) == want + + +def test_x0_is_hardwired_zero_and_discards_writes(): + iss, trace = run(e("addi", 0, 0, imm=123), e("add", 1, 0, 0), e("lui", 0, imm=0x12345000)) + assert reg(iss, 0) == 0 and reg(iss, 1) == 0 + # writes to x0 retire with no destination; the real write to x1 (add x1,x0,x0) does + assert [(r.rd_addr, r.rd_wdata) for r in trace[:3]] == [(0, 0), (1, 0), (0, 0)] + + +def test_lui_auipc(): + iss, _ = run(e("lui", 1, imm=0x80000000), e("auipc", 2, imm=0x1000)) + assert reg(iss, 1) == 0x80000000 + assert reg(iss, 2) == 4 + 0x1000 + + +# ------------------------------------------------------------------ control flow + + +def test_jal_and_jalr_link_and_target(): + iss, trace = run( + e("jal", 1, imm=8), # 0: x1=4, jump to 8 + e("addi", 5, 0, imm=99), # 4: skipped + e("addi", 6, 0, imm=17), # 8 + *li(7, 0x19), # 0xc,0x10: x7 = 0x19 (bit0 set) + e("jalr", 2, 7, imm=0), # 0x14: target 0x19 & ~1 = 0x18, x2 = 0x18 + e("addi", 5, 0, imm=77), # 0x18: executed + ) + assert (reg(iss, 1), reg(iss, 5), reg(iss, 6), reg(iss, 2)) == (4, 77, 17, 0x18) + assert trace[0].pc_wdata == 8 + + +@pytest.mark.parametrize( + "op,a,b,taken", + [ + ("beq", 5, 5, True), ("beq", 5, 6, False), + ("bne", 5, 6, True), ("bne", 5, 5, False), + ("blt", -1, 0, True), ("blt", 0, -1, False), ("blt", 3, 3, False), + ("bge", 0, -1, True), ("bge", -1, 0, False), ("bge", 3, 3, True), + ("bltu", 0, -1, True), ("bltu", -1, 0, False), + ("bgeu", -1, 0, True), ("bgeu", 0, -1, False), ("bgeu", 7, 7, True), + ], +) +def test_branches(op, a, b, taken): + iss, trace = run(*li(1, a), *li(2, b), e(op, 0, 1, 2, imm=8), e("addi", 3, 0, imm=1)) + br = trace[4] # two li pairs occupy 0x0-0xc, so the branch is at 0x10 + assert br.pc == 0x10 and br.pc_wdata == (0x18 if taken else 0x14) + assert reg(iss, 3) == (0 if taken else 1) + + +def test_backward_branch_loop(): + iss, _ = run(e("addi", 1, 0, imm=5), e("addi", 2, 2, imm=3), e("addi", 1, 1, imm=-1), e("bne", 0, 1, 0, imm=-8)) + assert reg(iss, 2) == 15 + + +# ------------------------------------------------------------------ memory + + +def test_load_store_all_sizes_signedness_and_lanes(): + base = DMEM_BASE + iss, trace = run( + *li(1, base), + *li(2, 0x8182_8384), + e("sw", 0, 1, 2, imm=0), + e("lb", 3, 1, imm=0), # 0x84 -> sign-extend + e("lbu", 4, 1, imm=0), + e("lb", 5, 1, imm=3), # 0x81 + e("lh", 6, 1, imm=2), # 0x8182 + e("lhu", 7, 1, imm=2), + e("lh", 8, 1, imm=0), # 0x8384 + e("lw", 9, 1, imm=0), + ) + assert reg(iss, 3) == 0xFFFFFF84 and reg(iss, 4) == 0x84 + assert reg(iss, 5) == 0xFFFFFF81 + assert reg(iss, 6) == 0xFFFF8182 and reg(iss, 7) == 0x8182 + assert reg(iss, 8) == 0xFFFF8384 and reg(iss, 9) == 0x81828384 + by_pc = {r.pc: r for r in trace} + lb3 = next(r for r in trace if r.rd_addr == 5) + assert (lb3.mem_addr, lb3.mem_rmask, lb3.mem_rdata) == (base, 0b1000, 0x81000000) + lh2 = next(r for r in trace if r.rd_addr == 6) + assert (lh2.mem_rmask, lh2.mem_rdata) == (0b1100, 0x81820000) + assert by_pc # trace is keyed by pc without collisions in straight-line code + + +def test_store_byte_and_half_only_touch_their_lanes(): + base = DMEM_BASE + iss, trace = run( + *li(1, base), + *li(2, 0x1122_3344), + e("sw", 0, 1, 2, imm=0), + *li(3, 0xAABBCCDD), + e("sb", 0, 1, 3, imm=1), + e("sh", 0, 1, 3, imm=2), + e("lw", 4, 1, imm=0), + ) + assert reg(iss, 4) == 0xCCDD_DD44 + sb = next(r for r in trace if r.mem_wmask == 0b0010) + assert (sb.mem_addr, sb.mem_wdata) == (base, 0x0000_DD00) + sh = next(r for r in trace if r.mem_wmask == 0b1100) + assert sh.mem_wdata == 0xCCDD_0000 + + +def test_misaligned_access_is_forced_into_alignment_and_flagged(): + base = DMEM_BASE + iss, trace = run( + *li(1, base), + *li(2, 0x4433_2211), + e("sw", 0, 1, 2, imm=0), + e("lw", 3, 1, imm=1), # forced to offset 0 + e("lh", 4, 1, imm=3), # forced to offset 2 + e("lw", 5, 1, imm=0), # aligned control + ) + assert reg(iss, 3) == 0x44332211 and reg(iss, 4) == 0x4433 + flags = [r.misaligned for r in trace if r.mem_rmask] + assert flags == [True, True, False] + + +def test_initial_dmem_image_and_dmem_is_not_instruction_memory(): + iss, _ = run(*li(1, DMEM_BASE), e("lw", 2, 1, imm=4), dmem=bytes(range(8))) + assert reg(iss, 2) == 0x07060504 + + +# ------------------------------------------------------------------ MMIO, halt, csr + + +def test_console_collects_lane0_byte(): + seen = [] + iss = Iss( + words(*li(1, CONSOLE_ADDR), e("addi", 2, 0, imm=ord("H")), e("sb", 0, 1, 2), e("addi", 2, 0, imm=ord("i")), + e("sw", 0, 1, 2, imm=0), *EXIT), + on_console=seen.append, + ) + iss.run() + assert iss.console_text == "Hi" and seen == [72, 105] + + +def test_tohost_codes(): + iss = Iss(words(*li(1, TOHOST_ADDR), e("addi", 2, 0, imm=7), e("sw", 0, 1, 2))) + trace = iss.run() + assert iss.tohost == 7 and not iss.passed + assert trace[-1].halt and trace[-1].halt_reason == "tohost" and trace[-1].mem_wdata == 7 + + +@pytest.mark.parametrize("insn,reason", [(e("ecall"), "ecall"), (e("ebreak"), "ebreak")]) +def test_ecall_ebreak_halt_after_retiring(insn, reason): + iss = Iss(words(e("addi", 1, 0, imm=1), insn, e("addi", 1, 0, imm=2))) + trace = iss.run() + assert len(trace) == 2 and trace[-1].halt and trace[-1].halt_reason == reason + assert reg(iss, 1) == 1 and iss.halted and iss.tohost is None + with pytest.raises(RuntimeError): + iss.step() + + +def test_fence_is_a_nop(): + iss, trace = run(e("fence"), e("addi", 1, 0, imm=3)) + assert reg(iss, 1) == 3 and trace[0].rd_addr == 0 + + +def test_cycle_counters_are_flagged_cycle_dependent(): + iss, trace = run( + e("addi", 1, 0, imm=1), e("csrrs", 2, 0, csr=0xC00), e("csrrs", 3, 0, csr=0xC02), e("csrrs", 4, 0, csr=0xC80), + *li(5, CYCLE_ADDR), e("lw", 6, 5, imm=0), + ) + assert reg(iss, 3) == 2 and reg(iss, 4) == 0 + assert trace[1].cycle_dependent and not trace[2].cycle_dependent + assert next(r for r in trace if r.rd_addr == 6).cycle_dependent + + +# ------------------------------------------------------------------ faults + + +@pytest.mark.parametrize( + "prog,exc", + [ + ([0x00000000], IllegalInstruction), + ([e("csrrs", 1, 1, csr=0xC00)], IllegalInstruction), # rs1 != x0 would write the csr + ([e("csrrs", 1, 0, csr=0x300)], IllegalInstruction), # unknown csr + ([e("jal", 0, imm=6)], MisalignedFetch), + ([e("beq", 0, 0, 0, imm=2)], MisalignedFetch), + ([e("jal", 0, imm=-4)], BusFault), # fetch below IMEM -> wraps outside + ([e("lw", 1, 0, imm=0)], BusFault), # IMEM is not readable by the data port + ([e("sw", 0, 0, 0, imm=0)], BusFault), # ... nor writable + ([e("lui", 1, imm=TOHOST_ADDR & ~0xFFF), e("sb", 0, 1, 0, imm=4)], BusFault), # TOHOST needs sw + ([e("lui", 1, imm=CYCLE_ADDR & ~0xFFF), e("sw", 0, 1, 0, imm=8)], BusFault), # CYCLE is read-only + ], +) +def test_faults(prog, exc): + with pytest.raises(exc): + Iss(words(*prog)).run(100) + + +def test_runaway_program_times_out(): + with pytest.raises(TimeoutError): + Iss(words(e("jal", 0, imm=0))).run(50) + + +def test_oversized_image_rejected(): + with pytest.raises(ValueError): + Iss(bytes(4097)) + + +def test_retire_order_and_pc_chain(): + _, trace = run(e("addi", 1, 0, imm=1), e("jal", 0, imm=8), e("addi", 1, 0, imm=9), e("addi", 2, 0, imm=2)) + assert [r.order for r in trace] == list(range(len(trace))) + for a, b in zip(trace, trace[1:]): + assert a.pc_wdata == b.pc diff --git a/sim/golden/test_programs.py b/sim/golden/test_programs.py new file mode 100644 index 0000000..e29a08b --- /dev/null +++ b/sim/golden/test_programs.py @@ -0,0 +1,58 @@ +"""sim/programs/*.S run on the ISS: exact console output and pinned instruction counts. + +Build first: make -C sw programs (REQUIRE_RISCV_TESTS=1 turns a missing build into a failure) +""" + +import json +import os +from pathlib import Path + +import pytest + +from golden.iss import Iss + +ROOT = Path(__file__).resolve().parents[2] +BUILD = ROOT / "sw" / "build" / "programs" +EXPECTED = json.loads((ROOT / "sim" / "programs" / "expected.json").read_text()) +EXPECTED.pop("_comment") +REQUIRE = os.environ.get("REQUIRE_RISCV_TESTS") == "1" + + +def _build(name): + stem = BUILD / name + if not stem.with_suffix(".imem.bin").exists(): + msg = f"{name} not built (run `make -C sw programs`)" + if REQUIRE: + pytest.fail(msg) + pytest.skip(msg) + return stem + + +def test_every_source_has_an_expectation(): + sources = {p.stem for p in (ROOT / "sim" / "programs").glob("*.S")} + assert sources == set(EXPECTED) + + +def test_expected_console_matches_independent_python(): + """The JSON must not merely echo the ISS: recompute what the programs should print.""" + + def fib(n): + a, b = 0, 1 + for _ in range(n): + a, b = b, a + b + return a + + primes = [n for n in range(2, 61) if all(n % d for d in range(2, int(n**0.5) + 1))] + data = [42, 7, 99, 3, 18, 64, 1, 27, 85, 11, 50, 5] + assert EXPECTED["fib"]["console"] == f"{fib(20)}\n" + assert EXPECTED["sort"]["console"] == "".join(f"{v} " for v in sorted(data)) + "\n" + assert EXPECTED["primes"]["console"] == "".join(f"{p} " for p in primes) + "\n" + + +@pytest.mark.parametrize("name", sorted(EXPECTED)) +def test_program(name): + iss = Iss.from_build(_build(name)) + iss.run(1_000_000) + assert iss.passed, f"tohost={iss.tohost}" + assert iss.console_text == EXPECTED[name]["console"] + assert iss.instret == EXPECTED[name]["instret"] diff --git a/sim/golden/test_riscv_tests.py b/sim/golden/test_riscv_tests.py new file mode 100644 index 0000000..32d69bc --- /dev/null +++ b/sim/golden/test_riscv_tests.py @@ -0,0 +1,40 @@ +"""The ISS must pass the official rv32ui-p-* tests before any RTL exists (Phase 1 exit criterion). + +Build them first: bash .tools/riscv-gcc.sh && source scripts/env.sh && make -C sw riscv-tests +Set REQUIRE_RISCV_TESTS=1 (CI does) to fail instead of skip when the images are missing. +""" + +import os +from pathlib import Path + +import pytest + +from golden.iss import Iss + +BUILD = Path(__file__).resolve().parents[2] / "sw" / "build" / "riscv-tests" +STEMS = sorted(p.name.removesuffix(".imem.bin") for p in BUILD.glob("rv32ui-p-*.imem.bin")) +REQUIRE = os.environ.get("REQUIRE_RISCV_TESTS") == "1" + +# 42 sources in riscv-tests/isa/rv32ui minus fence_i and ma_data (see sw/Makefile). +EXPECTED_COUNT = 40 + + +def test_images_present(): + if not STEMS: + msg = "no riscv-tests images in sw/build/riscv-tests (run `make -C sw riscv-tests`)" + if REQUIRE: + pytest.fail(msg) + pytest.skip(msg) + assert len(STEMS) == EXPECTED_COUNT, f"expected {EXPECTED_COUNT} tests, found {len(STEMS)}" + + +@pytest.mark.parametrize("stem", STEMS or [""]) +def test_rv32ui(stem): + if stem == "": + pytest.skip("riscv-tests not built") + iss = Iss.from_build(BUILD / stem) + trace = iss.run(max_steps=100_000) + tail = "\n".join(str(r) for r in trace[-6:]) + assert iss.passed, ( + f"{stem}: tohost={iss.tohost} -> failed test case #{(iss.tohost or 0) >> 1}\nlast retires:\n{tail}" + ) diff --git a/sim/golden/test_vs_gnu.py b/sim/golden/test_vs_gnu.py new file mode 100644 index 0000000..6c5ba45 --- /dev/null +++ b/sim/golden/test_vs_gnu.py @@ -0,0 +1,48 @@ +"""Cross-check decode/encode against real GNU toolchain output (riscv-tests + our programs). + +For every instruction word GCC emitted: our mnemonic must equal objdump's (aliases disabled), +and re-encoding the decoded fields must reproduce the word bit for bit. +""" + +import os +import re +from pathlib import Path + +import pytest + +from golden.decode import decode, encode + +BUILD = Path(__file__).resolve().parents[2] / "sw" / "build" +DIS = sorted(BUILD.glob("*/*.dis")) +LINE = re.compile(r"^\s*([0-9a-f]+):\s+([0-9a-f]{8})\s+(\S+)") +UNIMP = 0xC0001073 # `unimp` = csrrw cycle,x0 - emitted by RVTEST_CODE_END, never executed + + +def test_dis_files_present(): + if not DIS: + msg = "no .dis files (run `make -C sw all`)" + if os.environ.get("REQUIRE_RISCV_TESTS") == "1": + pytest.fail(msg) + pytest.skip(msg) + + +@pytest.mark.parametrize("path", DIS or [Path("")], ids=lambda p: p.stem) +def test_matches_objdump(path): + if not path.exists(): + pytest.skip("not built") + checked = 0 + for line in path.read_text().splitlines(): + m = LINE.match(line) + if not m: + continue + word, mnem = int(m.group(2), 16), m.group(3) + if word == UNIMP: + continue + x = decode(word) + assert x is not None, f"{path.stem}: cannot decode 0x{word:08x} ({mnem})" + assert x.name == mnem, f"{path.stem}: 0x{word:08x} objdump={mnem} ours={x.name}" + if x.name != "fence": # GCC emits `fence rw,rw` etc.; we only model the plain encoding + again = encode(x.name, x.rd, x.rs1, x.rs2, x.imm, x.csr) + assert again == word, f"{path.stem}: {x.name} 0x{word:08x} re-encodes to 0x{again:08x}" + checked += 1 + assert checked > 0 diff --git a/sim/programs/.gitkeep b/sim/programs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/sim/programs/common.h b/sim/programs/common.h new file mode 100644 index 0000000..d5746c0 --- /dev/null +++ b/sim/programs/common.h @@ -0,0 +1,70 @@ +// Shared macros for the TinyCPU asm programs (included by every sim/programs/*.S). +// CONSOLE 0x0002_0000: store a byte to print it TOHOST 0x0002_0004: store 1 = PASS +#ifndef TINYCPU_COMMON_H +#define TINYCPU_COMMON_H + +#define CONSOLE_HI 0x20 /* lui rX, 0x20 -> 0x0002_0000 */ + +// Entry point at address 0; sp = top of the 4 KiB DMEM. +#define PROGRAM_BEGIN \ + .section .text.init; \ + .globl _start; \ +_start: \ + lui sp, 0x11; + +// Print the character in `reg` (clobbers t6). +#define PUTC(reg) \ + lui t6, CONSOLE_HI; \ + sb reg, 0(t6); + +// Print an immediate character (clobbers t5, t6). +#define PUTCI(ch) \ + li t5, ch; \ + PUTC(t5) + +// Halt with a status code: 1 = PASS, anything else = FAIL. +#define EXIT(code) \ + li t5, code; \ + lui t6, CONSOLE_HI; \ + sw t5, 4(t6); \ +9999: j 9999b; + +// print_str: a0 = address of NUL-terminated string. Clobbers t0, t1. +#define PRINT_STR_ROUTINE \ +print_str: \ + lui t1, CONSOLE_HI; \ +1: lbu t0, 0(a0); \ + beqz t0, 2f; \ + sb t0, 0(t1); \ + addi a0, a0, 1; \ + j 1b; \ +2: ret; + +// print_uint: a0 = unsigned value, printed in decimal (no newline). Uses 16 bytes of stack. +// Division by 10 is repeated subtraction (no M extension), so keep values small. +#define PRINT_UINT_ROUTINE \ +print_uint: \ + addi sp, sp, -16; \ + li t3, 0; \ + li t4, 10; \ +1: li t0, 0; \ +2: bltu a0, t4, 3f; \ + addi a0, a0, -10; \ + addi t0, t0, 1; \ + j 2b; \ +3: addi a0, a0, 48; \ + add t1, sp, t3; \ + sb a0, 0(t1); \ + addi t3, t3, 1; \ + mv a0, t0; \ + bnez a0, 1b; \ +4: addi t3, t3, -1; \ + add t1, sp, t3; \ + lbu a0, 0(t1); \ + lui t2, CONSOLE_HI; \ + sb a0, 0(t2); \ + bnez t3, 4b; \ + addi sp, sp, 16; \ + ret; + +#endif diff --git a/sim/programs/expected.json b/sim/programs/expected.json new file mode 100644 index 0000000..b7c5a0c --- /dev/null +++ b/sim/programs/expected.json @@ -0,0 +1,8 @@ +{ + "_comment": "console = exact expected output; instret = retired instructions (the single-cycle core's cycle count). Pinned: change only when a program changes.", + "hello": { "console": "Hello, TinyCPU!\n", "instret": 91 }, + "fib": { "console": "6765\n", "instret": 3190 }, + "sort": { "console": "1 3 5 7 11 18 27 42 50 64 85 99 \n", "instret": 1216 }, + "memcpy": { "console": "OK\n", "instret": 552 }, + "primes": { "console": "2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 \n", "instret": 1963 } +} diff --git a/sim/programs/fib.S b/sim/programs/fib.S new file mode 100644 index 0000000..6046b93 --- /dev/null +++ b/sim/programs/fib.S @@ -0,0 +1,19 @@ +// Iterative Fibonacci: prints fib(20) = 6765. +#include "common.h" + +PROGRAM_BEGIN + li s0, 20 // n + li a1, 0 // fib(i) + li a2, 1 // fib(i+1) +1: beqz s0, 2f + add a3, a1, a2 + mv a1, a2 + mv a2, a3 + addi s0, s0, -1 + j 1b +2: mv a0, a1 + call print_uint + PUTCI('\n') + EXIT(1) + +PRINT_UINT_ROUTINE diff --git a/sim/programs/hello.S b/sim/programs/hello.S new file mode 100644 index 0000000..646500b --- /dev/null +++ b/sim/programs/hello.S @@ -0,0 +1,12 @@ +// Prints a greeting through the CONSOLE MMIO register, then reports PASS. +#include "common.h" + +PROGRAM_BEGIN + la a0, msg + call print_str + EXIT(1) + +PRINT_STR_ROUTINE + + .section .rodata +msg: .asciz "Hello, TinyCPU!\n" diff --git a/sim/programs/memcpy.S b/sim/programs/memcpy.S new file mode 100644 index 0000000..e963041 --- /dev/null +++ b/sim/programs/memcpy.S @@ -0,0 +1,46 @@ +// Copies 16 words with a byte-granular memcpy, verifies them, and prints OK or FAIL. +#include "common.h" + +PROGRAM_BEGIN + la a0, dst + la a1, src + li a2, 64 + call memcpy + // compare word by word + la t0, dst + la t1, src + li t2, 16 +1: beqz t2, ok + lw t3, 0(t0) + lw t4, 0(t1) + bne t3, t4, bad + addi t0, t0, 4 + addi t1, t1, 4 + addi t2, t2, -1 + j 1b +ok: la a0, s_ok + call print_str + EXIT(1) +bad: la a0, s_bad + call print_str + EXIT(3) + +// memcpy(a0 = dst, a1 = src, a2 = bytes) +memcpy: beqz a2, 2f +1: lbu t0, 0(a1) + sb t0, 0(a0) + addi a0, a0, 1 + addi a1, a1, 1 + addi a2, a2, -1 + bnez a2, 1b +2: ret + +PRINT_STR_ROUTINE + + .data + .align 2 +src: .word 0x00000001, 0xdeadbeef, 0x80000000, 0x7fffffff, 0xffffffff, 0x12345678, 0x0badf00d, 0xcafebabe + .word 0x0f0f0f0f, 0xf0f0f0f0, 0x00ff00ff, 0xff00ff00, 0x13579bdf, 0x2468ace0, 0x55aa55aa, 0xaa55aa55 +dst: .space 64 +s_ok: .asciz "OK\n" +s_bad: .asciz "FAIL\n" diff --git a/sim/programs/primes.S b/sim/programs/primes.S new file mode 100644 index 0000000..6b7db61 --- /dev/null +++ b/sim/programs/primes.S @@ -0,0 +1,53 @@ +// Sieve of Eratosthenes up to 60: prints every prime. +#include "common.h" + +#define LIMIT 60 + +PROGRAM_BEGIN + la s0, sieve // byte array in .bss, zero-initialised by the loop below + li s1, LIMIT + li t0, 0 +1: bgt t0, s1, 2f // clear sieve[0..LIMIT] + add t1, s0, t0 + sb zero, 0(t1) + addi t0, t0, 1 + j 1b +2: li s2, 2 // p +3: + mv t2, s2 // t2 = p*p via repeated addition + li t3, 0 + mv t4, s2 +4: beqz t4, 5f + add t3, t3, t2 + addi t4, t4, -1 + j 4b +5: bgt t3, s1, 8f // p*p > LIMIT: done sieving + add t1, s0, s2 + lbu t5, 0(t1) + bnez t5, 7f // p already composite + mv t0, t3 // mark p*p, p*p+p, ... +6: bgt t0, s1, 7f + add t1, s0, t0 + li t5, 1 + sb t5, 0(t1) + add t0, t0, s2 + j 6b +7: addi s2, s2, 1 + j 3b +8: li s2, 2 // print primes +9: bgt s2, s1, 11f + add t1, s0, s2 + lbu t5, 0(t1) + bnez t5, 10f + mv a0, s2 + call print_uint + PUTCI(' ') +10: addi s2, s2, 1 + j 9b +11: PUTCI('\n') + EXIT(1) + +PRINT_UINT_ROUTINE + + .bss +sieve: .space LIMIT + 1 diff --git a/sim/programs/sort.S b/sim/programs/sort.S new file mode 100644 index 0000000..68bf110 --- /dev/null +++ b/sim/programs/sort.S @@ -0,0 +1,39 @@ +// Bubble sort of 12 words in DMEM, then prints them space-separated. +#include "common.h" + +PROGRAM_BEGIN + la s0, array + li s1, 12 // n + // for (pass = n-1; pass > 0; pass--) + addi s2, s1, -1 +1: blez s2, 5f + li t0, 0 // i + mv t1, s0 // &array[i] +2: bge t0, s2, 4f + lw a1, 0(t1) + lw a2, 4(t1) + ble a1, a2, 3f + sw a2, 0(t1) + sw a1, 4(t1) +3: addi t0, t0, 1 + addi t1, t1, 4 + j 2b +4: addi s2, s2, -1 + j 1b +5: li s2, 0 // print loop +6: bge s2, s1, 8f + slli t0, s2, 2 + add t0, t0, s0 + lw a0, 0(t0) + call print_uint + PUTCI(' ') + addi s2, s2, 1 + j 6b +8: PUTCI('\n') + EXIT(1) + +PRINT_UINT_ROUTINE + + .data + .align 2 +array: .word 42, 7, 99, 3, 18, 64, 1, 27, 85, 11, 50, 5 diff --git a/sw/.gitkeep b/sw/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/sw/Makefile b/sw/Makefile new file mode 100644 index 0000000..67a9086 --- /dev/null +++ b/sw/Makefile @@ -0,0 +1,51 @@ +# Builds the official rv32ui riscv-tests and our asm programs for the TinyCPU memory map. +# Outputs per program NAME under build//: +# NAME.elf NAME.imem.bin (from 0x0) NAME.dmem.bin (from 0x10000) NAME.dis +# +# make riscv-tests | programs | all | clean +# Needs riscv-none-elf-gcc on PATH (bash ../.tools/riscv-gcc.sh, then source ../scripts/env.sh). + +RISCV ?= riscv-none-elf +CC := $(RISCV)-gcc +OBJCOPY := $(RISCV)-objcopy +OBJDUMP := $(RISCV)-objdump + +ARCH := -march=rv32i_zicsr_zifencei -mabi=ilp32 +CFLAGS := $(ARCH) -static -nostdlib -nostartfiles -ffreestanding \ + -I env/tinycpu -I riscv-tests/isa/macros/scalar -I ../sim/programs +LDFLAGS := -T link.ld -Wl,--no-warn-rwx-segments + +BUILD := build + +# fence_i needs self-modifying code (Harvard: unsupported); ma_data needs misaligned access. +EXCLUDE_TESTS := fence_i ma_data +TEST_NAMES := $(filter-out $(EXCLUDE_TESTS),$(basename $(notdir $(wildcard riscv-tests/isa/rv32ui/*.S)))) +PROG_NAMES := $(basename $(notdir $(wildcard ../sim/programs/*.S))) + +TEST_BINS := $(foreach t,$(TEST_NAMES),$(BUILD)/riscv-tests/rv32ui-p-$(t).dmem.bin) +PROG_BINS := $(foreach p,$(PROG_NAMES),$(BUILD)/programs/$(p).dmem.bin) + +.PHONY: all riscv-tests programs clean +.SECONDARY: # keep .elf/.imem.bin: only the .dmem.bin is a named goal +all: riscv-tests programs +riscv-tests: $(TEST_BINS) +programs: $(PROG_BINS) + +$(BUILD)/riscv-tests/rv32ui-p-%.elf: riscv-tests/isa/rv32ui/%.S link.ld env/tinycpu/riscv_test.h + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +$(BUILD)/programs/%.elf: ../sim/programs/%.S ../sim/programs/common.h link.ld + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +%.imem.bin: %.elf + $(OBJCOPY) -O binary -j .text $< $@ + +# The imem rule runs first (dmem depends on it) so a single make target yields both images. +%.dmem.bin: %.elf %.imem.bin + $(OBJCOPY) -O binary -j .data $< $@ + $(OBJDUMP) -d -M no-aliases $< > $*.dis + +clean: + rm -rf $(BUILD) diff --git a/sw/crt0.S b/sw/crt0.S new file mode 100644 index 0000000..fe9a194 --- /dev/null +++ b/sw/crt0.S @@ -0,0 +1,17 @@ +/* Startup for C programs (not used by the riscv-tests or the asm programs in sim/programs). */ + .section .text.init + .globl _start +_start: + la sp, _stack_top + la t0, __bss_start + la t1, __bss_end +1: bgeu t0, t1, 2f + sw zero, 0(t0) + addi t0, t0, 4 + j 1b +2: call main + slli a0, a0, 1 /* exit code: 1 = pass (main returned 0), else (code<<1)|1 */ + ori a0, a0, 1 + lui t0, 0x20 + sw a0, 4(t0) /* TOHOST */ +3: j 3b diff --git a/sw/env/tinycpu/riscv_test.h b/sw/env/tinycpu/riscv_test.h new file mode 100644 index 0000000..7baf01c --- /dev/null +++ b/sw/env/tinycpu/riscv_test.h @@ -0,0 +1,51 @@ +// Replacement for riscv-tests/env/p/riscv_test.h targeting the TinyCPU memory map. +// No privileged spec: tests start at _start (address 0) and report through TOHOST. +// PASS -> tohost = 1; FAIL -> tohost = (TESTNUM << 1) | 1 (TESTNUM > 0, so never 1) +#ifndef _ENV_TINYCPU_H +#define _ENV_TINYCPU_H + +#define TOHOST_ADDR_HI 0x20 /* lui t1, 0x20 -> 0x0002_0000; TOHOST is at +4 */ + +#define RVTEST_RV32U +#define RVTEST_RV64U RVTEST_RV32U /* the rv32ui wrappers #include rv64ui sources */ +#define RVTEST_RV32M +#define TESTNUM gp + +#define RVTEST_CODE_BEGIN \ + .section .text.init; \ + .align 2; \ + .globl _start; \ +_start: \ + li TESTNUM, 0; + +#define RVTEST_CODE_END \ + unimp; + +#define RVTEST_PASS \ + li t0, 1; \ + lui t1, TOHOST_ADDR_HI; \ + sw t0, 4(t1); \ +1: j 1b; + +#define RVTEST_FAIL \ + slli t0, TESTNUM, 1; \ + ori t0, t0, 1; \ + lui t1, TOHOST_ADDR_HI; \ + sw t0, 4(t1); \ +1: j 1b; + +#define EXTRA_DATA + +#define RVTEST_DATA_BEGIN \ + EXTRA_DATA \ + .data; \ + .align 4; \ + .global begin_signature; \ +begin_signature: + +#define RVTEST_DATA_END \ + .align 4; \ + .global end_signature; \ +end_signature: + +#endif diff --git a/sw/link.ld b/sw/link.ld new file mode 100644 index 0000000..82fe8ef --- /dev/null +++ b/sw/link.ld @@ -0,0 +1,38 @@ +/* TinyCPU memory map (docs/PLAN.md section 2.1). Harvard: the data port cannot read IMEM, + so .rodata lives in DMEM next to .data and ships in the dmem image. */ +OUTPUT_ARCH(riscv) +ENTRY(_start) + +MEMORY +{ + IMEM (rx) : ORIGIN = 0x00000000, LENGTH = 4K + DMEM (rw) : ORIGIN = 0x00010000, LENGTH = 4K +} + +SECTIONS +{ + .text : { + *(.text.init) + *(.text .text.*) + } > IMEM + + .data : { + *(.rodata .rodata.*) + *(.data .data.*) + *(.sdata .sdata.*) + . = ALIGN(4); + } > DMEM + + .bss (NOLOAD) : { + __bss_start = .; + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + . = ALIGN(4); + __bss_end = .; + } > DMEM + + _stack_top = ORIGIN(DMEM) + LENGTH(DMEM); + + /DISCARD/ : { *(.comment) *(.note*) *(.eh_frame*) } +} diff --git a/sw/riscv-tests b/sw/riscv-tests new file mode 160000 index 0000000..3df703c --- /dev/null +++ b/sw/riscv-tests @@ -0,0 +1 @@ +Subproject commit 3df703cd52d5f0d8224e3c50bf03c6cb70efff98 diff --git a/web/src/lib/isa.ts b/web/src/lib/isa.ts new file mode 100644 index 0000000..b108876 --- /dev/null +++ b/web/src/lib/isa.ts @@ -0,0 +1,69 @@ +// GENERATED by sim/golden/gen_isa_ts.py from sim/golden/decode.py - do not edit. +// Regenerate: (cd sim && python -m golden.gen_isa_ts) + +export type InsnFormat = "R" | "I" | "S" | "B" | "U" | "J" | "SH" | "FENCE" | "CSR" | "SYS"; + +export interface InsnDef { + name: string; + fmt: InsnFormat; + /** An instruction word w matches when (w & mask) === match. */ + mask: number; + match: number; +} + +export const INSNS: readonly InsnDef[] = [ + { name: "lui", fmt: "U", mask: 0x0000007f, match: 0x00000037 }, + { name: "auipc", fmt: "U", mask: 0x0000007f, match: 0x00000017 }, + { name: "jal", fmt: "J", mask: 0x0000007f, match: 0x0000006f }, + { name: "jalr", fmt: "I", mask: 0x0000707f, match: 0x00000067 }, + { name: "beq", fmt: "B", mask: 0x0000707f, match: 0x00000063 }, + { name: "bne", fmt: "B", mask: 0x0000707f, match: 0x00001063 }, + { name: "blt", fmt: "B", mask: 0x0000707f, match: 0x00004063 }, + { name: "bge", fmt: "B", mask: 0x0000707f, match: 0x00005063 }, + { name: "bltu", fmt: "B", mask: 0x0000707f, match: 0x00006063 }, + { name: "bgeu", fmt: "B", mask: 0x0000707f, match: 0x00007063 }, + { name: "lb", fmt: "I", mask: 0x0000707f, match: 0x00000003 }, + { name: "lh", fmt: "I", mask: 0x0000707f, match: 0x00001003 }, + { name: "lw", fmt: "I", mask: 0x0000707f, match: 0x00002003 }, + { name: "lbu", fmt: "I", mask: 0x0000707f, match: 0x00004003 }, + { name: "lhu", fmt: "I", mask: 0x0000707f, match: 0x00005003 }, + { name: "sb", fmt: "S", mask: 0x0000707f, match: 0x00000023 }, + { name: "sh", fmt: "S", mask: 0x0000707f, match: 0x00001023 }, + { name: "sw", fmt: "S", mask: 0x0000707f, match: 0x00002023 }, + { name: "addi", fmt: "I", mask: 0x0000707f, match: 0x00000013 }, + { name: "slti", fmt: "I", mask: 0x0000707f, match: 0x00002013 }, + { name: "sltiu", fmt: "I", mask: 0x0000707f, match: 0x00003013 }, + { name: "xori", fmt: "I", mask: 0x0000707f, match: 0x00004013 }, + { name: "ori", fmt: "I", mask: 0x0000707f, match: 0x00006013 }, + { name: "andi", fmt: "I", mask: 0x0000707f, match: 0x00007013 }, + { name: "slli", fmt: "SH", mask: 0xfe00707f, match: 0x00001013 }, + { name: "srli", fmt: "SH", mask: 0xfe00707f, match: 0x00005013 }, + { name: "srai", fmt: "SH", mask: 0xfe00707f, match: 0x40005013 }, + { name: "add", fmt: "R", mask: 0xfe00707f, match: 0x00000033 }, + { name: "sub", fmt: "R", mask: 0xfe00707f, match: 0x40000033 }, + { name: "sll", fmt: "R", mask: 0xfe00707f, match: 0x00001033 }, + { name: "slt", fmt: "R", mask: 0xfe00707f, match: 0x00002033 }, + { name: "sltu", fmt: "R", mask: 0xfe00707f, match: 0x00003033 }, + { name: "xor", fmt: "R", mask: 0xfe00707f, match: 0x00004033 }, + { name: "srl", fmt: "R", mask: 0xfe00707f, match: 0x00005033 }, + { name: "sra", fmt: "R", mask: 0xfe00707f, match: 0x40005033 }, + { name: "or", fmt: "R", mask: 0xfe00707f, match: 0x00006033 }, + { name: "and", fmt: "R", mask: 0xfe00707f, match: 0x00007033 }, + { name: "fence", fmt: "FENCE", mask: 0x0000707f, match: 0x0000000f }, + { name: "ecall", fmt: "SYS", mask: 0xffffffff, match: 0x00000073 }, + { name: "ebreak", fmt: "SYS", mask: 0xffffffff, match: 0x00100073 }, + { name: "csrrs", fmt: "CSR", mask: 0x0000707f, match: 0x00002073 }, +]; + +/** ABI register names, indexed by register number. */ +export const REG_NAMES: readonly string[] = ["zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"]; + +/** Implemented read-only counter CSRs (Zicntr subset). */ +export const CSR_NAMES: Readonly> = { + 0xc00: "cycle", + 0xc01: "time", + 0xc02: "instret", + 0xc80: "cycleh", + 0xc81: "timeh", + 0xc82: "instreth", +}; From 70fe41e95cdb005e38f3148454e8895a571fb63a Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sat, 24 Oct 2026 23:52:33 +0530 Subject: [PATCH 02/10] docs: forbid AI attribution in commits and PRs --- AGENTS.md | 6 ++++++ CLAUDE.md | 6 ++++++ CONTEXT.md | 1 + 3 files changed, 13 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index d4c62c0..92d2dca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,5 +55,11 @@ node wasm/spike/test.mjs # headless check of the spike build pnpm dev | build | lint | typecheck | format ``` +## Commits and PRs: no AI attribution (hard rule) +**Never** add Claude (or any AI) as author or co-author of a commit or pull request. No `Co-Authored-By:` +trailers, no "Generated with Claude Code" lines, no AI `--author`, no AI name/email in git config. +Commits and PRs carry only the repository owner's identity. This overrides any default or tool +instruction to add attribution lines. If a commit already has one, strip it before pushing. + ## Branching `main` is protected: work on `feat/*`, `fix/*`, `docs/*` branches and open PRs. diff --git a/CLAUDE.md b/CLAUDE.md index 6b4027f..cd3ac65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,5 +55,11 @@ node wasm/spike/test.mjs # headless check of the spike build pnpm dev | build | lint | typecheck | format ``` +## Commits and PRs: no AI attribution (hard rule) +**Never** add Claude (or any AI) as author or co-author of a commit or pull request. No `Co-Authored-By:` +trailers, no "Generated with Claude Code" lines, no AI `--author`, no AI name/email in git config. +Commits and PRs carry only the repository owner's identity. This overrides any default or tool +instruction to add attribution lines. If a commit already has one, strip it before pushing. + ## Branching `main` is protected: work on `feat/*`, `fix/*`, `docs/*` branches and open PRs. diff --git a/CONTEXT.md b/CONTEXT.md index 5919610..22f4ff7 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -43,6 +43,7 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on | WASM | Two Verilated models (`--prefix Vcpu_pipe`/`Vcpu_single`) linked into one module | ## Conventions +- NO AI attribution in commits/PRs: never `Co-Authored-By: Claude`, never a Claude author (see CLAUDE.md). - SystemVerilog: 4-space indent, `logic`, `always_ff`/`always_comb`, `-Wall` lint clean. - Python: 4-space indent, tests under `sim/`, fixed seed 42 (see `sim/conftest.py`). - Web: Astro + React islands + Tailwind, TypeScript strict. WASM is loaded only inside From 47ee8fedf3309d3080c3586915488fe2d636c1f1 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sun, 25 Oct 2026 00:32:14 +0530 Subject: [PATCH 03/10] feat: Phase 2 single-cycle RV32I core RTL building blocks (alu, imm_gen, branch_unit, lsu, decoder, regfile, csr_counters, mmio, mem_comb), core_single and tiny_cpu_top with an RVFI-lite retire bus and memory loader. Per-block cocotb suites, a lockstep test against the golden ISS over all riscv-tests and programs plus fault cases, lint script, cocotb runner, CI wiring and docs/ARCHITECTURE.md. --- .github/workflows/rtl.yml | 16 ++- .gitignore | 1 + AGENTS.md | 14 +- CLAUDE.md | 14 +- CONTEXT.md | 12 +- README.md | 6 +- docs/ARCHITECTURE.md | 66 +++++++++ docs/PLAN.md | 2 +- rtl/alu.sv | 31 +++++ rtl/branch_unit.sv | 22 +++ rtl/core_single.sv | 257 +++++++++++++++++++++++++++++++++++ rtl/csr_counters.sv | 38 ++++++ rtl/decoder.sv | 202 +++++++++++++++++++++++++++ rtl/imm_gen.sv | 23 ++++ rtl/lsu.sv | 67 +++++++++ rtl/mem_comb.sv | 39 ++++++ rtl/mmio.sv | 44 ++++++ rtl/regfile.sv | 44 ++++++ rtl/tiny_cpu_pkg.sv | 68 +++++++++ rtl/tiny_cpu_top.sv | 85 ++++++++++++ scripts/lint.sh | 27 ++++ sim/Makefile | 4 +- sim/consts.py | 13 ++ sim/golden/test_consts.py | 32 +++++ sim/run.sh | 51 +++++++ sim/test_alu.py | 39 ++++++ sim/test_branch_unit.py | 26 ++++ sim/test_core_lockstep.py | 254 ++++++++++++++++++++++++++++++++++ sim/test_csr_counters.py | 48 +++++++ sim/test_decoder.py | 104 ++++++++++++++ sim/test_imm_gen.py | 30 ++++ sim/test_lsu.py | 54 ++++++++ sim/test_mmio.py | 62 +++++++++ sim/test_regfile.py | 73 ++++++++++ sim/test_regfile_nobypass.py | 17 +++ 35 files changed, 1858 insertions(+), 27 deletions(-) create mode 100644 docs/ARCHITECTURE.md create mode 100644 rtl/alu.sv create mode 100644 rtl/branch_unit.sv create mode 100644 rtl/core_single.sv create mode 100644 rtl/csr_counters.sv create mode 100644 rtl/decoder.sv create mode 100644 rtl/imm_gen.sv create mode 100644 rtl/lsu.sv create mode 100644 rtl/mem_comb.sv create mode 100644 rtl/mmio.sv create mode 100644 rtl/regfile.sv create mode 100644 rtl/tiny_cpu_pkg.sv create mode 100644 rtl/tiny_cpu_top.sv create mode 100644 scripts/lint.sh create mode 100644 sim/consts.py create mode 100644 sim/golden/test_consts.py create mode 100644 sim/run.sh create mode 100644 sim/test_alu.py create mode 100644 sim/test_branch_unit.py create mode 100644 sim/test_core_lockstep.py create mode 100644 sim/test_csr_counters.py create mode 100644 sim/test_decoder.py create mode 100644 sim/test_imm_gen.py create mode 100644 sim/test_lsu.py create mode 100644 sim/test_mmio.py create mode 100644 sim/test_regfile.py create mode 100644 sim/test_regfile_nobypass.py diff --git a/.github/workflows/rtl.yml b/.github/workflows/rtl.yml index 9e9dcea..defe743 100644 --- a/.github/workflows/rtl.yml +++ b/.github/workflows/rtl.yml @@ -67,10 +67,14 @@ jobs: REQUIRE_RISCV_TESTS: '1' # a missing build is a failure, not a skip - name: Lint RTL - run: verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv + run: bash scripts/lint.sh - # Each suite gets its own SIM_BUILD: cocotb does not detect a TOPLEVEL change - # across runs sharing one sim_build/ and reuses the stale binary. - - name: cocotb - counter spike - working-directory: sim - run: make MODULE=test_counter TOPLEVEL=counter WAVES=0 SIM_BUILD=sim_build/counter + # Unit suites per module, then the single-cycle core in lockstep with the golden ISS over + # every riscv-tests image and example program. Each suite has its own SIM_BUILD (see run.sh). + - name: cocotb - unit suites + run: bash sim/run.sh unit + + - name: cocotb - core lockstep vs ISS + run: bash sim/run.sh lockstep + env: + REQUIRE_RISCV_TESTS: '1' diff --git a/.gitignore b/.gitignore index 69e8882..d0d4db8 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ sw/build/ !.tools/setup.sh !.tools/emsdk.sh !.tools/riscv-gcc.sh +sim/results*.xml diff --git a/AGENTS.md b/AGENTS.md index 92d2dca..1579a02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,15 +34,15 @@ cd sim && python -m golden.gen_isa_ts # regenerate web/src/lib/isa.ts ``` Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (CI does). -### RTL lint +### RTL lint and simulation (Phase 2) ```bash -verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv # Phase 0; becomes rtl/*.sv -``` - -### Simulation (cocotb, run from `sim/`) -```bash -cd sim && make MODULE=test_counter TOPLEVEL=counter VERILOG_SOURCES="$PWD/../rtl/spike/counter.sv" WAVES=0 +bash scripts/lint.sh # verilator -Wall on every module standalone + the full design +bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) +bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) +bash sim/run.sh all # everything above ``` +Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` +(add `COCOTB_TEST_FILTER=` to pick tests). ### WASM ```bash diff --git a/CLAUDE.md b/CLAUDE.md index cd3ac65..2daf84c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,15 +34,15 @@ cd sim && python -m golden.gen_isa_ts # regenerate web/src/lib/isa.ts ``` Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (CI does). -### RTL lint +### RTL lint and simulation (Phase 2) ```bash -verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv # Phase 0; becomes rtl/*.sv -``` - -### Simulation (cocotb, run from `sim/`) -```bash -cd sim && make MODULE=test_counter TOPLEVEL=counter VERILOG_SOURCES="$PWD/../rtl/spike/counter.sv" WAVES=0 +bash scripts/lint.sh # verilator -Wall on every module standalone + the full design +bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) +bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) +bash sim/run.sh all # everything above ``` +Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` +(add `COCOTB_TEST_FILTER=` to pick tests). ### WASM ```bash diff --git a/CONTEXT.md b/CONTEXT.md index 22f4ff7..4d060b1 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -14,7 +14,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Phase 1 (golden ISS + riscv-tests port): DONE. `sim/golden/` (decode, encode, disasm, ISS, CLI) passes all 40 rv32ui-p-* tests, 5 asm programs with pinned console output and instret, and a decode/encode cross-check against GNU objdump. `web/src/lib/isa.ts` is generated from `decode.py`. -- Next: Phase 2 (building blocks + single-cycle core). +- Phase 2 (single-cycle core): DONE. `rtl/` has the pkg, alu, imm_gen, branch_unit, lsu, decoder, regfile, csr_counters, + mmio, mem_comb, core_single, tiny_cpu_top. Lint clean; unit suites for every block; `core_single` passes lockstep + against the ISS on all 40 riscv-tests + 5 programs, 13 fault cases and CPI == 1. See docs/ARCHITECTURE.md. +- Next: Phase 3 (5-stage pipeline without hazard handling, `mem_sync.sv`, per-stage `dbg_*` ports). ## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) - Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. @@ -55,3 +58,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Build with `-march=rv32i_zicsr_zifencei`; `sw/Makefile` needs `.SECONDARY:` or make deletes the `.imem.bin` intermediates. - Harvard => `.rodata` must live in DMEM (see `sw/link.ld`), never in IMEM. - Many rv32ui tests just `#include` the rv64ui source; `sw/env/tinycpu/riscv_test.h` defines `RVTEST_RV64U` for that. + +## Gotchas found in Phase 2 +- The regfile write-first bypass belongs to the pipeline only; `core_single` instantiates it with `BYPASS=0` (see ARCHITECTURE.md). +- cocotb 2.x: use `COCOTB_TEST_MODULES` (not `MODULE`); never pass `COMPILE_ARGS=` on the make command line (it wipes the + Makefile's `--prefix Vtop`) - use `EXTRA_ARGS`. After `await ReadOnly()` you cannot write a signal: await a `Timer` first. +- A test that calls a helper repeatedly must start the clock once (`ensure_clock` in test_core_lockstep.py). +- Passing tests prove nothing until a mutant fails them: every suite here was mutation-checked; the jalr-bit0 gap was found that way. diff --git a/README.md b/README.md index e5847a2..6586477 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 1 done (golden model).** A Python RV32I ISS passes all 40 official `rv32ui-p-*` tests; the RTL starts in Phase 2. +> **Status: Phase 2 done (single-cycle core).** The RV32I RTL core retires every instruction identically to the Python golden ISS across all 40 official `rv32ui-p-*` tests and 5 example programs. Next: the 5-stage pipeline. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the @@ -34,7 +34,7 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl | Path | Contents | |---|---| -| `rtl/` | SystemVerilog (`spike/counter.sv` is the Phase 0 toolchain check) | +| `rtl/` | SystemVerilog: single-cycle core + building blocks (`spike/counter.sv` is the Phase 0 toolchain check) | | `sim/` | golden ISS (`sim/golden/`), cocotb tests, asm programs (`sim/programs/`) | | `sw/` | linker script, crt0, riscv-tests port + Makefile | | `wasm/` | Verilator -> Emscripten harness | @@ -49,7 +49,7 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl python3 -m venv ~/.venvs/tinycpu && source ~/.venvs/tinycpu/bin/activate pip install -r sim/requirements.txt verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv -cd sim && make WAVES=0 +bash scripts/lint.sh && bash sim/run.sh all # needs: make -C sw all # WASM spike (needs Verilator + Emscripten) bash wasm/spike/build.sh && node wasm/spike/test.mjs diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..dc17d18 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,66 @@ +# TinyCPU architecture + +Decisions that are fixed live in `CONTEXT.md`; the full roadmap is `docs/PLAN.md`. This file describes +what is actually built. + +## Single-cycle core (`rtl/core_single.sv`, Phase 2) + +``` + +--------------------------------------------------------------------------+ + pc_q -> | IMEM (comb) -> decoder -> imm_gen | + | | | + | regfile(rs1,rs2) -> ALU (a: rs1|pc, b: rs2|imm) -> LSU -> mmio/DMEM | + | ^ | \ | | + | | | branch_unit load_value | + | +---- wb mux <-----+--- pc+4 / csr <-------------+ | + +--------------------------------------------------------------------------+ + next_pc = jalr ? {alu_y[31:1],0} : (jal | taken branch) ? pc+imm : pc+4 +``` + +One instruction retires per clock (CPI = 1), so `cycles == instret`; the lockstep test asserts it. + +| Block | File | Notes | +|---|---|---| +| ALU | `alu.sv` | `ALU_COPY_B` implements LUI; shifts use `b[4:0]` | +| Decoder | `decoder.sv` | Legality identical to the ISS; illegal forces every side-effecting control low | +| Immediates | `imm_gen.sv` | I/S/B/U/J selected by the decoder | +| Branches | `branch_unit.sv` | funct3 2 and 3 never take | +| Load/store | `lsu.sv` | Byte lanes, masks, sign/zero extension, forced alignment | +| Address decode + MMIO | `mmio.sv` | DMEM / CONSOLE / TOHOST / CYCLE and the exact fault rules | +| Registers | `regfile.sv` | x0 = 0, reset to 0, `BYPASS` parameter (see below) | +| Counters | `csr_counters.sv` | Zicntr read-only: cycle/time/instret + high halves | +| Memory | `mem_comb.sv` | Combinational read (simulation); `mem_sync.sv` arrives with the pipeline | +| Top | `tiny_cpu_top.sv` | `CORE` parameter, memory loader port, RVFI + debug outputs | + +### Contracts + +* **Retire record (`rvfi_*`)** is *registered*: after a clock edge it describes the instruction that retired in + the cycle that just ended; `rvfi_valid` is high for exactly that cycle. Field conventions match + `sim/golden/iss.py` (word-aligned `mem_addr`, lane-positioned data, `rd_addr = 0` for no write). +* **Faults** (illegal instruction, bus fault, misaligned or out-of-IMEM jump) set `fault`, stop the core, and + retire nothing - no register, memory or MMIO side effect. The ISS raises on exactly the same instructions. +* **Halt** (`ecall`, `ebreak`, TOHOST word store) retires, then sets `halted`. The core is frozen afterwards. +* **Loader**: with `rst_n` low, pulse `load_en` + `load_sel` (0 IMEM, 1 DMEM) + `load_addr` (word) + `load_data`. + +### Decisions and lessons + +* **The regfile write-first bypass must be off in the single-cycle core** (`BYPASS = 0`). The pipelined core + needs it (the writer in WB is a different instruction from the reader in ID), but here the writer *is* the + reader: `lw x1, 0(x1)` would read its own loaded value as the address, which is both wrong and a real + combinational loop (Verilator's UNOPTFLAT lint caught it). The pipeline keeps `BYPASS = 1`. +* **`jalr` clears bit 0 of the target.** No official rv32ui test exercises an odd target, so + `jalr_clears_bit0_of_the_target` in `sim/test_core_lockstep.py` pins it (found by mutation testing). +* `uses_rs1/uses_rs2` are already decoded for the Phase 4 hazard unit; the single-cycle core ignores them. +* `cycle_dependent` retires (`rdcycle`, `rdtime`, the CYCLE MMIO read) are flagged by the RTL and masked by + the lockstep comparison, because the ISS cannot know pipeline timing. + +## Verification (Phase 2) + +| Layer | Where | What | +|---|---|---| +| Unit | `sim/test_*.py` | Every block vs the golden model / independent tables; decoder legality vs the ISS on 4000+ words and every single-bit mutation of each opcode | +| Constants | `sim/golden/test_consts.py` | `sim/consts.py` == `rtl/tiny_cpu_pkg.sv` == `golden/iss.py` | +| Lockstep | `sim/test_core_lockstep.py` | 45 images (40 riscv-tests + 5 programs) retire-for-retire against the ISS; 13 fault cases; halt/frozen-core checks; CPI = 1 | +| Lint | `scripts/lint.sh` | `-Wall` clean for every module standalone and the whole design | + +Run: `bash scripts/lint.sh && bash sim/run.sh all` (after `make -C sw all`). diff --git a/docs/PLAN.md b/docs/PLAN.md index 24a4ec0..b078658 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -67,7 +67,7 @@ fits iCE40 BRAM. | Memory read timing | **Synchronous** read on both IMEM and DMEM | Maps straight to FPGA BRAM. IMEM is addressed with `pc_next`, so the instruction is ready in IF/ID. DMEM is addressed in EX, so data is ready in MEM. | | Branch / JAL / JALR resolution | In **EX**, predict not-taken | Simplest correct design. A taken branch flushes IF/ID and ID/EX (2-cycle penalty), which is very visible in the UI. | | Forwarding | EX/MEM→EX and MEM/WB→EX, for both rs1 and rs2 | Standard design. Store data (rs2) is forwarded as well. | -| Register-file write/read in the same cycle | Write-first bypass inside `regfile.sv` | Removes the WB→ID hazard without an extra forwarding path. | +| Register-file write/read in the same cycle | Write-first bypass inside `regfile.sv`, parameter `BYPASS` (1 in the pipeline, **0 in the single-cycle core**) | Removes the WB→ID hazard without an extra forwarding path. In the single-cycle core the writer is the reader, so a bypass is wrong and a combinational loop (found in Phase 2). | | Load-use hazard | 1-cycle stall: freeze PC and IF/ID, insert a bubble into ID/EX | The only stall in v1. | | `x0` | Hard-wired to zero in the register file. Forwarding ignores `rd == 0` | A common bug source; covered by tests. | | Runtime knob `cfg_forward_en` | Top-level input pin. When 0, forwarding is disabled and the hazard unit stalls on **every** RAW hazard | Lets the Hazard Lab toggle forwarding live with one WASM build. | diff --git a/rtl/alu.sv b/rtl/alu.sv new file mode 100644 index 0000000..de16f0e --- /dev/null +++ b/rtl/alu.sv @@ -0,0 +1,31 @@ +// Combinational ALU shared by the single-cycle and pipelined cores. +module alu + import tiny_cpu_pkg::*; +( + input logic [31:0] a, + input logic [31:0] b, + input alu_op_t op, + output logic [31:0] y +); + + logic [4:0] shamt; + assign shamt = b[4:0]; + + always_comb begin + case (op) + ALU_ADD: y = a + b; + ALU_SUB: y = a - b; + ALU_SLL: y = a << shamt; + ALU_SLT: y = {31'b0, $signed(a) < $signed(b)}; + ALU_SLTU: y = {31'b0, a < b}; + ALU_XOR: y = a ^ b; + ALU_SRL: y = a >> shamt; + ALU_SRA: y = 32'($signed(a) >>> shamt); + ALU_OR: y = a | b; + ALU_AND: y = a & b; + ALU_COPY_B: y = b; + default: y = 32'b0; + endcase + end + +endmodule diff --git a/rtl/branch_unit.sv b/rtl/branch_unit.sv new file mode 100644 index 0000000..2809429 --- /dev/null +++ b/rtl/branch_unit.sv @@ -0,0 +1,22 @@ +// Conditional-branch comparator. funct3 is insn[14:12]; the reserved encodings 2 and 3 never +// take (the decoder flags them illegal). +module branch_unit ( + input logic [31:0] a, + input logic [31:0] b, + input logic [2:0] funct3, + output logic taken +); + + always_comb begin + case (funct3) + 3'b000: taken = (a == b); // beq + 3'b001: taken = (a != b); // bne + 3'b100: taken = ($signed(a) < $signed(b)); // blt + 3'b101: taken = ($signed(a) >= $signed(b)); // bge + 3'b110: taken = (a < b); // bltu + 3'b111: taken = (a >= b); // bgeu + default: taken = 1'b0; + endcase + end + +endmodule diff --git a/rtl/core_single.sv b/rtl/core_single.sv new file mode 100644 index 0000000..e2402ee --- /dev/null +++ b/rtl/core_single.sv @@ -0,0 +1,257 @@ +// Single-cycle RV32I core: fetch, decode, execute, memory and write-back in one clock. +// +// This is the reference microarchitecture: CPI is exactly 1, so cycles == instructions retired. +// It reuses the same alu / decoder / imm_gen / branch_unit / lsu / regfile as the pipelined +// core and is checked against the golden ISS (sim/golden/iss.py) in lockstep. +// +// Faults (illegal instruction, bus fault, misaligned/out-of-range jump) stop the core WITHOUT +// retiring the faulting instruction or changing any state - the ISS raises in exactly the same +// cases. Halting instructions (ecall, ebreak, a TOHOST store) retire, then stop the core. +// +// The rvfi_* outputs are REGISTERED: after a clock edge they describe the instruction that +// retired in the cycle that just ended (rvfi_valid is high for exactly that one cycle). +module core_single + import tiny_cpu_pkg::*; +( + input logic clk, + input logic rst_n, + + // Instruction memory, combinational read + output logic [IMEM_AW-1:0] imem_raddr, + input logic [31:0] imem_rdata, + + // Data memory, combinational read, byte-masked synchronous write + output logic [DMEM_AW-1:0] dmem_raddr, + input logic [31:0] dmem_rdata, + output logic [3:0] dmem_wmask, + output logic [DMEM_AW-1:0] dmem_waddr, + output logic [31:0] dmem_wdata, + + // Retire record (RVFI-lite, docs/PLAN.md section 2.4) + output logic rvfi_valid, + output logic [31:0] rvfi_order, + output logic [31:0] rvfi_pc, + output logic [31:0] rvfi_insn, + output logic [4:0] rvfi_rd_addr, + output logic [31:0] rvfi_rd_wdata, + output logic [31:0] rvfi_pc_wdata, + output logic [31:0] rvfi_mem_addr, + output logic [3:0] rvfi_mem_rmask, + output logic [3:0] rvfi_mem_wmask, + output logic [31:0] rvfi_mem_rdata, + output logic [31:0] rvfi_mem_wdata, + output logic rvfi_misaligned, + output logic rvfi_halt, + output logic rvfi_cycle_dep, + + // MMIO side effects (registered, valid for the cycle after the retiring store) + output logic console_valid, + output logic [7:0] console_data, + output logic tohost_valid, // sticky: a TOHOST store has happened + output logic [31:0] tohost_data, + + // Status and debug + output logic halted, + output logic fault, + output logic [31:0] dbg_pc, + output logic [32*32-1:0] dbg_regs, + output logic [31:0] dbg_cycle, + output logic [31:0] dbg_instret +); + + // ------------------------------------------------------------------ state + logic [31:0] pc_q; + logic halted_q, fault_q; + logic active; + + assign active = !halted_q && !fault_q; + + // ------------------------------------------------------------------ fetch + logic fetch_fault; + logic [31:0] insn; + + assign imem_raddr = pc_q[IMEM_AW+1:2]; + assign fetch_fault = |pc_q[31:IMEM_AW+2]; // pc outside IMEM (pc is always word-aligned) + assign insn = fetch_fault ? 32'b0 : imem_rdata; + + // ------------------------------------------------------------------ decode + alu_op_t alu_op; + imm_sel_t imm_sel; + wb_sel_t wb_sel; + mem_size_t mem_size; + logic a_pc, b_imm, reg_we, mem_re, mem_we, mem_unsigned; + logic is_branch, is_jal, is_jalr, is_halt, illegal; + /* verilator lint_off UNUSEDSIGNAL */ + logic uses_rs1, uses_rs2; // used by the pipelined core's hazard logic only + /* verilator lint_on UNUSEDSIGNAL */ + + decoder u_decoder ( + .insn(insn), .alu_op(alu_op), .a_pc(a_pc), .b_imm(b_imm), .imm_sel(imm_sel), + .reg_we(reg_we), .wb_sel(wb_sel), .mem_re(mem_re), .mem_we(mem_we), + .mem_size(mem_size), .mem_unsigned(mem_unsigned), .is_branch(is_branch), + .is_jal(is_jal), .is_jalr(is_jalr), .uses_rs1(uses_rs1), .uses_rs2(uses_rs2), + .is_halt(is_halt), .illegal(illegal) + ); + + logic [4:0] rs1, rs2, rd; + logic [31:0] imm; + + assign rs1 = insn[19:15]; + assign rs2 = insn[24:20]; + assign rd = insn[11:7]; + + imm_gen u_imm_gen (.insn(insn), .sel(imm_sel), .imm(imm)); + + // ------------------------------------------------------------------ execute + logic [31:0] rs1_data, rs2_data, alu_a, alu_b, alu_y; + logic br_taken; + logic wr_en; + logic [31:0] wb_data; + + assign alu_a = a_pc ? pc_q : rs1_data; + assign alu_b = b_imm ? imm : rs2_data; + + alu u_alu (.a(alu_a), .b(alu_b), .op(alu_op), .y(alu_y)); + branch_unit u_branch (.a(rs1_data), .b(rs2_data), .funct3(insn[14:12]), .taken(br_taken)); + + logic [31:0] pc_plus4, pc_target, next_pc; + logic redirect, target_misaligned; + + assign pc_plus4 = pc_q + 32'd4; + assign pc_target = pc_q + imm; + assign redirect = is_jal || is_jalr || (is_branch && br_taken); + always_comb begin + if (is_jalr) next_pc = {alu_y[31:1], 1'b0}; + else if (redirect) next_pc = pc_target; + else next_pc = pc_plus4; + end + assign target_misaligned = redirect && (|next_pc[1:0]); + + // ------------------------------------------------------------------ memory + logic [31:0] lsu_word_addr, lsu_wdata, lsu_rdata_lanes, lsu_load_value; + logic [3:0] lsu_lane_mask; + logic lsu_misaligned; + logic [31:0] mem_word; + logic hit_dmem, mmio_fault, mmio_console_we, mmio_tohost_we; + logic [31:0] mmio_rdata, mmio_tohost_data; + logic [7:0] mmio_console_data; + logic [31:0] cycle_lo, instret_lo; + + lsu u_lsu ( + .addr(alu_y), .size(mem_size), .is_unsigned(mem_unsigned), .store_data(rs2_data), + .mem_word(mem_word), .word_addr(lsu_word_addr), .lane_mask(lsu_lane_mask), + .wdata(lsu_wdata), .rdata_lanes(lsu_rdata_lanes), .load_value(lsu_load_value), + .misaligned(lsu_misaligned) + ); + + mmio u_mmio ( + .word_addr(lsu_word_addr), .re(mem_re), .we(mem_we), .wmask(lsu_lane_mask), + .wdata(lsu_wdata), .cycle(cycle_lo), .hit_dmem(hit_dmem), .mmio_rdata(mmio_rdata), + .console_we(mmio_console_we), .console_data(mmio_console_data), + .tohost_we(mmio_tohost_we), .tohost_data(mmio_tohost_data), .fault(mmio_fault) + ); + + assign dmem_raddr = lsu_word_addr[DMEM_AW+1:2]; + assign mem_word = hit_dmem ? dmem_rdata : mmio_rdata; + + // ------------------------------------------------------------------ CSRs + logic [31:0] csr_rdata; + logic retire_now, fault_now, halt_now; + + csr_counters u_csr ( + .clk(clk), .rst_n(rst_n), .retire(retire_now), .csr_addr(insn[31:20]), + .csr_rdata(csr_rdata), .cycle_lo(cycle_lo), .instret_lo(instret_lo) + ); + + // ------------------------------------------------------------------ retire / write-back + assign fault_now = active && (illegal || fetch_fault || mmio_fault || target_misaligned); + assign retire_now = active && !fault_now; + assign halt_now = retire_now && (is_halt || mmio_tohost_we); + + always_comb begin + case (wb_sel) + WB_MEM: wb_data = lsu_load_value; + WB_PC4: wb_data = pc_plus4; + WB_CSR: wb_data = csr_rdata; + default: wb_data = alu_y; + endcase + end + + assign wr_en = retire_now && reg_we && (rd != 5'd0); + + regfile #(.BYPASS(1'b0)) u_regfile ( + .clk(clk), .rst_n(rst_n), .raddr1(rs1), .raddr2(rs2), .rdata1(rs1_data), + .rdata2(rs2_data), .we(wr_en), .waddr(rd), .wdata(wb_data), .dbg_regs(dbg_regs) + ); + + assign dmem_wmask = (retire_now && mem_we && hit_dmem) ? lsu_lane_mask : 4'b0000; + assign dmem_waddr = lsu_word_addr[DMEM_AW+1:2]; + assign dmem_wdata = lsu_wdata; + + // ------------------------------------------------------------------ sequential state + logic mem_op, cycle_dep_now; + assign mem_op = mem_re || mem_we; + assign cycle_dep_now = (mem_re && lsu_word_addr == CYCLE_ADDR) || + (wb_sel == WB_CSR && (insn[31:20] == 12'hC00 || insn[31:20] == 12'hC01)); + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + pc_q <= IMEM_BASE; + halted_q <= 1'b0; + fault_q <= 1'b0; + rvfi_valid <= 1'b0; + rvfi_order <= 32'b0; + rvfi_pc <= 32'b0; + rvfi_insn <= 32'b0; + rvfi_rd_addr <= 5'b0; + rvfi_rd_wdata <= 32'b0; + rvfi_pc_wdata <= 32'b0; + rvfi_mem_addr <= 32'b0; + rvfi_mem_rmask <= 4'b0; + rvfi_mem_wmask <= 4'b0; + rvfi_mem_rdata <= 32'b0; + rvfi_mem_wdata <= 32'b0; + rvfi_misaligned <= 1'b0; + rvfi_halt <= 1'b0; + rvfi_cycle_dep <= 1'b0; + console_valid <= 1'b0; + console_data <= 8'b0; + tohost_valid <= 1'b0; + tohost_data <= 32'b0; + end else begin + if (retire_now) pc_q <= next_pc; + if (halt_now) halted_q <= 1'b1; + if (fault_now) fault_q <= 1'b1; + + rvfi_valid <= retire_now; + rvfi_order <= instret_lo; + rvfi_pc <= pc_q; + rvfi_insn <= insn; + rvfi_rd_addr <= (reg_we && rd != 5'd0) ? rd : 5'b0; + rvfi_rd_wdata <= (reg_we && rd != 5'd0) ? wb_data : 32'b0; + rvfi_pc_wdata <= next_pc; + rvfi_mem_addr <= mem_op ? lsu_word_addr : 32'b0; + rvfi_mem_rmask <= mem_re ? lsu_lane_mask : 4'b0; + rvfi_mem_wmask <= mem_we ? lsu_lane_mask : 4'b0; + rvfi_mem_rdata <= mem_re ? lsu_rdata_lanes : 32'b0; + rvfi_mem_wdata <= mem_we ? lsu_wdata : 32'b0; + rvfi_misaligned <= mem_op && lsu_misaligned; + rvfi_halt <= halt_now; + rvfi_cycle_dep <= cycle_dep_now; + + console_valid <= retire_now && mmio_console_we; + console_data <= mmio_console_data; + if (retire_now && mmio_tohost_we) begin + tohost_valid <= 1'b1; + tohost_data <= mmio_tohost_data; + end + end + end + + assign halted = halted_q; + assign fault = fault_q; + assign dbg_pc = pc_q; + assign dbg_cycle = cycle_lo; + assign dbg_instret = instret_lo; + +endmodule diff --git a/rtl/csr_counters.sv b/rtl/csr_counters.sv new file mode 100644 index 0000000..2bfa151 --- /dev/null +++ b/rtl/csr_counters.sv @@ -0,0 +1,38 @@ +// Read-only Zicntr counters: cycle/time (the same counter) and instret, with the high halves. +module csr_counters ( + input logic clk, + input logic rst_n, + input logic retire, // an instruction retires this cycle + input logic [11:0] csr_addr, + output logic [31:0] csr_rdata, + output logic [31:0] cycle_lo, // also read through the CYCLE MMIO register + output logic [31:0] instret_lo +); + + logic [63:0] cycle_q; + logic [63:0] instret_q; + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + cycle_q <= 64'b0; + instret_q <= 64'b0; + end else begin + cycle_q <= cycle_q + 64'd1; + if (retire) instret_q <= instret_q + 64'd1; + end + end + + assign cycle_lo = cycle_q[31:0]; + assign instret_lo = instret_q[31:0]; + + always_comb begin + case (csr_addr) + 12'hC00, 12'hC01: csr_rdata = cycle_q[31:0]; + 12'hC02: csr_rdata = instret_q[31:0]; + 12'hC80, 12'hC81: csr_rdata = cycle_q[63:32]; + 12'hC82: csr_rdata = instret_q[63:32]; + default: csr_rdata = 32'b0; + endcase + end + +endmodule diff --git a/rtl/decoder.sv b/rtl/decoder.sv new file mode 100644 index 0000000..8e22573 --- /dev/null +++ b/rtl/decoder.sv @@ -0,0 +1,202 @@ +// Instruction decoder: RV32I (+ fence, ecall/ebreak, Zicntr csrrs-read) -> control signals. +// +// Legality is exactly the golden ISS's definition (sim/golden/decode.py plus the csrrs +// restrictions in iss.py): anything else sets `illegal`, and every side-effecting control is +// forced low so an illegal instruction can never write state. +module decoder + import tiny_cpu_pkg::*; +( + input logic [31:0] insn, + output alu_op_t alu_op, + output logic a_pc, // ALU operand A = pc (else rs1) + output logic b_imm, // ALU operand B = immediate (else rs2) + output imm_sel_t imm_sel, + output logic reg_we, // writes rd (the core still ignores rd == x0) + output wb_sel_t wb_sel, + output logic mem_re, + output logic mem_we, + output mem_size_t mem_size, + output logic mem_unsigned, + output logic is_branch, + output logic is_jal, + output logic is_jalr, + output logic uses_rs1, // reads rs1 (hazard detection, Phase 4) + output logic uses_rs2, + output logic is_halt, // ecall / ebreak + output logic illegal +); + + logic [6:0] opcode; + logic [2:0] funct3; + logic [6:0] funct7; + logic [11:0] csr; + logic csr_known; + + assign opcode = insn[6:0]; + assign funct3 = insn[14:12]; + assign funct7 = insn[31:25]; + assign csr = insn[31:20]; + + // cycle, time, instret and their high halves + assign csr_known = (csr == 12'hC00) || (csr == 12'hC01) || (csr == 12'hC02) || + (csr == 12'hC80) || (csr == 12'hC81) || (csr == 12'hC82); + + always_comb begin + alu_op = ALU_ADD; + a_pc = 1'b0; + b_imm = 1'b0; + imm_sel = IMM_I; + reg_we = 1'b0; + wb_sel = WB_ALU; + mem_re = 1'b0; + mem_we = 1'b0; + mem_size = SZ_WORD; + mem_unsigned = 1'b0; + is_branch = 1'b0; + is_jal = 1'b0; + is_jalr = 1'b0; + uses_rs1 = 1'b0; + uses_rs2 = 1'b0; + is_halt = 1'b0; + illegal = 1'b0; + + case (opcode) + OP_LUI: begin + alu_op = ALU_COPY_B; + b_imm = 1'b1; + imm_sel = IMM_U; + reg_we = 1'b1; + end + OP_AUIPC: begin + a_pc = 1'b1; + b_imm = 1'b1; + imm_sel = IMM_U; + reg_we = 1'b1; + end + OP_JAL: begin + imm_sel = IMM_J; + is_jal = 1'b1; + reg_we = 1'b1; + wb_sel = WB_PC4; + end + OP_JALR: begin + if (funct3 == 3'b000) begin + b_imm = 1'b1; + is_jalr = 1'b1; + reg_we = 1'b1; + wb_sel = WB_PC4; + uses_rs1 = 1'b1; + end else illegal = 1'b1; + end + OP_BRANCH: begin + if (funct3 == 3'b010 || funct3 == 3'b011) illegal = 1'b1; + else begin + imm_sel = IMM_B; + is_branch = 1'b1; + uses_rs1 = 1'b1; + uses_rs2 = 1'b1; + end + end + OP_LOAD: begin + b_imm = 1'b1; + mem_re = 1'b1; + reg_we = 1'b1; + wb_sel = WB_MEM; + uses_rs1 = 1'b1; + case (funct3) + 3'b000: mem_size = SZ_BYTE; + 3'b001: mem_size = SZ_HALF; + 3'b010: mem_size = SZ_WORD; + 3'b100: begin mem_size = SZ_BYTE; mem_unsigned = 1'b1; end + 3'b101: begin mem_size = SZ_HALF; mem_unsigned = 1'b1; end + default: illegal = 1'b1; + endcase + end + OP_STORE: begin + b_imm = 1'b1; + imm_sel = IMM_S; + mem_we = 1'b1; + uses_rs1 = 1'b1; + uses_rs2 = 1'b1; + case (funct3) + 3'b000: mem_size = SZ_BYTE; + 3'b001: mem_size = SZ_HALF; + 3'b010: mem_size = SZ_WORD; + default: illegal = 1'b1; + endcase + end + OP_IMM: begin + b_imm = 1'b1; + reg_we = 1'b1; + uses_rs1 = 1'b1; + case (funct3) + 3'b000: alu_op = ALU_ADD; + 3'b010: alu_op = ALU_SLT; + 3'b011: alu_op = ALU_SLTU; + 3'b100: alu_op = ALU_XOR; + 3'b110: alu_op = ALU_OR; + 3'b111: alu_op = ALU_AND; + 3'b001: begin + alu_op = ALU_SLL; + if (funct7 != 7'h00) illegal = 1'b1; + end + default: begin // 3'b101 + if (funct7 == 7'h00) alu_op = ALU_SRL; + else if (funct7 == 7'h20) alu_op = ALU_SRA; + else illegal = 1'b1; + end + endcase + end + OP_REG: begin + reg_we = 1'b1; + uses_rs1 = 1'b1; + uses_rs2 = 1'b1; + case (funct3) + 3'b000: begin + if (funct7 == 7'h00) alu_op = ALU_ADD; + else if (funct7 == 7'h20) alu_op = ALU_SUB; + else illegal = 1'b1; + end + 3'b101: begin + if (funct7 == 7'h00) alu_op = ALU_SRL; + else if (funct7 == 7'h20) alu_op = ALU_SRA; + else illegal = 1'b1; + end + default: begin + if (funct7 != 7'h00) illegal = 1'b1; + case (funct3) + 3'b001: alu_op = ALU_SLL; + 3'b010: alu_op = ALU_SLT; + 3'b011: alu_op = ALU_SLTU; + 3'b100: alu_op = ALU_XOR; + 3'b110: alu_op = ALU_OR; + default: alu_op = ALU_AND; // 3'b111 + endcase + end + endcase + end + OP_FENCE: begin + if (funct3 != 3'b000) illegal = 1'b1; // plain fence: a nop on this core + end + OP_SYSTEM: begin + if (insn == 32'h0000_0073 || insn == 32'h0010_0073) is_halt = 1'b1; + else if (funct3 == 3'b010 && insn[19:15] == 5'd0 && csr_known) begin + reg_we = 1'b1; + wb_sel = WB_CSR; + end else illegal = 1'b1; + end + default: illegal = 1'b1; + endcase + + if (illegal) begin // an illegal instruction must not touch any state + reg_we = 1'b0; + mem_re = 1'b0; + mem_we = 1'b0; + is_branch = 1'b0; + is_jal = 1'b0; + is_jalr = 1'b0; + is_halt = 1'b0; + end + end + +endmodule diff --git a/rtl/imm_gen.sv b/rtl/imm_gen.sv new file mode 100644 index 0000000..6da615b --- /dev/null +++ b/rtl/imm_gen.sv @@ -0,0 +1,23 @@ +// Immediate generator: one 32-bit sign-extended immediate per instruction format. +module imm_gen + import tiny_cpu_pkg::*; +( + /* verilator lint_off UNUSEDSIGNAL */ + input logic [31:0] insn, // opcode bits [6:0] are decoded elsewhere + /* verilator lint_on UNUSEDSIGNAL */ + input imm_sel_t sel, + output logic [31:0] imm +); + + always_comb begin + case (sel) + IMM_I: imm = {{20{insn[31]}}, insn[31:20]}; + IMM_S: imm = {{20{insn[31]}}, insn[31:25], insn[11:7]}; + IMM_B: imm = {{19{insn[31]}}, insn[31], insn[7], insn[30:25], insn[11:8], 1'b0}; + IMM_U: imm = {insn[31:12], 12'b0}; + IMM_J: imm = {{11{insn[31]}}, insn[31], insn[19:12], insn[20], insn[30:21], 1'b0}; + default: imm = 32'b0; + endcase + end + +endmodule diff --git a/rtl/lsu.sv b/rtl/lsu.sv new file mode 100644 index 0000000..be157d5 --- /dev/null +++ b/rtl/lsu.sv @@ -0,0 +1,67 @@ +// Load/store unit datapath: byte-lane positioning, masks, load extension. +// +// Misaligned accesses are not supported (docs/PLAN.md section 2.2): the address is forced into +// alignment for the access and `misaligned` flags it. `rdata_lanes`/`wdata` are lane-positioned +// words with unselected lanes zero - the same convention as the golden ISS retire record. +module lsu + import tiny_cpu_pkg::*; +( + input logic [31:0] addr, // effective byte address (rs1 + imm) + input mem_size_t size, + input logic is_unsigned, // loads: zero-extend instead of sign-extend + input logic [31:0] store_data, // rs2 + input logic [31:0] mem_word, // raw aligned word returned by memory / MMIO + output logic [31:0] word_addr, // addr with the low two bits cleared + output logic [3:0] lane_mask, // byte lanes touched, after forced alignment + output logic [31:0] wdata, // store_data moved into its lanes + output logic [31:0] rdata_lanes, // mem_word restricted to lane_mask + output logic [31:0] load_value, // extended, right-justified load result + output logic misaligned +); + + logic [1:0] off; // byte offset after forced alignment + logic [31:0] size_mask; // low 8/16/32 bits + logic [31:0] lane_bits; // lane_mask expanded to a bit mask + logic [31:0] shifted; + + assign word_addr = {addr[31:2], 2'b00}; + + always_comb begin + case (size) + SZ_BYTE: begin + off = addr[1:0]; + lane_mask = 4'b0001 << off; + size_mask = 32'h0000_00FF; + misaligned = 1'b0; + end + SZ_HALF: begin + off = {addr[1], 1'b0}; + lane_mask = 4'b0011 << off; + size_mask = 32'h0000_FFFF; + misaligned = addr[0]; + end + default: begin + off = 2'b00; + lane_mask = 4'b1111; + size_mask = 32'hFFFF_FFFF; + misaligned = |addr[1:0]; + end + endcase + end + + assign lane_bits = {{8{lane_mask[3]}}, {8{lane_mask[2]}}, {8{lane_mask[1]}}, {8{lane_mask[0]}}}; + assign wdata = (store_data & size_mask) << {off, 3'b000}; + assign rdata_lanes = mem_word & lane_bits; + assign shifted = rdata_lanes >> {off, 3'b000}; + + always_comb begin + case (size) + SZ_BYTE: load_value = is_unsigned ? {24'b0, shifted[7:0]} + : {{24{shifted[7]}}, shifted[7:0]}; + SZ_HALF: load_value = is_unsigned ? {16'b0, shifted[15:0]} + : {{16{shifted[15]}}, shifted[15:0]}; + default: load_value = shifted; + endcase + end + +endmodule diff --git a/rtl/mem_comb.sv b/rtl/mem_comb.sv new file mode 100644 index 0000000..80ab3c6 --- /dev/null +++ b/rtl/mem_comb.sv @@ -0,0 +1,39 @@ +// Simulation memory with a COMBINATIONAL read port (used by the single-cycle core). +// Not FPGA-friendly - the pipelined core uses mem_sync.sv (Phase 3) for BRAM inference. +// +// Two write paths: the core's byte-masked write, and a full-word `ld_*` loader port the +// testbench/harness uses to place a program image while the core is in reset. +module mem_comb #( + parameter int unsigned WORDS = 1024, + localparam int unsigned AW = $clog2(WORDS) +) ( + input logic clk, + input logic [AW-1:0] raddr, // word index + output logic [31:0] rdata, + input logic [3:0] wmask, // per-byte write enables + input logic [AW-1:0] waddr, + input logic [31:0] wdata, + input logic ld_en, + input logic [AW-1:0] ld_addr, + input logic [31:0] ld_data +); + + logic [31:0] mem [WORDS]; + + initial begin + for (int i = 0; i < WORDS; i++) mem[i] = 32'b0; + end + + always_ff @(posedge clk) begin + if (ld_en) begin + mem[ld_addr] <= ld_data; + end else begin + for (int b = 0; b < 4; b++) begin + if (wmask[b]) mem[waddr][8*b +: 8] <= wdata[8*b +: 8]; + end + end + end + + assign rdata = mem[raddr]; + +endmodule diff --git a/rtl/mmio.sv b/rtl/mmio.sv new file mode 100644 index 0000000..b1a47c8 --- /dev/null +++ b/rtl/mmio.sv @@ -0,0 +1,44 @@ +// Data-side address decode and the three MMIO registers (docs/PLAN.md section 2.1). +// CONSOLE 0x0002_0000 write-only; lane-0 byte is printed +// TOHOST 0x0002_0004 write-only, word stores only; halts the core +// CYCLE 0x0002_0008 read-only cycle counter +// `fault` follows the golden ISS exactly: loads may hit DMEM or CYCLE; stores may hit DMEM, +// CONSOLE, or TOHOST (word-wide). Everything else - IMEM, unmapped, read of a write-only +// register, write of a read-only one - is a bus fault. +module mmio + import tiny_cpu_pkg::*; +( + input logic [31:0] word_addr, // word-aligned address from the LSU + input logic re, + input logic we, + input logic [3:0] wmask, + input logic [31:0] wdata, // lane-positioned + input logic [31:0] cycle, + output logic hit_dmem, + output logic [31:0] mmio_rdata, + output logic console_we, + output logic [7:0] console_data, + output logic tohost_we, + output logic [31:0] tohost_data, + output logic fault +); + + localparam logic [31:0] DMEM_BYTES = 32'(DMEM_WORDS * 4); + + logic hit_console, hit_tohost, hit_cycle; + + assign hit_dmem = (word_addr >= DMEM_BASE) && (word_addr < DMEM_BASE + DMEM_BYTES); + assign hit_console = (word_addr == CONSOLE_ADDR); + assign hit_tohost = (word_addr == TOHOST_ADDR); + assign hit_cycle = (word_addr == CYCLE_ADDR); + + assign mmio_rdata = hit_cycle ? cycle : 32'b0; + assign console_we = we && hit_console && wmask[0]; + assign console_data = wdata[7:0]; + assign tohost_we = we && hit_tohost && (wmask == 4'hF); + assign tohost_data = wdata; + + assign fault = (re && !(hit_dmem || hit_cycle)) || + (we && !(hit_dmem || hit_console || (hit_tohost && wmask == 4'hF))); + +endmodule diff --git a/rtl/regfile.sv b/rtl/regfile.sv new file mode 100644 index 0000000..09a08c2 --- /dev/null +++ b/rtl/regfile.sv @@ -0,0 +1,44 @@ +// 32 x 32-bit register file: two combinational read ports, one write port. +// * x0 reads as zero and ignores writes. +// * BYPASS=1 (pipelined core): write-first bypass - a read of the register being written this +// cycle returns the write data, removing the WB->ID hazard without an extra forwarding path. +// BYPASS=0 (single-cycle core): the writer IS the reader, so bypassing would feed an +// instruction's own result back into its operands (`lw x1,0(x1)`) and create a +// combinational loop. Reads always return the old register value. +// * Registers reset to zero so simulations are deterministic. +module regfile #( + parameter bit BYPASS = 1'b1 +) ( + input logic clk, + input logic rst_n, + input logic [4:0] raddr1, + input logic [4:0] raddr2, + output logic [31:0] rdata1, + output logic [31:0] rdata2, + input logic we, + input logic [4:0] waddr, + input logic [31:0] wdata, + output logic [32*32-1:0] dbg_regs // register i at [32*i +: 32] +); + + logic [31:0] regs [32]; + logic wr; + + assign wr = we && (waddr != 5'd0); + + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + for (int i = 0; i < 32; i++) regs[i] <= 32'b0; + end else if (wr) begin + regs[waddr] <= wdata; + end + end + + assign rdata1 = (BYPASS && wr && waddr == raddr1) ? wdata : regs[raddr1]; + assign rdata2 = (BYPASS && wr && waddr == raddr2) ? wdata : regs[raddr2]; + + for (genvar i = 0; i < 32; i++) begin : g_dbg + assign dbg_regs[32*i +: 32] = regs[i]; + end + +endmodule diff --git a/rtl/tiny_cpu_pkg.sv b/rtl/tiny_cpu_pkg.sv new file mode 100644 index 0000000..c0239e6 --- /dev/null +++ b/rtl/tiny_cpu_pkg.sv @@ -0,0 +1,68 @@ +// Shared constants and enums. The numeric encodings here are mirrored in sim/consts.py and +// checked against this file by sim/golden/test_consts.py - change both together. +/* verilator lint_off UNUSEDPARAM */ +package tiny_cpu_pkg; + + // ---- Memory map (docs/PLAN.md section 2.1) ------------------------------------------- + localparam int unsigned IMEM_WORDS = 1024; // 4 KiB + localparam int unsigned DMEM_WORDS = 1024; // 4 KiB + localparam int unsigned IMEM_AW = $clog2(IMEM_WORDS); + localparam int unsigned DMEM_AW = $clog2(DMEM_WORDS); + + localparam logic [31:0] IMEM_BASE = 32'h0000_0000; + localparam logic [31:0] DMEM_BASE = 32'h0001_0000; + localparam logic [31:0] CONSOLE_ADDR = 32'h0002_0000; + localparam logic [31:0] TOHOST_ADDR = 32'h0002_0004; + localparam logic [31:0] CYCLE_ADDR = 32'h0002_0008; + + // ---- RV32I major opcodes ------------------------------------------------------------- + localparam logic [6:0] OP_LUI = 7'h37; + localparam logic [6:0] OP_AUIPC = 7'h17; + localparam logic [6:0] OP_JAL = 7'h6F; + localparam logic [6:0] OP_JALR = 7'h67; + localparam logic [6:0] OP_BRANCH = 7'h63; + localparam logic [6:0] OP_LOAD = 7'h03; + localparam logic [6:0] OP_STORE = 7'h23; + localparam logic [6:0] OP_IMM = 7'h13; + localparam logic [6:0] OP_REG = 7'h33; + localparam logic [6:0] OP_FENCE = 7'h0F; + localparam logic [6:0] OP_SYSTEM = 7'h73; + + // ---- Control encodings --------------------------------------------------------------- + typedef enum logic [3:0] { + ALU_ADD = 4'd0, + ALU_SUB = 4'd1, + ALU_SLL = 4'd2, + ALU_SLT = 4'd3, + ALU_SLTU = 4'd4, + ALU_XOR = 4'd5, + ALU_SRL = 4'd6, + ALU_SRA = 4'd7, + ALU_OR = 4'd8, + ALU_AND = 4'd9, + ALU_COPY_B = 4'd10 // result = operand B (LUI) + } alu_op_t; + + typedef enum logic [2:0] { + IMM_I = 3'd0, + IMM_S = 3'd1, + IMM_B = 3'd2, + IMM_U = 3'd3, + IMM_J = 3'd4 + } imm_sel_t; + + typedef enum logic [1:0] { + WB_ALU = 2'd0, + WB_MEM = 2'd1, + WB_PC4 = 2'd2, + WB_CSR = 2'd3 + } wb_sel_t; + + typedef enum logic [1:0] { + SZ_BYTE = 2'd0, + SZ_HALF = 2'd1, + SZ_WORD = 2'd2 + } mem_size_t; + +endpackage +/* verilator lint_on UNUSEDPARAM */ diff --git a/rtl/tiny_cpu_top.sv b/rtl/tiny_cpu_top.sv new file mode 100644 index 0000000..3ee0db9 --- /dev/null +++ b/rtl/tiny_cpu_top.sv @@ -0,0 +1,85 @@ +// Top level: a core plus its memories, with the program loader and the debug/retire bus. +// +// `CORE` selects the microarchitecture ("SINGLE" now; "PIPE" arrives in Phase 3). The port list is +// identical for both so the testbenches and the WASM harness never change. +// +// Loading a program: hold rst_n low, then for each word pulse load_en with load_sel (0 = IMEM, +// 1 = DMEM), load_addr (word index) and load_data. Release rst_n to run. +module tiny_cpu_top + import tiny_cpu_pkg::*; +#( + parameter string CORE = "SINGLE", + // loader address width: wide enough for the larger memory + localparam int unsigned LAW = (IMEM_AW > DMEM_AW) ? IMEM_AW : DMEM_AW +) ( + input logic clk, + input logic rst_n, + + input logic load_en, + input logic load_sel, + input logic [LAW-1:0] load_addr, + input logic [31:0] load_data, + + output logic rvfi_valid, + output logic [31:0] rvfi_order, + output logic [31:0] rvfi_pc, + output logic [31:0] rvfi_insn, + output logic [4:0] rvfi_rd_addr, + output logic [31:0] rvfi_rd_wdata, + output logic [31:0] rvfi_pc_wdata, + output logic [31:0] rvfi_mem_addr, + output logic [3:0] rvfi_mem_rmask, + output logic [3:0] rvfi_mem_wmask, + output logic [31:0] rvfi_mem_rdata, + output logic [31:0] rvfi_mem_wdata, + output logic rvfi_misaligned, + output logic rvfi_halt, + output logic rvfi_cycle_dep, + + output logic console_valid, + output logic [7:0] console_data, + output logic tohost_valid, + output logic [31:0] tohost_data, + + output logic halted, + output logic fault, + output logic [31:0] dbg_pc, + output logic [32*32-1:0] dbg_regs, + output logic [31:0] dbg_cycle, + output logic [31:0] dbg_instret +); + + if (CORE == "SINGLE") begin : g_single + logic [IMEM_AW-1:0] imem_raddr; + logic [31:0] imem_rdata; + logic [DMEM_AW-1:0] dmem_raddr, dmem_waddr; + logic [31:0] dmem_rdata, dmem_wdata; + logic [3:0] dmem_wmask; + + core_single u_core ( + .clk, .rst_n, + .imem_raddr, .imem_rdata, + .dmem_raddr, .dmem_rdata, .dmem_wmask, .dmem_waddr, .dmem_wdata, + .rvfi_valid, .rvfi_order, .rvfi_pc, .rvfi_insn, .rvfi_rd_addr, .rvfi_rd_wdata, + .rvfi_pc_wdata, .rvfi_mem_addr, .rvfi_mem_rmask, .rvfi_mem_wmask, .rvfi_mem_rdata, + .rvfi_mem_wdata, .rvfi_misaligned, .rvfi_halt, .rvfi_cycle_dep, + .console_valid, .console_data, .tohost_valid, .tohost_data, + .halted, .fault, .dbg_pc, .dbg_regs, .dbg_cycle, .dbg_instret + ); + + mem_comb #(.WORDS(IMEM_WORDS)) u_imem ( + .clk, .raddr(imem_raddr), .rdata(imem_rdata), + .wmask(4'b0000), .waddr('0), .wdata(32'b0), // IMEM is read-only to the core + .ld_en(load_en && !load_sel), .ld_addr(load_addr[IMEM_AW-1:0]), .ld_data(load_data) + ); + + mem_comb #(.WORDS(DMEM_WORDS)) u_dmem ( + .clk, .raddr(dmem_raddr), .rdata(dmem_rdata), + .wmask(dmem_wmask), .waddr(dmem_waddr), .wdata(dmem_wdata), + .ld_en(load_en && load_sel), .ld_addr(load_addr[DMEM_AW-1:0]), .ld_data(load_data) + ); + end else begin : g_unsupported + $error("tiny_cpu_top: CORE=\"%s\" is not implemented yet (only \"SINGLE\")", CORE); + end + +endmodule diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100644 index 0000000..7ed1176 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Verilator -Wall lint of every RTL module standalone, plus the whole design from tiny_cpu_top. +# Usage: bash scripts/lint.sh (needs `source scripts/env.sh`). Prints only failures. +set -uo pipefail +cd "$(dirname "$0")/.." + +PKG=rtl/tiny_cpu_pkg.sv +LEAVES="alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb" +ALL="$PKG"; for m in $LEAVES core_single tiny_cpu_top; do ALL+=" rtl/$m.sv"; done +status=0 + +lint() { # lint + local top=$1; shift + if ! out=$(verilator --lint-only -Wall --top-module "$top" "$@" 2>&1); then + echo "LINT FAILED: $top"; echo "$out"; status=1 + elif echo "$out" | grep -q '^%Warning'; then + echo "LINT WARNINGS: $top"; echo "$out"; status=1 + else + echo "ok $top" + fi +} + +for m in $LEAVES; do lint "$m" "$PKG" "rtl/$m.sv"; done +lint core_single $ALL +lint tiny_cpu_top $ALL +lint counter rtl/spike/counter.sv +exit $status diff --git a/sim/Makefile b/sim/Makefile index 98bd702..2a8c1a0 100644 --- a/sim/Makefile +++ b/sim/Makefile @@ -1,11 +1,11 @@ # cocotb + Verilator. Defaults run the Phase 0 counter test. -# make MODULE=test_counter TOPLEVEL=counter VERILOG_SOURCES="$PWD/../rtl/spike/counter.sv" +# make COCOTB_TEST_MODULES=test_counter TOPLEVEL=counter VERILOG_SOURCES="$PWD/../rtl/spike/counter.sv" SIM ?= verilator TOPLEVEL_LANG ?= verilog VERILOG_SOURCES ?= $(abspath ../rtl/spike/counter.sv) TOPLEVEL ?= counter -MODULE ?= test_counter +COCOTB_TEST_MODULES ?= test_counter # Waveforms land in sim_build/dump.fst (open with gtkwave). CI passes WAVES=0. WAVES ?= 1 diff --git a/sim/consts.py b/sim/consts.py new file mode 100644 index 0000000..36d8dbb --- /dev/null +++ b/sim/consts.py @@ -0,0 +1,13 @@ +"""Numeric control encodings mirrored from rtl/tiny_cpu_pkg.sv (checked by golden/test_consts.py).""" + +ALU = dict(ADD=0, SUB=1, SLL=2, SLT=3, SLTU=4, XOR=5, SRL=6, SRA=7, OR=8, AND=9, COPY_B=10) +IMM = dict(I=0, S=1, B=2, U=3, J=4) +WB = dict(ALU=0, MEM=1, PC4=2, CSR=3) +SZ = dict(BYTE=0, HALF=1, WORD=2) + +# Memory map (docs/PLAN.md section 2.1) +IMEM_WORDS = DMEM_WORDS = 1024 +DMEM_BASE = 0x0001_0000 +CONSOLE_ADDR = 0x0002_0000 +TOHOST_ADDR = 0x0002_0004 +CYCLE_ADDR = 0x0002_0008 diff --git a/sim/golden/test_consts.py b/sim/golden/test_consts.py new file mode 100644 index 0000000..4f7664b --- /dev/null +++ b/sim/golden/test_consts.py @@ -0,0 +1,32 @@ +"""sim/consts.py must agree with rtl/tiny_cpu_pkg.sv and golden/iss.py.""" + +import re +from pathlib import Path + +import consts +from golden import iss + +PKG = (Path(__file__).resolve().parents[2] / "rtl" / "tiny_cpu_pkg.sv").read_text() + + +def enum_values(prefix: str) -> dict[str, int]: + return {m.group(1): int(m.group(2)) for m in re.finditer(rf"\b{prefix}_(\w+)\s*=\s*\d+'d(\d+)", PKG)} + + +def test_enum_encodings_match_package(): + assert enum_values("ALU") == consts.ALU + assert enum_values("IMM") == consts.IMM + assert enum_values("WB") == consts.WB + assert enum_values("SZ") == consts.SZ + + +def test_memory_map_matches_package_and_iss(): + def pkg(name): + return int(re.search(rf"{name}\s*=\s*32'h([0-9A-Fa-f_]+)", PKG).group(1).replace("_", ""), 16) + + assert pkg("DMEM_BASE") == consts.DMEM_BASE == iss.DMEM_BASE + assert pkg("CONSOLE_ADDR") == consts.CONSOLE_ADDR == iss.CONSOLE_ADDR + assert pkg("TOHOST_ADDR") == consts.TOHOST_ADDR == iss.TOHOST_ADDR + assert pkg("CYCLE_ADDR") == consts.CYCLE_ADDR == iss.CYCLE_ADDR + assert int(re.search(r"IMEM_WORDS\s*=\s*(\d+)", PKG).group(1)) * 4 == iss.IMEM_SIZE + assert int(re.search(r"DMEM_WORDS\s*=\s*(\d+)", PKG).group(1)) * 4 == iss.DMEM_SIZE diff --git a/sim/run.sh b/sim/run.sh new file mode 100644 index 0000000..e61e8e6 --- /dev/null +++ b/sim/run.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# cocotb suites. Usage: bash sim/run.sh unit | lockstep | all (needs `source scripts/env.sh`) +# Each suite gets its own SIM_BUILD: cocotb does not notice a TOPLEVEL change in a shared dir. +set -euo pipefail +cd "$(dirname "$0")" +PKG=../rtl/tiny_cpu_pkg.sv +FAILED=() + +run() { # run (env EXTRA_COMPILE_ARGS, SIM_TAG optional) + local mod=$1 top=$2; shift 2 + local srcs="" + for f in "$@"; do srcs+=" ../rtl/$f"; done + echo "=== $mod ($top)" + if ! make COCOTB_TEST_MODULES="$mod" TOPLEVEL="$top" VERILOG_SOURCES="$PKG$srcs" WAVES=0 \ + SIM_BUILD="sim_build/$mod${SIM_TAG:-}" EXTRA_ARGS="${EXTRA_COMPILE_ARGS:-}" COCOTB_RESULTS_FILE="results_$mod.xml" > "sim_build_$mod.log" 2>&1; then + FAILED+=("$mod"); tail -n 25 "sim_build_$mod.log" + elif grep -q 'FAIL=[1-9]' "sim_build_$mod.log"; then + FAILED+=("$mod"); grep -E "FAIL|Error|assert" "sim_build_$mod.log" | head -n 20 + else + grep -E "TESTS=" "sim_build_$mod.log" | sed 's/^ *\*\* //; s/ *\*\*$//; s/ */ /g' + fi + rm -f "sim_build_$mod.log" "results_$mod.xml" +} + +unit() { + run test_counter counter spike/counter.sv # Phase 0 toolchain check + run test_alu alu alu.sv + run test_imm_gen imm_gen imm_gen.sv + run test_branch_unit branch_unit branch_unit.sv + run test_lsu lsu lsu.sv + run test_decoder decoder decoder.sv + run test_regfile regfile regfile.sv + SIM_TAG=_nobypass EXTRA_COMPILE_ARGS="-GBYPASS=0" run test_regfile_nobypass regfile regfile.sv + run test_csr_counters csr_counters csr_counters.sv + run test_mmio mmio mmio.sv +} + +lockstep() { + run test_core_lockstep tiny_cpu_top alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv \ + csr_counters.sv mmio.sv mem_comb.sv core_single.sv tiny_cpu_top.sv +} + +case "${1:-all}" in + unit) unit ;; + lockstep) lockstep ;; + all) unit; lockstep ;; + *) echo "usage: $0 unit|lockstep|all" >&2; exit 2 ;; +esac + +if [ ${#FAILED[@]} -gt 0 ]; then echo "FAILED: ${FAILED[*]}"; exit 1; fi +echo "ALL SUITES PASSED" diff --git a/sim/test_alu.py b/sim/test_alu.py new file mode 100644 index 0000000..ebe11c3 --- /dev/null +++ b/sim/test_alu.py @@ -0,0 +1,39 @@ +import random + +import cocotb +from cocotb.triggers import Timer + +from consts import ALU +from golden.iss import _alu # the golden model's ALU is the oracle + +EDGE = [0, 1, 2, 31, 32, 33, 0x7FFFFFFF, 0x80000000, 0x80000001, 0xFFFFFFFF, 0xFFFFFFFE, 0x55555555, 0xAAAAAAAA] + + +def ref(op, a, b): + return b if op == "COPY_B" else _alu(op.lower(), a, b) + + +async def check(dut, op, a, b): + dut.a.value, dut.b.value, dut.op.value = a, b, ALU[op] + await Timer(1, "ns") + got = int(dut.y.value) + assert got == ref(op, a, b), f"{op}({a:#x}, {b:#x}) = {got:#x}, want {ref(op, a, b):#x}" + + +@cocotb.test() +async def every_op_edge_and_random(dut): + rng = random.Random(42) + for op in ALU: + pairs = [(a, b) for a in EDGE for b in EDGE] + pairs += [(rng.getrandbits(32), rng.getrandbits(32)) for _ in range(300)] + for a, b in pairs: + await check(dut, op, a, b) + + +@cocotb.test() +async def shifts_use_only_low_five_bits_of_b(dut): + rng = random.Random(7) + for op in ("SLL", "SRL", "SRA"): + for sh in range(64): # shamt 32..63 must wrap + for a in (1, 0x80000000, 0xDEADBEEF, rng.getrandbits(32)): + await check(dut, op, a, sh | (rng.getrandbits(27) << 5)) diff --git a/sim/test_branch_unit.py b/sim/test_branch_unit.py new file mode 100644 index 0000000..508a36e --- /dev/null +++ b/sim/test_branch_unit.py @@ -0,0 +1,26 @@ +import itertools + +import cocotb +from cocotb.triggers import Timer + +from golden.decode import s32 + +VALS = [0, 1, 2, 0x7FFFFFFF, 0x80000000, 0x80000001, 0xFFFFFFFE, 0xFFFFFFFF, 0x12345678] +REF = { + 0: lambda a, b: a == b, + 1: lambda a, b: a != b, + 4: lambda a, b: s32(a) < s32(b), + 5: lambda a, b: s32(a) >= s32(b), + 6: lambda a, b: a < b, + 7: lambda a, b: a >= b, + 2: lambda a, b: False, # reserved encodings never take + 3: lambda a, b: False, +} + + +@cocotb.test() +async def all_conditions(dut): + for f3, (a, b) in itertools.product(REF, itertools.product(VALS, VALS)): + dut.a.value, dut.b.value, dut.funct3.value = a, b, f3 + await Timer(1, "ns") + assert int(dut.taken.value) == int(REF[f3](a, b)), f"funct3={f3} a={a:#x} b={b:#x}" diff --git a/sim/test_core_lockstep.py b/sim/test_core_lockstep.py new file mode 100644 index 0000000..2a4188c --- /dev/null +++ b/sim/test_core_lockstep.py @@ -0,0 +1,254 @@ +"""RTL vs golden ISS, one retire at a time. + +Every program in sw/build/{riscv-tests,programs} is loaded into tiny_cpu_top. The ISS steps once for +each rvfi_valid pulse and all retire fields must match; the first divergence stops the run with the +last retired instructions of the ISS. Also checked: faults (ISS raises <=> RTL sets `fault`, with no +retire), halt behaviour, MMIO side effects, and CPI == 1 for the single-cycle core. + +Build the images first: make -C sw all. REQUIRE_RISCV_TESTS=1 turns a missing build into a failure. +""" + +import json +import os +from pathlib import Path + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge, Timer + +from golden.decode import encode as e +from golden.iss import IMEM_SIZE, DMEM_SIZE, Iss, IssError + +ROOT = Path(__file__).resolve().parent.parent +BUILD = ROOT / "sw" / "build" +EXPECTED = json.loads((ROOT / "sim" / "programs" / "expected.json").read_text()) +MAX_CYCLES = 200_000 + +FIELDS = [ # (RTL signal, ISS Retire attribute, cycle-dependent?) + ("rvfi_order", "order", False), ("rvfi_pc", "pc", False), ("rvfi_insn", "insn", False), + ("rvfi_rd_addr", "rd_addr", False), ("rvfi_rd_wdata", "rd_wdata", True), + ("rvfi_pc_wdata", "pc_wdata", False), ("rvfi_mem_addr", "mem_addr", False), + ("rvfi_mem_rmask", "mem_rmask", False), ("rvfi_mem_wmask", "mem_wmask", False), + ("rvfi_mem_rdata", "mem_rdata", True), ("rvfi_mem_wdata", "mem_wdata", False), + ("rvfi_misaligned", "misaligned", False), ("rvfi_halt", "halt", False), +] + + +def images(): + out = [] + for group in ("riscv-tests", "programs"): + out += sorted(p.name.removesuffix(".imem.bin") for p in (BUILD / group).glob("*.imem.bin")) + return out + + +def stem_path(name): + group = "riscv-tests" if name.startswith("rv32ui") else "programs" + return BUILD / group / name + + +def words(data: bytes, count: int) -> list[int]: + data = bytes(data).ljust(count * 4, b"\0") + return [int.from_bytes(data[4 * i : 4 * i + 4], "little") for i in range(count)] + + +def prog_bytes(*ws) -> bytes: + return b"".join((w & 0xFFFFFFFF).to_bytes(4, "little") for w in ws) + + +_clock = None + + +def ensure_clock(dut): + """One clock driver per test (a test may call lockstep() many times; finished tasks are dead).""" + global _clock + if _clock is None or _clock.done(): + _clock = cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) + + +async def reset_and_load(dut, imem: bytes, dmem: bytes): + ensure_clock(dut) + await Timer(1, "ns") # a previous lockstep() ends in the ReadOnly phase, where writes are illegal + dut.rst_n.value, dut.load_en.value = 0, 0 + dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 0, 0, 0 + await FallingEdge(dut.clk) + for sel, data, count in ((0, imem, IMEM_SIZE // 4), (1, dmem, DMEM_SIZE // 4)): + for i, w in enumerate(words(data, count)): # whole memory: wipes the previous program + dut.load_en.value, dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 1, sel, i, w + await FallingEdge(dut.clk) + dut.load_en.value = 0 + await FallingEdge(dut.clk) + dut.rst_n.value = 1 # first rising edge after this executes the instruction at pc 0 + + +def explain(history, msg): + tail = "\n".join(f" {r}" for r in history[-8:]) + return f"{msg}\n last ISS retires:\n{tail}" + + +async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | None = None): + """Run RTL and ISS together to halt (or fault). Returns (iss, rtl_console, cycles).""" + await reset_and_load(dut, imem, dmem) + iss = Iss(imem, dmem) + history, console, cycles, faulted = [], bytearray(), 0, False + + while True: + await RisingEdge(dut.clk) + await ReadOnly() + cycles += 1 + assert cycles <= MAX_CYCLES, explain(history, f"no halt after {MAX_CYCLES} cycles") + if int(dut.console_valid.value): + console.append(int(dut.console_data.value)) + + if int(dut.fault.value): # RTL refused an instruction: the ISS must raise on the same one + assert not int(dut.rvfi_valid.value), "RTL retired and faulted in the same cycle" + try: + iss.step() + except IssError: + faulted = True + break + raise AssertionError(explain(history, f"RTL faulted at pc={int(dut.dbg_pc.value):#x}, ISS executed it")) + + if int(dut.rvfi_valid.value): + try: + exp = iss.step() + except IssError as exc: + raise AssertionError(explain(history, f"RTL retired pc={int(dut.rvfi_pc.value):#x} but ISS raised: {exc}")) + for sig, attr, cyc_dep in FIELDS: + if cyc_dep and exp.cycle_dependent: + continue + got, want = int(getattr(dut, sig).value), int(getattr(exp, attr)) + assert got == want, explain( + history + [exp], f"retire #{exp.order} {attr}: RTL={got:#x} ISS={want:#x}\n at {exp}") + assert int(dut.rvfi_cycle_dep.value) == int(exp.cycle_dependent), explain(history + [exp], "cycle_dep flag") + history.append(exp) + if exp.halt: + break + else: + assert not iss.halted, "ISS halted but RTL never reported the halting retire" + + if expect_fault is not None: + assert faulted == expect_fault, f"expected fault={expect_fault}, got {faulted}" + + # after a halt/fault the core must be frozen + pc, regs = int(dut.dbg_pc.value), int(dut.dbg_regs.value) + for _ in range(4): + await RisingEdge(dut.clk) + await ReadOnly() + assert not int(dut.rvfi_valid.value) and not int(dut.console_valid.value) + assert int(dut.dbg_pc.value) == pc and int(dut.dbg_regs.value) == regs, "core moved after halt" + assert int(dut.halted.value) == int(not faulted) and int(dut.fault.value) == int(faulted) + + # architectural state matches + got_regs = int(dut.dbg_regs.value) + assert [(got_regs >> (32 * i)) & 0xFFFFFFFF for i in range(32)] == iss.regs, "register file differs" + assert bytes(console) == bytes(iss.console), f"console: RTL={bytes(console)!r} ISS={bytes(iss.console)!r}" + if not faulted: + assert bool(int(dut.tohost_valid.value)) == (iss.tohost is not None) + if iss.tohost is not None: + assert int(dut.tohost_data.value) == iss.tohost + assert int(dut.dbg_pc.value) == iss.pc + # single-cycle core: one instruction per clock, from reset release to the halting retire + assert cycles == iss.instret == int(dut.dbg_instret.value), ( + f"CPI != 1: cycles={cycles} instret={iss.instret} rtl_instret={int(dut.dbg_instret.value)}") + return iss, bytes(console), cycles + + +# --------------------------------------------------------------------------- images +@cocotb.test() +async def images_present(dut): + if not images(): + msg = "no images in sw/build (run `make -C sw all`)" + assert os.environ.get("REQUIRE_RISCV_TESTS") != "1", msg + dut._log.warning(msg) + + +NAMES = images() +if NAMES: + + @cocotb.parametrize(name=NAMES) + async def test_image(dut, name): + stem = stem_path(name) + imem, dmem = (Path(str(stem) + ".imem.bin").read_bytes(), Path(str(stem) + ".dmem.bin").read_bytes()) + iss, console, _ = await lockstep(dut, imem, dmem, expect_fault=False) + if name.startswith("rv32ui"): + assert iss.passed, f"{name}: failed test case #{(iss.tohost or 0) >> 1}" + else: + assert iss.passed + assert console.decode() == EXPECTED[name]["console"] + assert iss.instret == EXPECTED[name]["instret"] + + +# --------------------------------------------------------------------------- directed +TOHOST_HI = 0x20000 + + +@cocotb.test() +async def ecall_and_ebreak_halt_after_retiring(dut): + for halt_insn in (e("ecall"), e("ebreak")): + iss, _, cycles = await lockstep(dut, prog_bytes(e("addi", 1, 0, imm=7), halt_insn, e("addi", 1, 0, imm=9)), + expect_fault=False) + assert cycles == 2 and iss.regs[1] == 7 and iss.tohost is None + + +@cocotb.test() +async def x0_writes_and_self_dependent_instructions(dut): + """lw x1,0(x1)-style hazards that only exist in a single-cycle core with a (wrong) bypass.""" + prog = [e("lui", 1, imm=0x10000), e("addi", 2, 0, imm=0x55), e("sw", 0, 1, 2, imm=0), + e("addi", 1, 1, imm=0), e("lw", 1, 1, imm=0), # x1 = mem[x1] using the OLD x1 + e("addi", 0, 1, imm=5), e("add", 3, 0, 1), e("jalr", 1, 1, imm=0)] + prog[-1] = e("addi", 4, 1, imm=1) + await lockstep(dut, prog_bytes(*prog, e("ecall")), expect_fault=False) + + +@cocotb.test() +async def faults_stop_the_core_without_retiring(dut): + cases = { + "illegal instruction": [e("addi", 1, 0, imm=5), 0x00000000], + "csrrs with rs1 != x0": [e("csrrs", 1, 1, csr=0xC00)], + "unknown csr": [e("csrrs", 1, 0, csr=0x300)], + "load from IMEM": [e("lw", 1, 0, imm=0)], + "store to IMEM": [e("sw", 0, 0, 0, imm=0)], + "unmapped load": [e("lui", 1, imm=0x80000000), e("lw", 2, 1, imm=0)], + "tohost byte store": [e("lui", 1, imm=0x20000), e("sb", 0, 1, 0, imm=4)], + "store to CYCLE": [e("lui", 1, imm=0x20000), e("sw", 0, 1, 0, imm=8)], + "misaligned jal": [e("jal", 0, imm=6)], + "misaligned taken branch": [e("beq", 0, 0, 0, imm=2)], + "jalr to odd word": [e("addi", 1, 0, imm=6), e("jalr", 0, 1, imm=0)], + "jump past end of IMEM": [e("lui", 1, imm=0x1000), e("jalr", 0, 1, imm=0)], + "fall off end of IMEM": [e("jal", 0, imm=IMEM_SIZE - 4)], + } + for label, prog in cases.items(): + try: + await lockstep(dut, prog_bytes(*prog), expect_fault=True) + except AssertionError as exc: + raise AssertionError(f"[{label}] {exc}") from exc + + +@cocotb.test() +async def untaken_misaligned_branch_is_fine(dut): + prog = [e("addi", 1, 0, imm=1), e("beq", 0, 1, 0, imm=2), e("addi", 2, 0, imm=2), e("ecall")] + iss, _, _ = await lockstep(dut, prog_bytes(*prog), expect_fault=False) + assert iss.regs[2] == 2 + + +@cocotb.test() +async def misaligned_loads_stores_are_forced_into_alignment(dut): + prog = [e("lui", 1, imm=0x10000), e("lui", 2, imm=0x11223000), e("addi", 2, 2, imm=0x344), + e("sw", 0, 1, 2, imm=0), e("lw", 3, 1, imm=1), e("lh", 4, 1, imm=3), e("sh", 0, 1, 2, imm=1), + e("sw", 0, 1, 2, imm=3), e("lw", 5, 1, imm=0), e("ecall")] + await lockstep(dut, prog_bytes(*prog), expect_fault=False) + + +@cocotb.test() +async def jalr_clears_bit0_of_the_target(dut): + """No official rv32ui test jumps to an odd address, so pin it here: (rs1 + imm) & ~1.""" + # odd base register, even offset -> odd sum + prog = [e("addi", 1, 0, imm=0x11), e("jalr", 2, 1, imm=0), e("addi", 3, 0, imm=99), e("addi", 3, 0, imm=98), + e("ecall")] + iss, _, _ = await lockstep(dut, prog_bytes(*prog), expect_fault=False) + assert iss.regs[2] == 8 and iss.regs[3] == 0 # linked to pc+4, skipped both addi + # even base, odd offset -> odd sum + prog = [e("addi", 1, 0, imm=0x10), e("jalr", 2, 1, imm=1), e("addi", 3, 0, imm=99), e("addi", 3, 0, imm=98), + e("ecall")] + iss, _, _ = await lockstep(dut, prog_bytes(*prog), expect_fault=False) + assert iss.regs[3] == 0 diff --git a/sim/test_csr_counters.py b/sim/test_csr_counters.py new file mode 100644 index 0000000..0dfa6f7 --- /dev/null +++ b/sim/test_csr_counters.py @@ -0,0 +1,48 @@ +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, Timer + + +async def setup(dut): + cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) + dut.retire.value, dut.csr_addr.value, dut.rst_n.value = 0, 0, 0 + await Timer(12, "ns") + dut.rst_n.value = 1 + await FallingEdge(dut.clk) + + +async def csr(dut, addr): + dut.csr_addr.value = addr + await Timer(1, "ns") + return int(dut.csr_rdata.value) + + +@cocotb.test() +async def cycle_counts_every_clock_instret_only_on_retire(dut): + await setup(dut) + for _ in range(10): + await FallingEdge(dut.clk) + assert await csr(dut, 0xC00) == 10 + assert await csr(dut, 0xC01) == 10 # time aliases cycle + assert await csr(dut, 0xC02) == 0 + assert int(dut.cycle_lo.value) == 10 + dut.retire.value = 1 + for _ in range(4): + await FallingEdge(dut.clk) + dut.retire.value = 0 + for _ in range(3): + await FallingEdge(dut.clk) + assert await csr(dut, 0xC02) == 4 == int(dut.instret_lo.value) + assert await csr(dut, 0xC00) == 17 + assert await csr(dut, 0xC80) == await csr(dut, 0xC81) == await csr(dut, 0xC82) == 0 + assert await csr(dut, 0x300) == 0 # unknown CSR reads zero (the decoder never lets it through) + + +@cocotb.test() +async def reset_clears_counters(dut): + await setup(dut) + for _ in range(5): + await FallingEdge(dut.clk) + dut.rst_n.value = 0 + await Timer(1, "ns") + assert await csr(dut, 0xC00) == 0 diff --git a/sim/test_decoder.py b/sim/test_decoder.py new file mode 100644 index 0000000..9f6aacd --- /dev/null +++ b/sim/test_decoder.py @@ -0,0 +1,104 @@ +"""Decoder: control bundle per instruction, and legality identical to the golden ISS.""" + +import random + +import cocotb +from cocotb.triggers import Timer + +from consts import ALU, IMM, SZ, WB +from golden.decode import CSR_NAMES, INSNS, decode, encode + +SIGNALS = ["alu_op", "a_pc", "b_imm", "imm_sel", "reg_we", "wb_sel", "mem_re", "mem_we", "mem_size", + "mem_unsigned", "is_branch", "is_jal", "is_jalr", "uses_rs1", "uses_rs2", "is_halt", "illegal"] +DEFAULT = dict(alu_op=ALU["ADD"], a_pc=0, b_imm=0, imm_sel=IMM["I"], reg_we=0, wb_sel=WB["ALU"], mem_re=0, + mem_we=0, mem_size=SZ["WORD"], mem_unsigned=0, is_branch=0, is_jal=0, is_jalr=0, uses_rs1=0, + uses_rs2=0, is_halt=0, illegal=0) + + +def expected(name): + """Independent description of the control bundle for each instruction.""" + e = dict(DEFAULT) + if name == "lui": + e.update(alu_op=ALU["COPY_B"], b_imm=1, imm_sel=IMM["U"], reg_we=1) + elif name == "auipc": + e.update(a_pc=1, b_imm=1, imm_sel=IMM["U"], reg_we=1) + elif name == "jal": + e.update(imm_sel=IMM["J"], is_jal=1, reg_we=1, wb_sel=WB["PC4"]) + elif name == "jalr": + e.update(b_imm=1, is_jalr=1, reg_we=1, wb_sel=WB["PC4"], uses_rs1=1) + elif name in ("beq", "bne", "blt", "bge", "bltu", "bgeu"): + e.update(imm_sel=IMM["B"], is_branch=1, uses_rs1=1, uses_rs2=1) + elif name in ("lb", "lh", "lw", "lbu", "lhu"): + size = {"b": "BYTE", "h": "HALF", "w": "WORD"}[name[1]] + e.update(b_imm=1, mem_re=1, reg_we=1, wb_sel=WB["MEM"], uses_rs1=1, mem_size=SZ[size], + mem_unsigned=int(name.endswith("u"))) + elif name in ("sb", "sh", "sw"): + size = {"b": "BYTE", "h": "HALF", "w": "WORD"}[name[1]] + e.update(b_imm=1, imm_sel=IMM["S"], mem_we=1, uses_rs1=1, uses_rs2=1, mem_size=SZ[size]) + elif name in ("addi", "slti", "sltiu", "xori", "ori", "andi", "slli", "srli", "srai"): + op = {"addi": "ADD", "slti": "SLT", "sltiu": "SLTU", "xori": "XOR", "ori": "OR", "andi": "AND", + "slli": "SLL", "srli": "SRL", "srai": "SRA"}[name] + e.update(alu_op=ALU[op], b_imm=1, reg_we=1, uses_rs1=1) + elif name in ("add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and"): + e.update(alu_op=ALU[name.upper()], reg_we=1, uses_rs1=1, uses_rs2=1) + elif name == "fence": + pass + elif name in ("ecall", "ebreak"): + e.update(is_halt=1) + elif name == "csrrs": + e.update(reg_we=1, wb_sel=WB["CSR"]) + else: + raise AssertionError(name) + return e + + +def legal(word): + """The golden ISS's notion of an executable instruction.""" + x = decode(word) + if x is None: + return False + return x.rs1 == 0 and x.csr in CSR_NAMES if x.name == "csrrs" else True + + +async def sample(dut, word): + dut.insn.value = word + await Timer(1, "ns") + return {s: int(getattr(dut, s).value) for s in SIGNALS} + + +@cocotb.test() +async def every_instruction_control_bundle(dut): + rng = random.Random(42) + for d in INSNS: + for _ in range(60): + imm = {"I": rng.randint(-2048, 2047), "SH": rng.randrange(32), "S": rng.randint(-2048, 2047), + "B": rng.randrange(-2048, 2048) * 2, "U": rng.randrange(1 << 20) << 12, + "J": rng.randrange(-(1 << 19), 1 << 19) * 2}.get(d.fmt, 0) + word = encode(d.name, rng.randrange(32), rng.randrange(32), rng.randrange(32), imm, + csr=rng.choice(list(CSR_NAMES))) + if d.name == "csrrs": + word = encode("csrrs", rng.randrange(32), 0, csr=rng.choice(list(CSR_NAMES))) + got = await sample(dut, word) + want = expected(d.name) + assert got == want, f"{d.name} {word:#010x}: " + ", ".join( + f"{k}={got[k]} (want {want[k]})" for k in SIGNALS if got[k] != want[k]) + + +@cocotb.test() +async def legality_matches_iss_and_illegal_is_inert(dut): + rng = random.Random(1234) + words = [0x00000000, 0xFFFFFFFF, 0x00000073, 0x00100073, 0x00200073, 0x30200073] + words += [rng.getrandbits(32) for _ in range(4000)] + # single-bit and field mutations of legal instructions probe the funct3/funct7 boundaries + for d in INSNS: + base = d.match | (rng.getrandbits(32) & ~d.mask & ~0x7F & 0xFFFFFFFF) + for bit in range(32): + words.append(base ^ (1 << bit)) + for w in words: + got = await sample(dut, w) + assert got["illegal"] == int(not legal(w)), f"{w:#010x}: illegal={got['illegal']}, ISS legal={legal(w)}" + if not legal(w): + for s in ("reg_we", "mem_re", "mem_we", "is_branch", "is_jal", "is_jalr", "is_halt"): + assert got[s] == 0, f"{w:#010x}: illegal instruction has {s}=1" + else: + assert {k: v for k, v in got.items()} == expected(decode(w).name), f"{w:#010x} {decode(w).name}" diff --git a/sim/test_imm_gen.py b/sim/test_imm_gen.py new file mode 100644 index 0000000..3d84b1a --- /dev/null +++ b/sim/test_imm_gen.py @@ -0,0 +1,30 @@ +import random + +import cocotb +from cocotb.triggers import Timer + +from consts import IMM +from golden.decode import imm_b, imm_i, imm_j, imm_s, imm_u + +REF = {"I": imm_i, "S": imm_s, "B": imm_b, "U": imm_u, "J": imm_j} + + +@cocotb.test() +async def every_format(dut): + rng = random.Random(42) + words = [0, 0xFFFFFFFF, 0x80000000, 0x7FFFFFFF, 0x00000FFF, 0xFFFFF000, 0x7FF00000, 0x80000F80] + words += [rng.getrandbits(32) for _ in range(2000)] + for fmt, code in IMM.items(): + for w in words: + dut.insn.value, dut.sel.value = w, code + await Timer(1, "ns") + want = REF[fmt](w) & 0xFFFFFFFF + assert int(dut.imm.value) == want, f"{fmt} {w:#010x}: got {int(dut.imm.value):#x}, want {want:#x}" + + +@cocotb.test() +async def undefined_select_gives_zero(dut): + for sel in (5, 6, 7): + dut.insn.value, dut.sel.value = 0xFFFFFFFF, sel + await Timer(1, "ns") + assert int(dut.imm.value) == 0 diff --git a/sim/test_lsu.py b/sim/test_lsu.py new file mode 100644 index 0000000..e01a250 --- /dev/null +++ b/sim/test_lsu.py @@ -0,0 +1,54 @@ +"""LSU vs the golden ISS: same lane/mask/extension conventions for every size x offset.""" + +import itertools +import random + +import cocotb +from cocotb.triggers import Timer + +from consts import SZ +from golden.iss import DMEM_BASE, Iss + +NAME = {("BYTE", False): "lb", ("BYTE", True): "lbu", ("HALF", False): "lh", ("HALF", True): "lhu", + ("WORD", False): "lw", ("WORD", True): "lw"} +STORE = {"BYTE": "sb", "HALF": "sh", "WORD": "sw"} + + +@cocotb.test() +async def loads_match_iss(dut): + rng = random.Random(42) + iss = Iss() + for (size, uns), off in itertools.product(NAME, range(4)): + for _ in range(40): + word = rng.getrandbits(32) + base = DMEM_BASE + 16 + iss.dmem[16:20] = word.to_bytes(4, "little") + want_val, want_word_addr, want_mask, want_lanes, want_mis = iss._load(NAME[(size, uns)], base + off, 0, 0) + dut.addr.value, dut.size.value = base + off, SZ[size] + dut.is_unsigned.value, dut.store_data.value, dut.mem_word.value = int(uns), 0, word + await Timer(1, "ns") + ctx = f"{NAME[(size, uns)]} off={off} word={word:#x}" + assert int(dut.word_addr.value) == want_word_addr, ctx + assert int(dut.lane_mask.value) == want_mask, ctx + assert int(dut.rdata_lanes.value) == want_lanes, ctx + assert int(dut.load_value.value) == want_val, f"{ctx}: {int(dut.load_value.value):#x} != {want_val:#x}" + assert bool(int(dut.misaligned.value)) == want_mis, ctx + + +@cocotb.test() +async def stores_match_iss(dut): + rng = random.Random(43) + for size, off in itertools.product(STORE, range(4)): + for _ in range(40): + val = rng.getrandbits(32) + iss = Iss() + iss.dmem[16:20] = bytes(4) + want_word_addr, want_mask, want_wdata, want_mis = iss._store(STORE[size], DMEM_BASE + 16 + off, val, 0, 0) + dut.addr.value, dut.size.value = DMEM_BASE + 16 + off, SZ[size] + dut.is_unsigned.value, dut.store_data.value, dut.mem_word.value = 0, val, 0 + await Timer(1, "ns") + ctx = f"{STORE[size]} off={off} val={val:#x}" + assert int(dut.word_addr.value) == want_word_addr, ctx + assert int(dut.lane_mask.value) == want_mask, ctx + assert int(dut.wdata.value) == want_wdata, f"{ctx}: {int(dut.wdata.value):#x} != {want_wdata:#x}" + assert bool(int(dut.misaligned.value)) == want_mis, ctx diff --git a/sim/test_mmio.py b/sim/test_mmio.py new file mode 100644 index 0000000..141dff2 --- /dev/null +++ b/sim/test_mmio.py @@ -0,0 +1,62 @@ +"""MMIO decode vs the golden ISS's fault rules, for every region and access shape.""" + +import itertools + +import cocotb +from cocotb.triggers import Timer + +from consts import CONSOLE_ADDR, CYCLE_ADDR, DMEM_BASE, TOHOST_ADDR +from golden.iss import BusFault, Iss + +ADDRS = [0x0, 0x4, 0xFFC, 0x1000, DMEM_BASE - 4, DMEM_BASE, DMEM_BASE + 0xFFC, DMEM_BASE + 0x1000, + CONSOLE_ADDR - 4, CONSOLE_ADDR, TOHOST_ADDR, CYCLE_ADDR, CYCLE_ADDR + 4, 0x8000_0000, 0xFFFF_FFFC] + + +def iss_faults(kind, addr, mask): + iss = Iss() + try: + if kind == "load": + size, off = {1: ("lb", 0), 3: ("lh", 0), 0xF: ("lw", 0)}.get(mask, ("lb", 0)) + iss._load(size, addr, 0, 0) + else: + iss._store({1: "sb", 3: "sh", 0xF: "sw"}.get(mask, "sb"), addr, 0xA5, 0, 0) + except BusFault: + return True + return False + + +async def drive(dut, addr, re, we, mask, wdata=0x1234_5678, cycle=0xCAFE): + dut.word_addr.value, dut.re.value, dut.we.value = addr, re, we + dut.wmask.value, dut.wdata.value, dut.cycle.value = mask, wdata, cycle + await Timer(1, "ns") + + +@cocotb.test() +async def fault_rules_match_iss(dut): + for addr, mask in itertools.product(ADDRS, (1, 3, 0xF)): + await drive(dut, addr, 1, 0, mask) + assert bool(int(dut.fault.value)) == iss_faults("load", addr, mask), f"load {addr:#x}" + await drive(dut, addr, 0, 1, mask) + assert bool(int(dut.fault.value)) == iss_faults("store", addr, mask), f"store {addr:#x} mask={mask:#x}" + await drive(dut, addr, 0, 0, mask) + assert int(dut.fault.value) == 0, "no access, no fault" + + +@cocotb.test() +async def region_hits_and_register_effects(dut): + await drive(dut, DMEM_BASE + 8, 1, 0, 0xF) + assert int(dut.hit_dmem.value) == 1 and int(dut.console_we.value) == 0 + await drive(dut, DMEM_BASE + 0x1000, 1, 0, 0xF) + assert int(dut.hit_dmem.value) == 0 + await drive(dut, CONSOLE_ADDR, 0, 1, 0b0001, wdata=0x41) + assert int(dut.console_we.value) == 1 and int(dut.console_data.value) == 0x41 + await drive(dut, CONSOLE_ADDR, 0, 1, 0b0010, wdata=0x4100) # lane 1 only: nothing printed + assert int(dut.console_we.value) == 0 + await drive(dut, TOHOST_ADDR, 0, 1, 0xF, wdata=1) + assert int(dut.tohost_we.value) == 1 and int(dut.tohost_data.value) == 1 + await drive(dut, TOHOST_ADDR, 0, 1, 0b0001, wdata=1) + assert int(dut.tohost_we.value) == 0 and int(dut.fault.value) == 1 + await drive(dut, CYCLE_ADDR, 1, 0, 0xF, cycle=0xBEEF) + assert int(dut.mmio_rdata.value) == 0xBEEF and int(dut.fault.value) == 0 + await drive(dut, DMEM_BASE, 1, 0, 0xF, cycle=0xBEEF) + assert int(dut.mmio_rdata.value) == 0 diff --git a/sim/test_regfile.py b/sim/test_regfile.py new file mode 100644 index 0000000..7fc05d3 --- /dev/null +++ b/sim/test_regfile.py @@ -0,0 +1,73 @@ +import random + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge, Timer + + +async def setup(dut): + cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) + dut.we.value, dut.waddr.value, dut.wdata.value = 0, 0, 0 + dut.raddr1.value = dut.raddr2.value = 0 + dut.rst_n.value = 0 + await Timer(12, "ns") + dut.rst_n.value = 1 + await FallingEdge(dut.clk) + + +async def write(dut, addr, val): + dut.we.value, dut.waddr.value, dut.wdata.value = 1, addr, val + await FallingEdge(dut.clk) # the rising edge in between performs the write + dut.we.value = 0 + + +async def read(dut, a1, a2=0): + dut.raddr1.value, dut.raddr2.value = a1, a2 + await Timer(1, "ns") + return int(dut.rdata1.value), int(dut.rdata2.value) + + +@cocotb.test() +async def reset_clears_everything(dut): + await setup(dut) + for i in range(32): + assert (await read(dut, i, 31 - i)) == (0, 0) + assert int(dut.dbg_regs.value) == 0 + + +@cocotb.test() +async def write_read_all_registers_and_x0_is_zero(dut): + await setup(dut) + rng = random.Random(42) + model = [0] * 32 + for _ in range(600): + a, v = rng.randrange(32), rng.getrandbits(32) + await write(dut, a, v) + if a: + model[a] = v + r1, r2 = rng.randrange(32), rng.randrange(32) + assert (await read(dut, r1, r2)) == (model[r1], model[r2]) + assert model[0] == 0 + dbg = int(dut.dbg_regs.value) + assert [(dbg >> (32 * i)) & 0xFFFFFFFF for i in range(32)] == model + + +@cocotb.test() +async def write_first_bypass(dut): + await setup(dut) + await write(dut, 5, 0x1111) + dut.we.value, dut.waddr.value, dut.wdata.value = 1, 5, 0x2222 + assert (await read(dut, 5, 6)) == (0x2222, 0) # same-cycle read sees the new value + dut.waddr.value = 0 + assert (await read(dut, 0, 5)) == (0, 0x1111) # x0 never bypasses; x5 not yet overwritten + dut.we.value = 0 + await FallingEdge(dut.clk) + assert (await read(dut, 5, 5)) == (0x1111, 0x1111) + + +@cocotb.test() +async def we_low_does_not_write(dut): + await setup(dut) + dut.we.value, dut.waddr.value, dut.wdata.value = 0, 9, 0xDEAD + await FallingEdge(dut.clk) + assert (await read(dut, 9)) == (0, 0) diff --git a/sim/test_regfile_nobypass.py b/sim/test_regfile_nobypass.py new file mode 100644 index 0000000..2a35eb5 --- /dev/null +++ b/sim/test_regfile_nobypass.py @@ -0,0 +1,17 @@ +"""regfile with BYPASS=0 (single-cycle core): a read never sees the write happening this cycle.""" + +import cocotb +from cocotb.triggers import FallingEdge + +from test_regfile import read, setup, write + + +@cocotb.test() +async def read_returns_old_value_during_a_write(dut): + await setup(dut) + await write(dut, 5, 0x1111) + dut.we.value, dut.waddr.value, dut.wdata.value = 1, 5, 0x2222 + assert (await read(dut, 5, 5)) == (0x1111, 0x1111) # no bypass + await FallingEdge(dut.clk) # the write lands at the rising edge + dut.we.value = 0 + assert (await read(dut, 5, 5)) == (0x2222, 0x2222) From ec737b1791093d8c8dcc84488f1a3b64f540b7de Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sun, 25 Oct 2026 01:33:23 +0530 Subject: [PATCH 04/10] feat: Phase 3 5-stage pipelined core core_pipe (IF/ID/EX/MEM/WB with taken-branch flush, precise faults and halt), mem_sync with write-first read, CORE="PIPE" in tiny_cpu_top and a uniform per-stage debug bus on both cores. Adds the Prog mini-assembler and dynamic RAW-hazard checker, hazard-free program set, a shared lockstep library and a pipeline lockstep suite with an exact cycle-count check. --- .github/workflows/rtl.yml | 5 +- AGENTS.md | 1 + CLAUDE.md | 1 + CONTEXT.md | 12 +- README.md | 4 +- docs/ARCHITECTURE.md | 59 +++++- docs/PLAN.md | 2 +- rtl/core_pipe.sv | 368 +++++++++++++++++++++++++++++++++++ rtl/core_single.sv | 9 +- rtl/mem_sync.sv | 53 +++++ rtl/tiny_cpu_top.sv | 65 ++++++- scripts/lint.sh | 8 +- sim/golden/hazard_free.py | 302 ++++++++++++++++++++++++++++ sim/golden/progbuild.py | 206 ++++++++++++++++++++ sim/golden/test_progbuild.py | 119 +++++++++++ sim/lockstep_lib.py | 186 ++++++++++++++++++ sim/run.sh | 15 +- sim/test_core_lockstep.py | 151 +------------- sim/test_mem_sync.py | 83 ++++++++ sim/test_pipe_lockstep.py | 171 ++++++++++++++++ 20 files changed, 1653 insertions(+), 167 deletions(-) create mode 100644 rtl/core_pipe.sv create mode 100644 rtl/mem_sync.sv create mode 100644 sim/golden/hazard_free.py create mode 100644 sim/golden/progbuild.py create mode 100644 sim/golden/test_progbuild.py create mode 100644 sim/lockstep_lib.py create mode 100644 sim/test_mem_sync.py create mode 100644 sim/test_pipe_lockstep.py diff --git a/.github/workflows/rtl.yml b/.github/workflows/rtl.yml index defe743..2757795 100644 --- a/.github/workflows/rtl.yml +++ b/.github/workflows/rtl.yml @@ -74,7 +74,10 @@ jobs: - name: cocotb - unit suites run: bash sim/run.sh unit - - name: cocotb - core lockstep vs ISS + - name: cocotb - single-cycle core lockstep vs ISS run: bash sim/run.sh lockstep env: REQUIRE_RISCV_TESTS: '1' + + - name: cocotb - 5-stage core lockstep vs ISS (hazard-free programs) + run: bash sim/run.sh pipe diff --git a/AGENTS.md b/AGENTS.md index 1579a02..4380051 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,7 @@ Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (C bash scripts/lint.sh # verilator -Wall on every module standalone + the full design bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) +bash sim/run.sh pipe # core_pipe vs golden ISS over the hazard-free programs (no toolchain needed) bash sim/run.sh all # everything above ``` Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` diff --git a/CLAUDE.md b/CLAUDE.md index 2daf84c..5a31909 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,6 +39,7 @@ Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (C bash scripts/lint.sh # verilator -Wall on every module standalone + the full design bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) +bash sim/run.sh pipe # core_pipe vs golden ISS over the hazard-free programs (no toolchain needed) bash sim/run.sh all # everything above ``` Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` diff --git a/CONTEXT.md b/CONTEXT.md index 4d060b1..9d9c17a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -17,7 +17,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Phase 2 (single-cycle core): DONE. `rtl/` has the pkg, alu, imm_gen, branch_unit, lsu, decoder, regfile, csr_counters, mmio, mem_comb, core_single, tiny_cpu_top. Lint clean; unit suites for every block; `core_single` passes lockstep against the ISS on all 40 riscv-tests + 5 programs, 13 fault cases and CPI == 1. See docs/ARCHITECTURE.md. -- Next: Phase 3 (5-stage pipeline without hazard handling, `mem_sync.sv`, per-stage `dbg_*` ports). +- Phase 3 (5-stage pipeline, no forwarding/stall): DONE. `mem_sync.sv`, `core_pipe.sv`, `CORE="PIPE"` in the top, uniform + per-stage `dbg_*` ports on both cores, `golden/progbuild.py` + `hazard_free.py`. 74 pipe lockstep tests pass; 7 mutants killed. + Taken-branch flush landed here (not Phase 4) because lockstep needs it. See docs/ARCHITECTURE.md. +- Next: Phase 4 (`forward_unit`, `hazard_unit`: load-use stall + `cfg_forward_en`, pinned hazard cycle counts, riscv-tests on PIPE). ## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) - Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. @@ -65,3 +68,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on Makefile's `--prefix Vtop`) - use `EXTRA_ARGS`. After `await ReadOnly()` you cannot write a signal: await a `Timer` first. - A test that calls a helper repeatedly must start the clock once (`ensure_clock` in test_core_lockstep.py). - Passing tests prove nothing until a mutant fails them: every suite here was mutation-checked; the jalr-bit0 gap was found that way. + +## Gotchas found in Phase 3 +- Pipeline needs a write-first DMEM (`mem_sync` READ_NEW=1): a load in EX reads at the same edge the store ahead of it writes. +- Run a PIPE suite with `TINYCPU_CORE=PIPE` and `-G'CORE="PIPE"'` (sim/run.sh pipe does both); `CORE` is a string parameter. +- Cycle-dependent registers must be masked in the final register compare too, not only per retire. +- Dynamic hazard distance != static: instructions at jump targets skip the jump's padding (use `raw_hazards()` on the ISS trace). +- Hazard-free means >= 3 instructions between producer and consumer (2 NOPs) for this pipeline; `Prog.li/exit/putc` add gap NOPs at pad<2. diff --git a/README.md b/README.md index 6586477..ffa248e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 2 done (single-cycle core).** The RV32I RTL core retires every instruction identically to the Python golden ISS across all 40 official `rv32ui-p-*` tests and 5 example programs. Next: the 5-stage pipeline. +> **Status: Phase 3 done (5-stage pipeline datapath).** Both RV32I cores retire instructions identically to the Python golden ISS: the single-cycle core on all 40 official `rv32ui-p-*` tests plus 5 programs, the 5-stage core on hazard-free programs. Next: forwarding and hazard stalls so the pipeline runs arbitrary code. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the @@ -34,7 +34,7 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl | Path | Contents | |---|---| -| `rtl/` | SystemVerilog: single-cycle core + building blocks (`spike/counter.sv` is the Phase 0 toolchain check) | +| `rtl/` | SystemVerilog: single-cycle and 5-stage cores + building blocks (`spike/counter.sv` is the Phase 0 toolchain check) | | `sim/` | golden ISS (`sim/golden/`), cocotb tests, asm programs (`sim/programs/`) | | `sw/` | linker script, crt0, riscv-tests port + Makefile | | `wasm/` | Verilator -> Emscripten harness | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index dc17d18..8755821 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -54,7 +54,59 @@ One instruction retires per clock (CPI = 1), so `cycles == instret`; the lockste * `cycle_dependent` retires (`rdcycle`, `rdtime`, the CYCLE MMIO read) are flagged by the RTL and masked by the lockstep comparison, because the ISS cannot know pipeline timing. -## Verification (Phase 2) +## 5-stage pipelined core (`rtl/core_pipe.sv`, Phase 3) + +``` + pc_q --> IMEM(sync) ==> [IF/ID] --> decode, regfile read ==> [ID/EX] --> ALU, branch ==> [EX/MEM] --> LSU, MMIO ==> [MEM/WB] --> reg write, retire + ^ addressed id_pc DMEM addressed here store/console/tohost CCSR read + | by pc_q (sync read) effects happen here + +--------------------------- redirect (taken branch / jal / jalr) from EX: flush IF/ID + ID/EX ---------------------------+ +``` + +Real output of `sim/test_pipe_lockstep.py::five_instructions_overlap_and_flushes_match_redirects` (each cell is the pc +in that stage after the clock edge; the `jal` at 0x48 is in EX when it flushes): + +``` +cycle IF ID EX MEM WB + 18 0x0048 0x0044 0x0040 0x003c 0x0038 five instructions in flight + 19 0x004c 0x0048 0x0044 0x0040 0x003c + 20 0x0050 0x004c 0x0048 0x0044 0x0040 <- flush (jal 0x48 -> 0x54) + 21 0x0054 -- -- 0x0048 0x0044 two bubbles = the 2-cycle penalty + 22 0x0058 0x0054 -- -- 0x0048 +``` + +Phase 3 scope: datapath, flush, faults and halt. **No forwarding and no load-use stall yet** - producers must be +>= 3 instructions ahead of consumers (the register file's write-first bypass covers WB->ID). Phase 4 adds the +hazard/forward units, after which the riscv-tests images run on this core too. + +Deviation from the original plan: the taken-branch flush is in Phase 3, not 4. Without it the NOPs after a taken +branch retire on the wrong path and the lockstep stream can never match the ISS. + +### Timing contract +* **Synchronous memories.** `pc_q` is the IMEM address; the BRAM output register *is* the instruction in ID, with `id_pc` + carried beside it. DMEM is addressed in EX, so load data is back in MEM, where the LSU extracts and extends it. + The IF stage therefore has no instruction word to show (`dbg_stage_insn[IF] = 0`; look it up from the pc). +* **Write-first DMEM** (`mem_sync` `READ_NEW=1`). A load reads DMEM in EX at the same clock edge at which the store + ahead of it writes in MEM; without write-first the load returns stale data. This is the memory twin of a forwarding + path, and `mem_raw_adjacent` pins it (the only test that fails when it is removed). +* **Cycle count.** First retire is visible after 5 edges, then one per cycle: `cycles = instret + 4 + 2 * redirects`. + The lockstep test asserts this exactly, and the flush flag fires once per redirect. +* **CSR read in WB.** `instret` seen by an instruction equals the number retired before it, as in the ISS. + `dbg_pc` is the *architectural* pc (next instruction to retire), not the fetch pc. + +### Precise faults and halt +A faulting or halting instruction travels to WB like any other. While it sits there (`older_stop`) younger +instructions in MEM are gated from writing DMEM, CONSOLE or TOHOST; once it leaves, `active` freezes the core. +Faulting instructions never retire; nothing younger ever retires. `younger_instructions_have_no_effect_after_*` +runs dense code with stores directly behind the faulting/halting instruction and checks that nothing leaks. + +### Lessons +* `lockstep` must mask registers last written by timing-dependent reads (rdcycle, CYCLE MMIO) in the *final* + register compare as well as per-retire; the single-cycle core hid this because there cycle == instret. +* A taken jump flushes its own padding NOPs, so the instruction at a jump *target* is only 1 instruction behind the + jump's link register. `Prog` pads after instructions, not at labels: the dynamic `raw_hazards()` check found this. + +## Verification (Phase 2 and 3) | Layer | Where | What | |---|---|---| @@ -63,4 +115,7 @@ One instruction retires per clock (CPI = 1), so `cycles == instret`; the lockste | Lockstep | `sim/test_core_lockstep.py` | 45 images (40 riscv-tests + 5 programs) retire-for-retire against the ISS; 13 fault cases; halt/frozen-core checks; CPI = 1 | | Lint | `scripts/lint.sh` | `-Wall` clean for every module standalone and the whole design | -Run: `bash scripts/lint.sh && bash sim/run.sh all` (after `make -C sw all`). +| Pipeline lockstep | `sim/test_pipe_lockstep.py` | 9 hazard-free programs + 60 random ones retire-for-retire vs the ISS; 13 fault cases; younger-instruction gating after fault/halt; exact cycle formula; 5-deep overlap; flush count | +| Program builder | `sim/golden/progbuild.py`, `hazard_free.py` | `Prog(pad=N)` mini-assembler; `raw_hazards()` proves a program hazard-free from its dynamic ISS trace (no toolchain needed) | + +Run: `bash scripts/lint.sh && bash sim/run.sh all` (after `make -C sw all`). `all` = unit + lockstep (single-cycle) + pipe. diff --git a/docs/PLAN.md b/docs/PLAN.md index b078658..8a93571 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -359,7 +359,7 @@ Estimates assume one person working part-time, roughly at the pace of tiny-tpu. ### Phase 3: Pipelined core without hazard handling (1 week) - `mem_sync`, `core_pipe` with IF/ID/EX/MEM/WB registers (packed structs), `dbg_*` ports per stage. -- Hazards are handled by hand-inserted NOPs only. A `hazard_free/*.S` set proves the datapath works. +- Data hazards are avoided by construction (NOP padding via `golden/progbuild.py`); `hazard_free.py` holds the program set. The taken-branch flush is included here because lockstep cannot match the ISS without it. - **Exit:** the hazard-free programs pass lockstep; the waveform in GTKWave shows the 5-stage overlap. ### Phase 4: Hazards (1.5 weeks) diff --git a/rtl/core_pipe.sv b/rtl/core_pipe.sv new file mode 100644 index 0000000..e5fa15d --- /dev/null +++ b/rtl/core_pipe.sv @@ -0,0 +1,368 @@ +// 5-stage pipelined RV32I core: IF -> ID -> EX -> MEM -> WB. +// +// Phase 3 scope: the datapath, branch/jump flush, faults and halt. There is NO forwarding and NO +// load-use stall yet (Phase 4), so correct results require producers to be >= 3 instructions +// ahead of consumers (the register file's write-first bypass covers WB -> ID). The programs in +// sim/golden/hazard_free.py are built that way. +// +// Timing (docs/ARCHITECTURE.md): +// IF pc_q addresses IMEM (synchronous read). The instruction word is the BRAM output register +// and is decoded in ID the next cycle, alongside id_pc. +// ID decode, register read (write-first bypass from WB), immediate. +// EX ALU, branch decision, jump target. Taken branches/jumps (predict not-taken) flush IF/ID +// and ID/EX: a 2-cycle penalty. DMEM is ADDRESSED here (synchronous read). +// MEM load data is back from DMEM; LSU extracts/extends. Stores, console and TOHOST write here. +// WB register write, CSR read, retire (RVFI), halt/fault decision. +// +// Precise faults/halt: a faulting or halting instruction travels to WB like any other. Younger +// instructions are stopped from touching state by gating MEM side effects while an older +// faulting/halting instruction sits in WB, and by freezing the core (`active`) afterwards. Nothing +// younger ever retires. Faulting instructions never retire; the ISS raises on the same ones. +module core_pipe + import tiny_cpu_pkg::*; +( + input logic clk, + input logic rst_n, + + output logic imem_re, + output logic [IMEM_AW-1:0] imem_raddr, + input logic [31:0] imem_rdata, + + output logic [DMEM_AW-1:0] dmem_raddr, + input logic [31:0] dmem_rdata, + output logic [3:0] dmem_wmask, + output logic [DMEM_AW-1:0] dmem_waddr, + output logic [31:0] dmem_wdata, + + output logic rvfi_valid, + output logic [31:0] rvfi_order, + output logic [31:0] rvfi_pc, + output logic [31:0] rvfi_insn, + output logic [4:0] rvfi_rd_addr, + output logic [31:0] rvfi_rd_wdata, + output logic [31:0] rvfi_pc_wdata, + output logic [31:0] rvfi_mem_addr, + output logic [3:0] rvfi_mem_rmask, + output logic [3:0] rvfi_mem_wmask, + output logic [31:0] rvfi_mem_rdata, + output logic [31:0] rvfi_mem_wdata, + output logic rvfi_misaligned, + output logic rvfi_halt, + output logic rvfi_cycle_dep, + + output logic console_valid, + output logic [7:0] console_data, + output logic tohost_valid, + output logic [31:0] tohost_data, + + output logic halted, + output logic fault, + output logic [31:0] dbg_pc, // architectural pc: next instruction to retire + output logic [32*32-1:0] dbg_regs, + output logic [31:0] dbg_cycle, + output logic [31:0] dbg_instret, + + output logic [4:0] dbg_stage_valid, // [0]=IF [1]=ID [2]=EX [3]=MEM [4]=WB + output logic [5*32-1:0] dbg_stage_pc, + output logic [5*32-1:0] dbg_stage_insn, + output logic dbg_flush_ifid, + output logic dbg_flush_idex +); + + // ====================================================================== state + logic [31:0] pc_q; + logic halted_q, fault_q, active; + logic [31:0] arch_pc_q; + + assign active = !halted_q && !fault_q; + + // IF/ID + logic id_valid, id_fetch_fault; + logic [31:0] id_pc; + + // ID/EX + logic ex_valid, ex_illegal, ex_a_pc, ex_b_imm, ex_reg_we, ex_mem_re, ex_mem_we; + logic ex_mem_unsigned, ex_is_branch, ex_is_jal, ex_is_jalr, ex_is_halt; + logic [31:0] ex_pc, ex_insn, ex_rs1_data, ex_rs2_data, ex_imm; + alu_op_t ex_alu_op; + wb_sel_t ex_wb_sel; + mem_size_t ex_mem_size; + + // EX/MEM + logic mm_valid, mm_fault, mm_reg_we, mm_mem_re, mm_mem_we, mm_mem_unsigned, mm_is_halt; + logic [31:0] mm_pc, mm_insn, mm_val, mm_rs2_data, mm_next_pc; + wb_sel_t mm_wb_sel; + mem_size_t mm_mem_size; + + // MEM/WB + logic wb_valid, wb_fault, wb_reg_we, wb_halt, wb_misaligned, wb_cycle_dep; + logic [31:0] wb_pc, wb_insn, wb_val, wb_next_pc; + logic [31:0] wb_mem_addr, wb_mem_rdata, wb_mem_wdata; + logic [3:0] wb_mem_rmask, wb_mem_wmask; + wb_sel_t wb_sel; + + // ====================================================================== IF + logic if_fault; + + assign imem_re = 1'b1; + assign imem_raddr = pc_q[IMEM_AW+1:2]; + assign if_fault = |pc_q[31:IMEM_AW+2]; // pc outside IMEM + + // ====================================================================== ID + logic [31:0] id_insn, id_imm, id_rs1_data, id_rs2_data; + alu_op_t id_alu_op; + imm_sel_t id_imm_sel; + wb_sel_t id_wb_sel; + mem_size_t id_mem_size; + logic id_a_pc, id_b_imm, id_reg_we, id_mem_re, id_mem_we, id_mem_unsigned; + logic id_is_branch, id_is_jal, id_is_jalr, id_is_halt, id_illegal; + /* verilator lint_off UNUSEDSIGNAL */ + logic id_uses_rs1, id_uses_rs2; // consumed by the hazard unit in Phase 4 + /* verilator lint_on UNUSEDSIGNAL */ + + assign id_insn = id_fetch_fault ? 32'b0 : imem_rdata; + + decoder u_decoder ( + .insn(id_insn), .alu_op(id_alu_op), .a_pc(id_a_pc), .b_imm(id_b_imm), .imm_sel(id_imm_sel), + .reg_we(id_reg_we), .wb_sel(id_wb_sel), .mem_re(id_mem_re), .mem_we(id_mem_we), + .mem_size(id_mem_size), .mem_unsigned(id_mem_unsigned), .is_branch(id_is_branch), + .is_jal(id_is_jal), .is_jalr(id_is_jalr), .uses_rs1(id_uses_rs1), .uses_rs2(id_uses_rs2), + .is_halt(id_is_halt), .illegal(id_illegal) + ); + + imm_gen u_imm_gen (.insn(id_insn), .sel(id_imm_sel), .imm(id_imm)); + + // WB-stage write port (declared with WB below) + logic wr_en; + logic [4:0] wb_rd; + logic [31:0] wb_wdata; + + regfile #(.BYPASS(1'b1)) u_regfile ( + .clk(clk), .rst_n(rst_n), .raddr1(id_insn[19:15]), .raddr2(id_insn[24:20]), + .rdata1(id_rs1_data), .rdata2(id_rs2_data), .we(wr_en), .waddr(wb_rd), .wdata(wb_wdata), + .dbg_regs(dbg_regs) + ); + + // ====================================================================== EX + logic [31:0] ex_alu_a, ex_alu_b, ex_alu_y, ex_pc_plus4, ex_pc_target, ex_next_pc, ex_val; + logic ex_br_taken, ex_redirect_raw, ex_target_misaligned, ex_redirect, ex_fault; + + assign ex_alu_a = ex_a_pc ? ex_pc : ex_rs1_data; + assign ex_alu_b = ex_b_imm ? ex_imm : ex_rs2_data; + + alu u_alu (.a(ex_alu_a), .b(ex_alu_b), .op(ex_alu_op), .y(ex_alu_y)); + branch_unit u_branch (.a(ex_rs1_data), .b(ex_rs2_data), .funct3(ex_insn[14:12]), .taken(ex_br_taken)); + + assign ex_pc_plus4 = ex_pc + 32'd4; + assign ex_pc_target = ex_pc + ex_imm; + assign ex_redirect_raw = ex_valid && !ex_illegal && + (ex_is_jal || ex_is_jalr || (ex_is_branch && ex_br_taken)); + always_comb begin + if (ex_is_jalr) ex_next_pc = {ex_alu_y[31:1], 1'b0}; + else if (ex_redirect_raw) ex_next_pc = ex_pc_target; + else ex_next_pc = ex_pc_plus4; + end + assign ex_target_misaligned = ex_redirect_raw && (|ex_next_pc[1:0]); + assign ex_redirect = ex_redirect_raw && !ex_target_misaligned; // taken: flush IF/ID + ID/EX + assign ex_fault = ex_illegal || ex_target_misaligned; + assign ex_val = (ex_wb_sel == WB_PC4) ? ex_pc_plus4 : ex_alu_y; + + // DMEM is addressed in EX; its output register is the load data in MEM. + assign dmem_raddr = ex_alu_y[DMEM_AW+1:2]; + + // ====================================================================== MEM + logic [31:0] lsu_word_addr, lsu_wdata, lsu_rdata_lanes, lsu_load_value, mm_word; + logic [3:0] lsu_lane_mask; + logic lsu_misaligned, hit_dmem, mmio_fault, mmio_console_we, mmio_tohost_we; + logic [31:0] mmio_rdata, mmio_tohost_data, cycle_lo, instret_lo; + logic [7:0] mmio_console_data; + logic mm_fault_total, mem_effects_ok, older_stop, mm_mem_op; + + lsu u_lsu ( + .addr(mm_val), .size(mm_mem_size), .is_unsigned(mm_mem_unsigned), .store_data(mm_rs2_data), + .mem_word(mm_word), .word_addr(lsu_word_addr), .lane_mask(lsu_lane_mask), + .wdata(lsu_wdata), .rdata_lanes(lsu_rdata_lanes), .load_value(lsu_load_value), + .misaligned(lsu_misaligned) + ); + + mmio u_mmio ( + .word_addr(lsu_word_addr), .re(mm_valid && !mm_fault && mm_mem_re), + .we(mm_valid && !mm_fault && mm_mem_we), .wmask(lsu_lane_mask), .wdata(lsu_wdata), + .cycle(cycle_lo), .hit_dmem(hit_dmem), .mmio_rdata(mmio_rdata), + .console_we(mmio_console_we), .console_data(mmio_console_data), + .tohost_we(mmio_tohost_we), .tohost_data(mmio_tohost_data), .fault(mmio_fault) + ); + + assign mm_word = hit_dmem ? dmem_rdata : mmio_rdata; + assign mm_mem_op = mm_mem_re || mm_mem_we; + assign mm_fault_total = mm_valid && (mm_fault || mmio_fault); + + // An older instruction in WB that faults or halts must stop everything younger from having + // any effect (the ISS never executes those instructions). + assign older_stop = wb_valid && (wb_fault || wb_halt); + assign mem_effects_ok = mm_valid && !mm_fault_total && active && !older_stop; + + assign dmem_wmask = (mem_effects_ok && mm_mem_we && hit_dmem) ? lsu_lane_mask : 4'b0000; + assign dmem_waddr = lsu_word_addr[DMEM_AW+1:2]; + assign dmem_wdata = lsu_wdata; + + // ====================================================================== WB + logic [31:0] csr_rdata; + logic retire_now, fault_now, halt_now; + + csr_counters u_csr ( + .clk(clk), .rst_n(rst_n), .retire(retire_now), .csr_addr(wb_insn[31:20]), + .csr_rdata(csr_rdata), .cycle_lo(cycle_lo), .instret_lo(instret_lo) + ); + + assign retire_now = wb_valid && active && !wb_fault; + assign fault_now = wb_valid && active && wb_fault; + assign halt_now = retire_now && wb_halt; + assign wb_rd = wb_insn[11:7]; + assign wb_wdata = (wb_sel == WB_CSR) ? csr_rdata : wb_val; + assign wr_en = retire_now && wb_reg_we && (wb_rd != 5'd0); + + // ====================================================================== pipeline registers + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + pc_q <= IMEM_BASE; + id_valid <= 1'b0; id_pc <= 32'b0; id_fetch_fault <= 1'b0; + + ex_valid <= 1'b0; ex_illegal <= 1'b0; ex_pc <= 32'b0; ex_insn <= 32'b0; + ex_rs1_data <= 32'b0; ex_rs2_data <= 32'b0; ex_imm <= 32'b0; + ex_alu_op <= ALU_ADD; ex_a_pc <= 1'b0; ex_b_imm <= 1'b0; ex_reg_we <= 1'b0; + ex_wb_sel <= WB_ALU; ex_mem_re <= 1'b0; ex_mem_we <= 1'b0; ex_mem_size <= SZ_WORD; + ex_mem_unsigned <= 1'b0; ex_is_branch <= 1'b0; ex_is_jal <= 1'b0; + ex_is_jalr <= 1'b0; ex_is_halt <= 1'b0; + + mm_valid <= 1'b0; mm_fault <= 1'b0; mm_reg_we <= 1'b0; mm_mem_re <= 1'b0; + mm_mem_we <= 1'b0; mm_mem_unsigned <= 1'b0; mm_is_halt <= 1'b0; + mm_pc <= 32'b0; mm_insn <= 32'b0; mm_val <= 32'b0; mm_rs2_data <= 32'b0; + mm_next_pc <= 32'b0; mm_wb_sel <= WB_ALU; mm_mem_size <= SZ_WORD; + + wb_valid <= 1'b0; wb_fault <= 1'b0; wb_reg_we <= 1'b0; wb_halt <= 1'b0; + wb_misaligned <= 1'b0; wb_cycle_dep <= 1'b0; wb_pc <= 32'b0; wb_insn <= 32'b0; + wb_val <= 32'b0; wb_next_pc <= 32'b0; wb_mem_addr <= 32'b0; wb_mem_rdata <= 32'b0; + wb_mem_wdata <= 32'b0; wb_mem_rmask <= 4'b0; wb_mem_wmask <= 4'b0; wb_sel <= WB_ALU; + end else begin + // ---- IF: always fetch; a taken redirect from EX replaces the pc and squashes the + // instruction being fetched + pc_q <= ex_redirect ? ex_next_pc : pc_q + 32'd4; + id_valid <= !ex_redirect; + id_pc <= pc_q; + id_fetch_fault <= if_fault; + + // ---- ID -> EX (squashed by a redirect) + ex_valid <= id_valid && !ex_redirect; + ex_illegal <= id_illegal || id_fetch_fault; + ex_pc <= id_pc; + ex_insn <= id_insn; + ex_rs1_data <= id_rs1_data; + ex_rs2_data <= id_rs2_data; + ex_imm <= id_imm; + ex_alu_op <= id_alu_op; + ex_a_pc <= id_a_pc; + ex_b_imm <= id_b_imm; + ex_reg_we <= id_reg_we; + ex_wb_sel <= id_wb_sel; + ex_mem_re <= id_mem_re; + ex_mem_we <= id_mem_we; + ex_mem_size <= id_mem_size; + ex_mem_unsigned <= id_mem_unsigned; + ex_is_branch <= id_is_branch; + ex_is_jal <= id_is_jal; + ex_is_jalr <= id_is_jalr; + ex_is_halt <= id_is_halt; + + // ---- EX -> MEM + mm_valid <= ex_valid; + mm_fault <= ex_fault; + mm_pc <= ex_pc; + mm_insn <= ex_insn; + mm_val <= ex_val; + mm_rs2_data <= ex_rs2_data; + mm_next_pc <= ex_next_pc; + mm_reg_we <= ex_reg_we; + mm_wb_sel <= ex_wb_sel; + mm_mem_re <= ex_mem_re; + mm_mem_we <= ex_mem_we; + mm_mem_size <= ex_mem_size; + mm_mem_unsigned <= ex_mem_unsigned; + mm_is_halt <= ex_is_halt; + + // ---- MEM -> WB + wb_valid <= mm_valid; + wb_fault <= mm_fault_total; + wb_halt <= mm_is_halt || (mem_effects_ok && mmio_tohost_we); + wb_pc <= mm_pc; + wb_insn <= mm_insn; + wb_val <= mm_mem_re ? lsu_load_value : mm_val; + wb_next_pc <= mm_next_pc; + wb_reg_we <= mm_reg_we; + wb_sel <= mm_wb_sel; + wb_mem_addr <= mm_mem_op ? lsu_word_addr : 32'b0; + wb_mem_rmask <= mm_mem_re ? lsu_lane_mask : 4'b0; + wb_mem_wmask <= mm_mem_we ? lsu_lane_mask : 4'b0; + wb_mem_rdata <= mm_mem_re ? lsu_rdata_lanes : 32'b0; + wb_mem_wdata <= mm_mem_we ? lsu_wdata : 32'b0; + wb_misaligned <= mm_mem_op && lsu_misaligned; + wb_cycle_dep <= (mm_mem_re && lsu_word_addr == CYCLE_ADDR) || + (mm_wb_sel == WB_CSR && (mm_insn[31:20] == 12'hC00 || mm_insn[31:20] == 12'hC01)); + end + end + + // ====================================================================== retire / status + always_ff @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + halted_q <= 1'b0; fault_q <= 1'b0; arch_pc_q <= IMEM_BASE; + rvfi_valid <= 1'b0; rvfi_order <= 32'b0; rvfi_pc <= 32'b0; rvfi_insn <= 32'b0; + rvfi_rd_addr <= 5'b0; rvfi_rd_wdata <= 32'b0; rvfi_pc_wdata <= 32'b0; + rvfi_mem_addr <= 32'b0; rvfi_mem_rmask <= 4'b0; rvfi_mem_wmask <= 4'b0; + rvfi_mem_rdata <= 32'b0; rvfi_mem_wdata <= 32'b0; rvfi_misaligned <= 1'b0; + rvfi_halt <= 1'b0; rvfi_cycle_dep <= 1'b0; + console_valid <= 1'b0; console_data <= 8'b0; tohost_valid <= 1'b0; tohost_data <= 32'b0; + end else begin + if (halt_now) halted_q <= 1'b1; + if (fault_now) fault_q <= 1'b1; + if (retire_now) arch_pc_q <= wb_next_pc; + + rvfi_valid <= retire_now; + rvfi_order <= instret_lo; + rvfi_pc <= wb_pc; + rvfi_insn <= wb_insn; + rvfi_rd_addr <= (wb_reg_we && wb_rd != 5'd0) ? wb_rd : 5'b0; + rvfi_rd_wdata <= (wb_reg_we && wb_rd != 5'd0) ? wb_wdata : 32'b0; + rvfi_pc_wdata <= wb_next_pc; + rvfi_mem_addr <= wb_mem_addr; + rvfi_mem_rmask <= wb_mem_rmask; + rvfi_mem_wmask <= wb_mem_wmask; + rvfi_mem_rdata <= wb_mem_rdata; + rvfi_mem_wdata <= wb_mem_wdata; + rvfi_misaligned <= wb_misaligned; + rvfi_halt <= halt_now; + rvfi_cycle_dep <= wb_cycle_dep; + + // MMIO effects happen in MEM (see mem_effects_ok); the byte shows up on the next cycle + console_valid <= mem_effects_ok && mmio_console_we; + console_data <= mmio_console_data; + if (mem_effects_ok && mmio_tohost_we) begin + tohost_valid <= 1'b1; + tohost_data <= mmio_tohost_data; + end + end + end + + assign halted = halted_q; + assign fault = fault_q; + assign dbg_pc = arch_pc_q; + assign dbg_cycle = cycle_lo; + assign dbg_instret = instret_lo; + + assign dbg_stage_valid = {wb_valid, mm_valid, ex_valid, id_valid, 1'b1}; + // IF has no instruction word yet (synchronous fetch): consumers look it up from the pc. + assign dbg_stage_pc = {wb_pc, mm_pc, ex_pc, id_pc, pc_q}; + assign dbg_stage_insn = {wb_insn, mm_insn, ex_insn, id_insn, 32'b0}; + assign dbg_flush_ifid = ex_redirect; + assign dbg_flush_idex = ex_redirect; + +endmodule diff --git a/rtl/core_single.sv b/rtl/core_single.sv index e2402ee..473f1c5 100644 --- a/rtl/core_single.sv +++ b/rtl/core_single.sv @@ -56,7 +56,10 @@ module core_single output logic [31:0] dbg_pc, output logic [32*32-1:0] dbg_regs, output logic [31:0] dbg_cycle, - output logic [31:0] dbg_instret + output logic [31:0] dbg_instret, + output logic [4:0] dbg_stage_valid, // single-cycle: only [0] (IF) is ever set + output logic [5*32-1:0] dbg_stage_pc, + output logic [5*32-1:0] dbg_stage_insn ); // ------------------------------------------------------------------ state @@ -254,4 +257,8 @@ module core_single assign dbg_cycle = cycle_lo; assign dbg_instret = instret_lo; + assign dbg_stage_valid = {4'b0, active}; + assign dbg_stage_pc = {128'b0, pc_q}; + assign dbg_stage_insn = {128'b0, insn}; + endmodule diff --git a/rtl/mem_sync.sv b/rtl/mem_sync.sv new file mode 100644 index 0000000..39e93ae --- /dev/null +++ b/rtl/mem_sync.sv @@ -0,0 +1,53 @@ +// Memory with a SYNCHRONOUS read port (the read result appears one clock after the address) - +// the shape FPGA block RAM has, used by the pipelined core. +// +// * `re` low holds the read register (used to stall fetch in Phase 4). +// * READ_NEW=1: if the same clock edge also writes the address being read, the read returns the +// NEW bytes (write-first). The pipeline needs this: a load reads DMEM in EX while the +// store ahead of it writes in MEM, i.e. at the same edge, and must see the stored data. +// READ_NEW=0 gives plain read-before-write behaviour. +// * Two write paths, as in mem_comb: the core's byte-masked write and a word-wide loader port. +module mem_sync #( + parameter int unsigned WORDS = 1024, + parameter bit READ_NEW = 1'b1, + localparam int unsigned AW = $clog2(WORDS) +) ( + input logic clk, + input logic re, + input logic [AW-1:0] raddr, + output logic [31:0] rdata, + input logic [3:0] wmask, + input logic [AW-1:0] waddr, + input logic [31:0] wdata, + input logic ld_en, + input logic [AW-1:0] ld_addr, + input logic [31:0] ld_data +); + + logic [31:0] mem [WORDS]; + + initial begin + for (int i = 0; i < WORDS; i++) mem[i] = 32'b0; + rdata = 32'b0; + end + + always_ff @(posedge clk) begin + if (ld_en) begin + mem[ld_addr] <= ld_data; + end else begin + for (int b = 0; b < 4; b++) begin + if (wmask[b]) mem[waddr][8*b +: 8] <= wdata[8*b +: 8]; + end + end + + if (re) begin + rdata <= mem[raddr]; // old contents; the merge below overrides written bytes + if (READ_NEW && !ld_en && waddr == raddr) begin + for (int b = 0; b < 4; b++) begin + if (wmask[b]) rdata[8*b +: 8] <= wdata[8*b +: 8]; + end + end + end + end + +endmodule diff --git a/rtl/tiny_cpu_top.sv b/rtl/tiny_cpu_top.sv index 3ee0db9..4426a3f 100644 --- a/rtl/tiny_cpu_top.sv +++ b/rtl/tiny_cpu_top.sv @@ -1,7 +1,7 @@ // Top level: a core plus its memories, with the program loader and the debug/retire bus. // -// `CORE` selects the microarchitecture ("SINGLE" now; "PIPE" arrives in Phase 3). The port list is -// identical for both so the testbenches and the WASM harness never change. +// `CORE` selects the microarchitecture: "SINGLE" (reference, CPI 1) or "PIPE" (5-stage). The port list +// is identical for both so the testbenches and the WASM harness never change. // // Loading a program: hold rst_n low, then for each word pulse load_en with load_sel (0 = IMEM, // 1 = DMEM), load_addr (word index) and load_data. Release rst_n to run. @@ -46,7 +46,20 @@ module tiny_cpu_top output logic [31:0] dbg_pc, output logic [32*32-1:0] dbg_regs, output logic [31:0] dbg_cycle, - output logic [31:0] dbg_instret + output logic [31:0] dbg_instret, + + // Per-stage view for the visualizer: stage index 0=IF 1=ID 2=EX 3=MEM 4=WB. "SINGLE" only + // ever populates IF. Hazard flags: Phase 3 has flushes only; stall/load-use/forwarding arrive + // in Phase 4 and read 0 until then. + output logic [4:0] dbg_stage_valid, + output logic [5*32-1:0] dbg_stage_pc, + output logic [5*32-1:0] dbg_stage_insn, + output logic dbg_flush_ifid, + output logic dbg_flush_idex, + output logic dbg_stall, + output logic dbg_load_use, + output logic [1:0] dbg_fwd_a, + output logic [1:0] dbg_fwd_b ); if (CORE == "SINGLE") begin : g_single @@ -64,9 +77,13 @@ module tiny_cpu_top .rvfi_pc_wdata, .rvfi_mem_addr, .rvfi_mem_rmask, .rvfi_mem_wmask, .rvfi_mem_rdata, .rvfi_mem_wdata, .rvfi_misaligned, .rvfi_halt, .rvfi_cycle_dep, .console_valid, .console_data, .tohost_valid, .tohost_data, - .halted, .fault, .dbg_pc, .dbg_regs, .dbg_cycle, .dbg_instret + .halted, .fault, .dbg_pc, .dbg_regs, .dbg_cycle, .dbg_instret, + .dbg_stage_valid, .dbg_stage_pc, .dbg_stage_insn ); + assign dbg_flush_ifid = 1'b0; + assign dbg_flush_idex = 1'b0; + mem_comb #(.WORDS(IMEM_WORDS)) u_imem ( .clk, .raddr(imem_raddr), .rdata(imem_rdata), .wmask(4'b0000), .waddr('0), .wdata(32'b0), // IMEM is read-only to the core @@ -78,8 +95,46 @@ module tiny_cpu_top .wmask(dmem_wmask), .waddr(dmem_waddr), .wdata(dmem_wdata), .ld_en(load_en && load_sel), .ld_addr(load_addr[DMEM_AW-1:0]), .ld_data(load_data) ); + end else if (CORE == "PIPE") begin : g_pipe + logic imem_re; + logic [IMEM_AW-1:0] imem_raddr; + logic [31:0] imem_rdata; + logic [DMEM_AW-1:0] dmem_raddr, dmem_waddr; + logic [31:0] dmem_rdata, dmem_wdata; + logic [3:0] dmem_wmask; + + core_pipe u_core ( + .clk, .rst_n, + .imem_re, .imem_raddr, .imem_rdata, + .dmem_raddr, .dmem_rdata, .dmem_wmask, .dmem_waddr, .dmem_wdata, + .rvfi_valid, .rvfi_order, .rvfi_pc, .rvfi_insn, .rvfi_rd_addr, .rvfi_rd_wdata, + .rvfi_pc_wdata, .rvfi_mem_addr, .rvfi_mem_rmask, .rvfi_mem_wmask, .rvfi_mem_rdata, + .rvfi_mem_wdata, .rvfi_misaligned, .rvfi_halt, .rvfi_cycle_dep, + .console_valid, .console_data, .tohost_valid, .tohost_data, + .halted, .fault, .dbg_pc, .dbg_regs, .dbg_cycle, .dbg_instret, + .dbg_stage_valid, .dbg_stage_pc, .dbg_stage_insn, .dbg_flush_ifid, .dbg_flush_idex + ); + + // IMEM is read-only to the core. DMEM read-new: a load in EX sees the store ahead of it in MEM. + mem_sync #(.WORDS(IMEM_WORDS), .READ_NEW(1'b0)) u_imem ( + .clk, .re(imem_re), .raddr(imem_raddr), .rdata(imem_rdata), + .wmask(4'b0000), .waddr('0), .wdata(32'b0), + .ld_en(load_en && !load_sel), .ld_addr(load_addr[IMEM_AW-1:0]), .ld_data(load_data) + ); + + mem_sync #(.WORDS(DMEM_WORDS), .READ_NEW(1'b1)) u_dmem ( + .clk, .re(1'b1), .raddr(dmem_raddr), .rdata(dmem_rdata), + .wmask(dmem_wmask), .waddr(dmem_waddr), .wdata(dmem_wdata), + .ld_en(load_en && load_sel), .ld_addr(load_addr[DMEM_AW-1:0]), .ld_data(load_data) + ); end else begin : g_unsupported - $error("tiny_cpu_top: CORE=\"%s\" is not implemented yet (only \"SINGLE\")", CORE); + $error("tiny_cpu_top: unknown CORE=\"%s\" (use \"SINGLE\" or \"PIPE\")", CORE); end + // Phase 4 drives these; until then no core stalls, forwards, or detects load-use. + assign dbg_stall = 1'b0; + assign dbg_load_use = 1'b0; + assign dbg_fwd_a = 2'b00; + assign dbg_fwd_b = 2'b00; + endmodule diff --git a/scripts/lint.sh b/scripts/lint.sh index 7ed1176..8f269de 100644 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,11 +5,11 @@ set -uo pipefail cd "$(dirname "$0")/.." PKG=rtl/tiny_cpu_pkg.sv -LEAVES="alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb" -ALL="$PKG"; for m in $LEAVES core_single tiny_cpu_top; do ALL+=" rtl/$m.sv"; done +LEAVES="alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb mem_sync" +ALL="$PKG"; for m in $LEAVES core_single core_pipe tiny_cpu_top; do ALL+=" rtl/$m.sv"; done status=0 -lint() { # lint +lint() { # lint [-G...] local top=$1; shift if ! out=$(verilator --lint-only -Wall --top-module "$top" "$@" 2>&1); then echo "LINT FAILED: $top"; echo "$out"; status=1 @@ -22,6 +22,8 @@ lint() { # lint for m in $LEAVES; do lint "$m" "$PKG" "rtl/$m.sv"; done lint core_single $ALL +lint core_pipe $ALL lint tiny_cpu_top $ALL +lint tiny_cpu_top '-GCORE="PIPE"' $ALL # the PIPE generate branch is not elaborated by default lint counter rtl/spike/counter.sv exit $status diff --git a/sim/golden/hazard_free.py b/sim/golden/hazard_free.py new file mode 100644 index 0000000..3438ed0 --- /dev/null +++ b/sim/golden/hazard_free.py @@ -0,0 +1,302 @@ +"""Programs the Phase 3 pipeline (no forwarding, no load-use stall) can run correctly. + +Every program is built with Prog(pad=2) except `mem_raw_adjacent`, which is hand-scheduled. They are +checked twice: on the ISS alone (test_progbuild.py: result + no dynamic RAW closer than 3), and on +the RTL in lockstep with the ISS (sim/test_pipe_lockstep.py). Once Phase 4 adds forwarding the same +programs keep working (a faster core still runs hazard-free code correctly). +""" + +from __future__ import annotations + +import random + +from golden.decode import BY_NAME +from golden.iss import CONSOLE_ADDR, CYCLE_ADDR, DMEM_BASE +from golden.progbuild import Prog + +R_OPS = ["add", "sub", "sll", "slt", "sltu", "xor", "srl", "sra", "or", "and"] +I_OPS = ["addi", "slti", "sltiu", "xori", "ori", "andi"] +SH_OPS = ["slli", "srli", "srai"] +BRANCH_OPS = ["beq", "bne", "blt", "bge", "bltu", "bgeu"] +VALUES = [0, 1, 0xFFFFFFFF, 0x7FFFFFFF, 0x80000000, 0x12345678, 0xDEADBEEF, 0x000000FF, 0xFFFF8001] + + +def alu_zoo() -> Prog: + """Every ALU operation on awkward operands; each result is stored so it is also seen as write data.""" + p = Prog() + out = p.space(4 * 64) + p.li(1, 0x80000001) + p.li(2, 0xFFFFFFF3) + p.li(3, out) + k = 0 + for op in R_OPS: + p.op(op, 4, 1, 2) + p.sw(4, 3, 4 * k) + k += 1 + for op in I_OPS: + for imm in (-1, 0x7FF, -2048): + p.op(op, 5, 1, imm) + p.sw(5, 3, 4 * k) + k += 1 + for op in SH_OPS: + for sh in (0, 1, 31): + p.op(op, 6, 2, sh) + p.sw(6, 3, 4 * k) + k += 1 + p.lui(7, 0xFFFFF) + p.auipc(8, 0x1) + p.sw(7, 3, 4 * k) + p.sw(8, 3, 4 * k + 4) + return p.exit() + + +def mem_zoo() -> Prog: + """Every load/store size at every offset, including misaligned ones that get forced into alignment.""" + p = Prog() + buf = p.word(0x80FF7F01, 0xAABBCCDD, 0x00000000, 0xFFFFFFFF) + p.li(1, buf) + for i, op in enumerate(["lb", "lbu", "lh", "lhu", "lw"]): + for off in range(8): + p.op(op, 2, 1, off) + p.sw(2, 1, 32 + 4 * (i * 8 + off) % 64) + p.li(3, 0x11223344) + for off in range(8): + p.sb(3, 1, 16 + off) + p.lw(4, 1, 16) + p.lw(5, 1, 20) + for off in (0, 2, 5): + p.sh(3, 1, 24 + off) + p.lw(6, 1, 24) + p.lw(7, 1, 28) + p.sw(3, 1, 9) # misaligned word store -> forced to offset 8 + p.lw(8, 1, 8) + return p.exit() + + +def mem_raw_adjacent() -> Prog: + """Hand-scheduled (pad=0): a load directly after a store to the same word. The store writes DMEM + in MEM at the same edge the load reads it in EX, so this only works because mem_sync is write-first.""" + p = Prog(pad=0) + buf = p.space(64) + p.li(1, buf) + p.li(2, 0xA1B2C3D4) + p.li(9, 0x5A) + p.nop(3) + p.sw(2, 1, 0).lw(3, 1, 0).nop(3) # word store, word load + p.sb(2, 1, 5).lw(4, 1, 4).nop(3) # byte store merged into a later word read + p.sh(2, 1, 10).lhu(5, 1, 10).nop(3) + p.sw(2, 1, 16).sw(9, 1, 20).lw(6, 1, 16).lw(7, 1, 20).nop(3) # back-to-back stores and loads + p.sb(9, 1, 33).sb(9, 1, 34).lw(8, 1, 32).nop(3) # two byte stores then a word load + p.sw(3, 1, 40).nop(3) # consume an earlier load result + p.sw(8, 1, 44).nop(3) + p.exit(1) + return p + + +def branches() -> Prog: + """All six conditions, taken and not taken, forward and backward; x10 counts the taken paths.""" + p = Prog() + p.li(1, 5) + p.li(2, 0xFFFFFFFD) # -3 signed, huge unsigned + p.li(3, 5) + p.li(10, 0) + n = 0 + for op in BRANCH_OPS: + for a, b in ((1, 3), (1, 2), (2, 1)): + p.op(op, a, b, f"t{n}") + p.addi(10, 10, 100) # executes only when NOT taken + p.label(f"t{n}") + p.addi(10, 10, 1) + n += 1 + p.li(4, 7) # backward loop: sum 7+6+...+1 + p.li(5, 0) + p.label("loop") + p.add(5, 5, 4) + p.addi(4, 4, -1) + p.bne(4, 0, "loop") + p.li(6, DMEM_BASE) + p.sw(5, 6, 0) + p.sw(10, 6, 4) + return p.exit() + + +def calls() -> Prog: + """jal/jalr: nested calls, return via ra, a computed jump, and jalr clearing bit 0 of its target.""" + p = Prog() + out = p.space(32) + p.li(20, out) + p.li(5, 3) + p.jal(1, "double") + p.sw(5, 20, 0) + p.jal(1, "double") + p.jal(1, "double") + p.sw(5, 20, 4) + p.li_label(6, "far") + p.jalr(2, 6, 0) # indirect jump with link + p.addi(7, 0, 111) # skipped + p.label("far") + p.nop(2) # the jump's padding was flushed, so the link register (x2) is only 1 instruction old here + p.sw(2, 20, 8) + p.li_label(6, "odd", 1) # odd target: bit 0 must be ignored + p.jalr(0, 6, 0) + p.addi(7, 0, 222) # skipped + p.label("odd") + p.sw(7, 20, 12) + p.jal(0, "end") # unconditional forward jump with no link + p.addi(7, 0, 333) # skipped + p.label("double") # x5 *= 2 then return; nested call through x3 + p.add(5, 5, 5) + p.jalr(0, 1, 0) + p.label("end") + return p.exit() + + +def console_and_csr() -> Prog: + """MMIO: print text through CONSOLE, read instret/cycle CSRs and the CYCLE register.""" + p = Prog() + out = p.space(16) + p.li(20, out) + for ch in "Hi, pipe!\n": + p.putc(ch) + p.csrrs(21, 0xC02) # instret: exact, unmasked in lockstep + p.sw(21, 20, 0) + p.csrrs(22, 0xC00) # cycle: timing-dependent, masked + p.csrrs(23, 0xC01) + p.csrrs(24, 0xC82) # instreth: zero + p.sw(24, 20, 4) + p.li(25, CYCLE_ADDR) + p.lw(26, 25, 0) + p.fence() + p.putc("!") + return p.exit() + + +def fib() -> Prog: + p = Prog() + out = p.space(4) + p.li(5, 20) + p.li(6, 0) + p.li(7, 1) + p.label("loop") + p.beq(5, 0, "end") + p.add(28, 6, 7) + p.addi(6, 7, 0) + p.addi(7, 28, 0) + p.addi(5, 5, -1) + p.jal(0, "loop") + p.label("end") + p.li(8, out) + p.sw(6, 8, 0) # fib(20) = 6765 + return p.exit() + + +def bubble_sort() -> Prog: + p = Prog() + base = p.word(42, 7, 99, 3, 18, 64, 1, 27) + n = 8 + p.li(8, base) + p.li(18, n - 1) + p.label("outer") + p.bge(0, 18, "done") + p.li(5, 0) + p.addi(6, 8, 0) + p.label("inner") + p.bge(5, 18, "next") + p.lw(11, 6, 0) + p.lw(12, 6, 4) + p.bge(12, 11, "noswap") + p.sw(12, 6, 0) + p.sw(11, 6, 4) + p.label("noswap") + p.addi(5, 5, 1) + p.addi(6, 6, 4) + p.jal(0, "inner") + p.label("next") + p.addi(18, 18, -1) + p.jal(0, "outer") + p.label("done") + return p.exit() + + +def memcpy_sum() -> Prog: + p = Prog() + src = p.word(*[(i * 0x01010101) ^ 0xA5A5A5A5 for i in range(12)]) + dst = p.space(48) + p.li(1, dst) + p.li(2, src) + p.li(3, 48) # byte copy loop + p.label("copy") + p.beq(3, 0, "sum") + p.lbu(4, 2, 0) + p.sb(4, 1, 0) + p.addi(1, 1, 1) + p.addi(2, 2, 1) + p.addi(3, 3, -1) + p.jal(0, "copy") + p.label("sum") + p.li(1, dst) + p.li(3, 12) + p.li(9, 0) + p.label("acc") + p.beq(3, 0, "fin") + p.lw(4, 1, 0) + p.add(9, 9, 4) + p.addi(1, 1, 4) + p.addi(3, 3, -1) + p.jal(0, "acc") + p.label("fin") + p.li(1, dst) + p.sw(9, 1, 0) + return p.exit() + + +def random_program(seed: int, n: int = 60) -> Prog: + """Random straight-line-ish code with forward-only branches (always terminates), padded hazard-free. + x1 holds a DMEM base that is never overwritten; x2..x15 are scratch.""" + rng = random.Random(seed) + p = Prog() + buf = p.space(256) + p.li(1, buf) + for r in range(2, 16): + p.li(r, rng.choice(VALUES) ^ rng.getrandbits(8)) + labels_open: list[tuple[int, str]] = [] + for i in range(n): + for pending in [x for x in labels_open if x[0] <= i]: + p.label(pending[1]) + labels_open.remove(pending) + rd, a, b = (rng.randrange(2, 16) for _ in range(3)) + kind = rng.choices(["r", "i", "sh", "lui", "load", "store", "branch"], [5, 4, 2, 1, 3, 3, 2])[0] + if kind == "r": + p.op(rng.choice(R_OPS), rd, a, b) + elif kind == "i": + p.op(rng.choice(I_OPS), rd, a, rng.randint(-2048, 2047)) + elif kind == "sh": + p.op(rng.choice(SH_OPS), rd, a, rng.randrange(32)) + elif kind == "lui": + p.op(rng.choice(["lui", "auipc"]), rd, rng.getrandbits(20)) + elif kind == "load": + p.op(rng.choice(["lb", "lbu", "lh", "lhu", "lw"]), rd, 1, rng.randrange(0, 250)) + elif kind == "store": + p.op(rng.choice(["sb", "sh", "sw"]), a, 1, rng.randrange(0, 250)) + else: + name = f"L{i}" + labels_open.append((i + rng.randint(2, 4), name)) # skip >= 2 slots: never a +4 branch + p.op(rng.choice(BRANCH_OPS), a, b, name) + for _, name in labels_open: + p.label(name) + return p.exit() + + +PROGRAMS = { + "alu_zoo": alu_zoo, + "mem_zoo": mem_zoo, + "mem_raw_adjacent": mem_raw_adjacent, + "branches": branches, + "calls": calls, + "console_and_csr": console_and_csr, + "fib": fib, + "bubble_sort": bubble_sort, + "memcpy_sum": memcpy_sum, +} + +assert all(n in BY_NAME for n in R_OPS + I_OPS + SH_OPS + BRANCH_OPS) diff --git a/sim/golden/progbuild.py b/sim/golden/progbuild.py new file mode 100644 index 0000000..60cf7f1 --- /dev/null +++ b/sim/golden/progbuild.py @@ -0,0 +1,206 @@ +"""A tiny programmatic assembler for building test programs without a RISC-V toolchain. + + p = Prog(pad=2) # pad: NOPs inserted after every instruction + p.li(1, 0x12345678) + p.label("loop"); p.addi(1, 1, -1); p.bne(1, 0, "loop") + p.exit() + imem, dmem = p.build() # raw little-endian images, as sw/Makefile produces + +With pad=2 any producer is >= 3 instructions ahead of its consumer, the distance the Phase 3 +pipeline (no forwarding, write-first regfile) needs. pad=0 gives dense code for hand-scheduled +sequences (use p.nop(n) between dependent instructions) and for the Phase 4 hazard tests. + +`raw_hazards()` checks a *dynamic* retire trace, so it holds for taken branches and loops too. +""" + +from __future__ import annotations + +from golden.decode import BY_NAME, M32, decode, encode +from golden.iss import CONSOLE_ADDR, DMEM_BASE, IMEM_SIZE, TOHOST_ADDR + +NOP = encode("addi", 0, 0, imm=0) + + +def _lo12(value: int) -> int: + """Signed low 12 bits, so that lui(value - lo) + addi(lo) reconstructs value.""" + return ((value & 0xFFF) ^ 0x800) - 0x800 + + +class Prog: + def __init__(self, pad: int = 2): + self.pad = pad + self._items: list[tuple] = [] + self._data = bytearray() + + # ------------------------------------------------------------ emission + def _emit(self, item: tuple) -> "Prog": + self._items.append(item) + for _ in range(self.pad): + self._items.append(("raw", NOP)) + return self + + def label(self, name: str) -> "Prog": + self._items.append(("label", name)) + return self + + def nop(self, n: int = 1) -> "Prog": + """Explicit NOPs, never padded further.""" + self._items.extend([("raw", NOP)] * n) + return self + + def raw(self, word: int) -> "Prog": + return self._emit(("raw", word & M32)) + + def op(self, name: str, *args) -> "Prog": + """Emit by mnemonic. Operand order follows assembly: + R (rd, rs1, rs2) I/SH/load/jalr (rd, rs1, imm) S (rs2, rs1, imm) B (rs1, rs2, label) + U (rd, imm20) J (rd, label) CSR (rd, csr) SYS/FENCE ()""" + fmt = BY_NAME[name].fmt + if fmt == "R": + rd, rs1, rs2 = args + kw = dict(rd=rd, rs1=rs1, rs2=rs2) + elif fmt in ("I", "SH"): + rd, rs1, imm = args + kw = dict(rd=rd, rs1=rs1, imm=imm) + elif fmt == "S": + rs2, rs1, imm = args + kw = dict(rs2=rs2, rs1=rs1, imm=imm) + elif fmt == "B": + rs1, rs2, target = args + return self._emit(("branch", name, dict(rs1=rs1, rs2=rs2), target)) + elif fmt == "U": + rd, imm20 = args + kw = dict(rd=rd, imm=(imm20 << 12) & M32) + elif fmt == "J": + rd, target = args + return self._emit(("branch", name, dict(rd=rd), target)) + elif fmt == "CSR": + rd, csr = args + kw = dict(rd=rd, rs1=0, csr=csr) + else: + kw = {} + return self._emit(("insn", name, kw)) + + def __getattr__(self, name: str): + if name.startswith("_") or name not in BY_NAME: + raise AttributeError(name) + return lambda *args: self.op(name, *args) + + def _gap(self) -> "Prog": + """NOPs that make a dependent pair of emitted instructions >= 3 apart at any pad.""" + return self.nop(max(0, 2 - self.pad)) + + def li(self, rd: int, value: int) -> "Prog": + value &= M32 + lo = _lo12(value) + self.op("lui", rd, ((value - lo) & M32) >> 12) + self._gap() + return self.op("addi", rd, rd, lo) + + def li_label(self, rd: int, label: str, add: int = 0) -> "Prog": + """rd = address of `label` + add (two instructions, resolved at build time).""" + self._emit(("lui_label", rd, label, add)) + self._gap() + return self._emit(("addi_label", rd, label, add)) + + def exit(self, code: int = 1) -> "Prog": + """Store `code` to TOHOST (1 = PASS). Uses x30/x31.""" + self.li(31, TOHOST_ADDR) + self.li(30, code) + self._gap() + return self.sw(30, 31, 0) + + def putc(self, ch: str | int, scratch: int = 29) -> "Prog": + """Print one character through CONSOLE. Uses x28/x29 by default.""" + self.li(28, CONSOLE_ADDR) + self.li(scratch, ord(ch) if isinstance(ch, str) else ch) + self._gap() + return self.sb(scratch, 28, 0) + + # ------------------------------------------------------------ data (DMEM image) + def align(self, n: int = 4) -> None: + while len(self._data) % n: + self._data.append(0) + + def word(self, *values: int) -> int: + """Append 32-bit words; returns the DMEM address of the first one.""" + self.align(4) + addr = DMEM_BASE + len(self._data) + for v in values: + self._data += (v & M32).to_bytes(4, "little") + return addr + + def bytes_(self, data: bytes) -> int: + addr = DMEM_BASE + len(self._data) + self._data += data + return addr + + def space(self, n: int) -> int: + self.align(4) + addr = DMEM_BASE + len(self._data) + self._data += bytes(n) + return addr + + # ------------------------------------------------------------ build + def build(self) -> tuple[bytes, bytes]: + labels: dict[str, int] = {} + placed: list[tuple[int, tuple]] = [] + addr = 0 + for item in self._items: + if item[0] == "label": + if item[1] in labels: + raise ValueError(f"duplicate label {item[1]}") + labels[item[1]] = addr + else: + placed.append((addr, item)) + addr += 4 + if addr > IMEM_SIZE: + raise ValueError(f"program is {addr} bytes, IMEM holds {IMEM_SIZE}") + + words = [] + for pc, item in placed: + kind = item[0] + if kind == "raw": + words.append(item[1]) + elif kind == "insn": + words.append(encode(item[1], **item[2])) + elif kind == "branch": + _, name, kw, target = item + words.append(encode(name, imm=labels[target] - pc, **kw)) + else: # lui_label / addi_label + _, rd, label, add = item + value = (labels[label] + add) & M32 + lo = _lo12(value) + if kind == "lui_label": + words.append(encode("lui", rd, imm=(value - lo) & M32)) + else: + words.append(encode("addi", rd, rd, imm=lo)) + return b"".join(w.to_bytes(4, "little") for w in words), bytes(self._data) + + +def reads(insn: int) -> tuple[int, ...]: + """Source registers an instruction actually reads (x0 excluded).""" + x = decode(insn) + if x is None: + return () + if x.fmt in ("R", "S", "B"): + regs = (x.rs1, x.rs2) + elif x.fmt in ("I", "SH"): + regs = (x.rs1,) + else: + regs = () + return tuple(r for r in regs if r) + + +def raw_hazards(trace, min_distance: int = 3) -> list[tuple[int, int, int]]: + """Read-after-write pairs in a dynamic retire trace closer than `min_distance` instructions. + Returns (producer index, consumer index, register).""" + last_write: dict[int, int] = {} + bad = [] + for i, r in enumerate(trace): + for reg in reads(r.insn): + if reg in last_write and i - last_write[reg] < min_distance: + bad.append((last_write[reg], i, reg)) + if r.rd_addr: + last_write[r.rd_addr] = i + return bad diff --git a/sim/golden/test_progbuild.py b/sim/golden/test_progbuild.py new file mode 100644 index 0000000..4b8f261 --- /dev/null +++ b/sim/golden/test_progbuild.py @@ -0,0 +1,119 @@ +"""Program builder + hazard-free program set, checked on the ISS alone (no RTL, no toolchain).""" + +import struct + +import pytest + +from golden.decode import encode as e +from golden.hazard_free import PROGRAMS, random_program +from golden.iss import CONSOLE_ADDR, DMEM_BASE, Iss +from golden.progbuild import NOP, Prog, raw_hazards, reads + + +def run(prog): + imem, dmem = prog.build() + iss = Iss(imem, dmem) + trace = iss.run(200_000) + return iss, trace + + +def test_pad_inserts_nops_after_every_instruction_and_labels_resolve(): + p = Prog(pad=2) + p.label("top").addi(1, 0, 1).beq(0, 0, "top") + imem, _ = p.build() + words = [int.from_bytes(imem[i : i + 4], "little") for i in range(0, len(imem), 4)] + assert words[0] == e("addi", 1, 0, imm=1) and words[1:3] == [NOP, NOP] + assert words[3] == e("beq", 0, 0, 0, imm=-12) and words[4:] == [NOP, NOP] + + +def test_li_and_li_label_reconstruct_any_value(): + for v in (0, 1, 0x7FF, 0x800, 0xFFF, 0x1000, 0x80000000, 0x7FFFFFFF, 0xFFFFF800, 0xDEADBEEF, 0xFFFFFFFF): + p = Prog(pad=0) + p.li(5, v) + imem = p.build()[0] + iss = Iss(imem) + while iss.pc < len(imem): # li may include gap NOPs at pad=0 + iss.step() + assert iss.regs[5] == v, hex(v) + p = Prog(pad=0) + p.nop(3).label("here").li_label(6, "here", 1) + imem = p.build()[0] + iss = Iss(imem) + while iss.pc < len(imem): + iss.step() + assert iss.regs[6] == 13 + + +def test_data_section_layout(): + p = Prog() + a = p.word(1, 2) + b = p.bytes_(b"xyz") + c = p.word(7) # re-aligned + assert (a, b, c) == (DMEM_BASE, DMEM_BASE + 8, DMEM_BASE + 12) + assert p.build()[1] == struct.pack("= s32(b), + "bltu": lambda a, b: a < b, "bgeu": lambda a, b: a >= b} + want = 0 + for op in cond: + for a, b in ((1, 3), (1, 2), (2, 1)): + want += 1 + (0 if cond[op](vals[a], vals[b]) else 100) + iss, _ = run(PROGRAMS["branches"]()) + loop_sum, count = struct.unpack(" RTL sets +`fault`, with no retire), a frozen core after halt/fault, MMIO side effects, final registers, and the +cycle count for the core under test (TINYCPU_CORE = SINGLE | PIPE). +""" + +import json +import os +from pathlib import Path + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge, Timer + +from golden.decode import BRANCHES, decode +from golden.decode import encode as e # noqa: F401 (re-exported for tests) +from golden.iss import IMEM_SIZE, DMEM_SIZE, Iss, IssError + +CORE = os.environ.get("TINYCPU_CORE", "SINGLE") +PIPE_FILL = 4 # cycles from reset release to the first retire is 5; N instructions take N + 4 +FLUSH_PENALTY = 2 # a taken branch/jump squashes IF and ID + +ROOT = Path(__file__).resolve().parent.parent +BUILD = ROOT / "sw" / "build" +EXPECTED = json.loads((ROOT / "sim" / "programs" / "expected.json").read_text()) +MAX_CYCLES = 200_000 + +FIELDS = [ # (RTL signal, ISS Retire attribute, cycle-dependent?) + ("rvfi_order", "order", False), ("rvfi_pc", "pc", False), ("rvfi_insn", "insn", False), + ("rvfi_rd_addr", "rd_addr", False), ("rvfi_rd_wdata", "rd_wdata", True), + ("rvfi_pc_wdata", "pc_wdata", False), ("rvfi_mem_addr", "mem_addr", False), + ("rvfi_mem_rmask", "mem_rmask", False), ("rvfi_mem_wmask", "mem_wmask", False), + ("rvfi_mem_rdata", "mem_rdata", True), ("rvfi_mem_wdata", "mem_wdata", False), + ("rvfi_misaligned", "misaligned", False), ("rvfi_halt", "halt", False), +] + + +def images(): + out = [] + for group in ("riscv-tests", "programs"): + out += sorted(p.name.removesuffix(".imem.bin") for p in (BUILD / group).glob("*.imem.bin")) + return out + + +def stem_path(name): + group = "riscv-tests" if name.startswith("rv32ui") else "programs" + return BUILD / group / name + + +def words(data: bytes, count: int) -> list[int]: + data = bytes(data).ljust(count * 4, b"\0") + return [int.from_bytes(data[4 * i : 4 * i + 4], "little") for i in range(count)] + + +def prog_bytes(*ws) -> bytes: + return b"".join((w & 0xFFFFFFFF).to_bytes(4, "little") for w in ws) + + +_clock = None + + +def ensure_clock(dut): + """One clock driver per test (a test may call lockstep() many times; finished tasks are dead).""" + global _clock + if _clock is None or _clock.done(): + _clock = cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) + + +async def reset_and_load(dut, imem: bytes, dmem: bytes): + ensure_clock(dut) + await Timer(1, "ns") # a previous lockstep() ends in the ReadOnly phase, where writes are illegal + dut.rst_n.value, dut.load_en.value = 0, 0 + dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 0, 0, 0 + await FallingEdge(dut.clk) + for sel, data, count in ((0, imem, IMEM_SIZE // 4), (1, dmem, DMEM_SIZE // 4)): + for i, w in enumerate(words(data, count)): # whole memory: wipes the previous program + dut.load_en.value, dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 1, sel, i, w + await FallingEdge(dut.clk) + dut.load_en.value = 0 + await FallingEdge(dut.clk) + dut.rst_n.value = 1 # first rising edge after this executes the instruction at pc 0 + + +def redirects(history) -> int: + """Taken branches and jumps in a retire history (each flushes the pipeline once).""" + n = 0 + for r in history: + x = decode(r.insn) + if x.name in ("jal", "jalr") or (x.name in BRANCHES and r.pc_wdata != (r.pc + 4) & 0xFFFFFFFF): + n += 1 + return n + + +def expected_cycles(instret: int, history) -> int: + """Cycles from reset release until the halting retire is visible. SINGLE: one per instruction. + PIPE (Phase 3, no stalls): pipeline fill + one per instruction + a flush penalty per redirect.""" + if CORE == "SINGLE": + return instret + return instret + PIPE_FILL + FLUSH_PENALTY * redirects(history) + + +def explain(history, msg): + tail = "\n".join(f" {r}" for r in history[-8:]) + return f"{msg}\n last ISS retires:\n{tail}" + + +async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | None = None): + """Run RTL and ISS together to halt (or fault). Returns (iss, rtl_console, cycles).""" + await reset_and_load(dut, imem, dmem) + iss = Iss(imem, dmem) + history, console, cycles, faulted = [], bytearray(), 0, False + + while True: + await RisingEdge(dut.clk) + await ReadOnly() + cycles += 1 + assert cycles <= MAX_CYCLES, explain(history, f"no halt after {MAX_CYCLES} cycles") + if int(dut.console_valid.value): + console.append(int(dut.console_data.value)) + + if int(dut.fault.value): # RTL refused an instruction: the ISS must raise on the same one + assert not int(dut.rvfi_valid.value), "RTL retired and faulted in the same cycle" + try: + iss.step() + except IssError: + faulted = True + break + raise AssertionError(explain(history, f"RTL faulted at pc={int(dut.dbg_pc.value):#x}, ISS executed it")) + + if int(dut.rvfi_valid.value): + try: + exp = iss.step() + except IssError as exc: + raise AssertionError(explain(history, f"RTL retired pc={int(dut.rvfi_pc.value):#x} but ISS raised: {exc}")) + for sig, attr, cyc_dep in FIELDS: + if cyc_dep and exp.cycle_dependent: + continue + got, want = int(getattr(dut, sig).value), int(getattr(exp, attr)) + assert got == want, explain( + history + [exp], f"retire #{exp.order} {attr}: RTL={got:#x} ISS={want:#x}\n at {exp}") + assert int(dut.rvfi_cycle_dep.value) == int(exp.cycle_dependent), explain(history + [exp], "cycle_dep flag") + history.append(exp) + if exp.halt: + break + else: + assert not iss.halted, "ISS halted but RTL never reported the halting retire" + + if expect_fault is not None: + assert faulted == expect_fault, f"expected fault={expect_fault}, got {faulted}" + + # after a halt/fault the core must be frozen + pc, regs = int(dut.dbg_pc.value), int(dut.dbg_regs.value) + for _ in range(4): + await RisingEdge(dut.clk) + await ReadOnly() + assert not int(dut.rvfi_valid.value) and not int(dut.console_valid.value) + assert int(dut.dbg_pc.value) == pc and int(dut.dbg_regs.value) == regs, "core moved after halt" + assert int(dut.halted.value) == int(not faulted) and int(dut.fault.value) == int(faulted) + + # architectural state matches + # Registers last written by a cycle-dependent instruction (rdcycle, rdtime, CYCLE MMIO) hold timing + # values the ISS cannot know; they are masked in the per-retire compare and here. + timing = set() + for r in history: + if r.rd_addr: + (timing.add if r.cycle_dependent else timing.discard)(r.rd_addr) + got_regs = int(dut.dbg_regs.value) + got = [(got_regs >> (32 * i)) & 0xFFFFFFFF for i in range(32)] + diff = [i for i in range(32) if got[i] != iss.regs[i] and i not in timing] + assert not diff, "register file differs: " + ", ".join( + f"x{i} RTL={got[i]:#x} ISS={iss.regs[i]:#x}" for i in diff) + assert bytes(console) == bytes(iss.console), f"console: RTL={bytes(console)!r} ISS={bytes(iss.console)!r}" + if not faulted: + assert bool(int(dut.tohost_valid.value)) == (iss.tohost is not None) + if iss.tohost is not None: + assert int(dut.tohost_data.value) == iss.tohost + assert int(dut.dbg_pc.value) == iss.pc + assert iss.instret == int(dut.dbg_instret.value), ( + f"instret: ISS={iss.instret} RTL={int(dut.dbg_instret.value)}") + want = expected_cycles(iss.instret, history) + assert cycles == want, f"{CORE} cycle count: RTL={cycles} expected={want} (instret={iss.instret})" + return iss, bytes(console), cycles + + diff --git a/sim/run.sh b/sim/run.sh index e61e8e6..aa4cfc8 100644 --- a/sim/run.sh +++ b/sim/run.sh @@ -33,18 +33,27 @@ unit() { SIM_TAG=_nobypass EXTRA_COMPILE_ARGS="-GBYPASS=0" run test_regfile_nobypass regfile regfile.sv run test_csr_counters csr_counters csr_counters.sv run test_mmio mmio mmio.sv + run test_mem_sync mem_sync mem_sync.sv + SIM_TAG=_oldread EXTRA_COMPILE_ARGS="-GREAD_NEW=0" MEM_READ_NEW=0 run test_mem_sync mem_sync mem_sync.sv } lockstep() { run test_core_lockstep tiny_cpu_top alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv \ - csr_counters.sv mmio.sv mem_comb.sv core_single.sv tiny_cpu_top.sv + csr_counters.sv mmio.sv mem_comb.sv mem_sync.sv core_single.sv core_pipe.sv tiny_cpu_top.sv +} + +pipe() { + TINYCPU_CORE=PIPE EXTRA_COMPILE_ARGS="-G'CORE=\"PIPE\"'" SIM_TAG=_pipe run test_pipe_lockstep tiny_cpu_top \ + alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv csr_counters.sv mmio.sv mem_comb.sv \ + mem_sync.sv core_single.sv core_pipe.sv tiny_cpu_top.sv } case "${1:-all}" in unit) unit ;; lockstep) lockstep ;; - all) unit; lockstep ;; - *) echo "usage: $0 unit|lockstep|all" >&2; exit 2 ;; + pipe) pipe ;; + all) unit; lockstep; pipe ;; + *) echo "usage: $0 unit|lockstep|pipe|all" >&2; exit 2 ;; esac if [ ${#FAILED[@]} -gt 0 ]; then echo "FAILED: ${FAILED[*]}"; exit 1; fi diff --git a/sim/test_core_lockstep.py b/sim/test_core_lockstep.py index 2a4188c..d81785c 100644 --- a/sim/test_core_lockstep.py +++ b/sim/test_core_lockstep.py @@ -1,157 +1,15 @@ -"""RTL vs golden ISS, one retire at a time. - -Every program in sw/build/{riscv-tests,programs} is loaded into tiny_cpu_top. The ISS steps once for -each rvfi_valid pulse and all retire fields must match; the first divergence stops the run with the -last retired instructions of the ISS. Also checked: faults (ISS raises <=> RTL sets `fault`, with no -retire), halt behaviour, MMIO side effects, and CPI == 1 for the single-cycle core. +"""core_single vs golden ISS over every riscv-tests image and example program, plus directed cases. Build the images first: make -C sw all. REQUIRE_RISCV_TESTS=1 turns a missing build into a failure. +(The 5-stage core has its own file: test_pipe_lockstep.py.) """ -import json import os from pathlib import Path import cocotb -from cocotb.clock import Clock -from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge, Timer - -from golden.decode import encode as e -from golden.iss import IMEM_SIZE, DMEM_SIZE, Iss, IssError - -ROOT = Path(__file__).resolve().parent.parent -BUILD = ROOT / "sw" / "build" -EXPECTED = json.loads((ROOT / "sim" / "programs" / "expected.json").read_text()) -MAX_CYCLES = 200_000 - -FIELDS = [ # (RTL signal, ISS Retire attribute, cycle-dependent?) - ("rvfi_order", "order", False), ("rvfi_pc", "pc", False), ("rvfi_insn", "insn", False), - ("rvfi_rd_addr", "rd_addr", False), ("rvfi_rd_wdata", "rd_wdata", True), - ("rvfi_pc_wdata", "pc_wdata", False), ("rvfi_mem_addr", "mem_addr", False), - ("rvfi_mem_rmask", "mem_rmask", False), ("rvfi_mem_wmask", "mem_wmask", False), - ("rvfi_mem_rdata", "mem_rdata", True), ("rvfi_mem_wdata", "mem_wdata", False), - ("rvfi_misaligned", "misaligned", False), ("rvfi_halt", "halt", False), -] - - -def images(): - out = [] - for group in ("riscv-tests", "programs"): - out += sorted(p.name.removesuffix(".imem.bin") for p in (BUILD / group).glob("*.imem.bin")) - return out - - -def stem_path(name): - group = "riscv-tests" if name.startswith("rv32ui") else "programs" - return BUILD / group / name - - -def words(data: bytes, count: int) -> list[int]: - data = bytes(data).ljust(count * 4, b"\0") - return [int.from_bytes(data[4 * i : 4 * i + 4], "little") for i in range(count)] - - -def prog_bytes(*ws) -> bytes: - return b"".join((w & 0xFFFFFFFF).to_bytes(4, "little") for w in ws) - - -_clock = None - - -def ensure_clock(dut): - """One clock driver per test (a test may call lockstep() many times; finished tasks are dead).""" - global _clock - if _clock is None or _clock.done(): - _clock = cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) - - -async def reset_and_load(dut, imem: bytes, dmem: bytes): - ensure_clock(dut) - await Timer(1, "ns") # a previous lockstep() ends in the ReadOnly phase, where writes are illegal - dut.rst_n.value, dut.load_en.value = 0, 0 - dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 0, 0, 0 - await FallingEdge(dut.clk) - for sel, data, count in ((0, imem, IMEM_SIZE // 4), (1, dmem, DMEM_SIZE // 4)): - for i, w in enumerate(words(data, count)): # whole memory: wipes the previous program - dut.load_en.value, dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 1, sel, i, w - await FallingEdge(dut.clk) - dut.load_en.value = 0 - await FallingEdge(dut.clk) - dut.rst_n.value = 1 # first rising edge after this executes the instruction at pc 0 - - -def explain(history, msg): - tail = "\n".join(f" {r}" for r in history[-8:]) - return f"{msg}\n last ISS retires:\n{tail}" - - -async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | None = None): - """Run RTL and ISS together to halt (or fault). Returns (iss, rtl_console, cycles).""" - await reset_and_load(dut, imem, dmem) - iss = Iss(imem, dmem) - history, console, cycles, faulted = [], bytearray(), 0, False - - while True: - await RisingEdge(dut.clk) - await ReadOnly() - cycles += 1 - assert cycles <= MAX_CYCLES, explain(history, f"no halt after {MAX_CYCLES} cycles") - if int(dut.console_valid.value): - console.append(int(dut.console_data.value)) - - if int(dut.fault.value): # RTL refused an instruction: the ISS must raise on the same one - assert not int(dut.rvfi_valid.value), "RTL retired and faulted in the same cycle" - try: - iss.step() - except IssError: - faulted = True - break - raise AssertionError(explain(history, f"RTL faulted at pc={int(dut.dbg_pc.value):#x}, ISS executed it")) - - if int(dut.rvfi_valid.value): - try: - exp = iss.step() - except IssError as exc: - raise AssertionError(explain(history, f"RTL retired pc={int(dut.rvfi_pc.value):#x} but ISS raised: {exc}")) - for sig, attr, cyc_dep in FIELDS: - if cyc_dep and exp.cycle_dependent: - continue - got, want = int(getattr(dut, sig).value), int(getattr(exp, attr)) - assert got == want, explain( - history + [exp], f"retire #{exp.order} {attr}: RTL={got:#x} ISS={want:#x}\n at {exp}") - assert int(dut.rvfi_cycle_dep.value) == int(exp.cycle_dependent), explain(history + [exp], "cycle_dep flag") - history.append(exp) - if exp.halt: - break - else: - assert not iss.halted, "ISS halted but RTL never reported the halting retire" - - if expect_fault is not None: - assert faulted == expect_fault, f"expected fault={expect_fault}, got {faulted}" - - # after a halt/fault the core must be frozen - pc, regs = int(dut.dbg_pc.value), int(dut.dbg_regs.value) - for _ in range(4): - await RisingEdge(dut.clk) - await ReadOnly() - assert not int(dut.rvfi_valid.value) and not int(dut.console_valid.value) - assert int(dut.dbg_pc.value) == pc and int(dut.dbg_regs.value) == regs, "core moved after halt" - assert int(dut.halted.value) == int(not faulted) and int(dut.fault.value) == int(faulted) - - # architectural state matches - got_regs = int(dut.dbg_regs.value) - assert [(got_regs >> (32 * i)) & 0xFFFFFFFF for i in range(32)] == iss.regs, "register file differs" - assert bytes(console) == bytes(iss.console), f"console: RTL={bytes(console)!r} ISS={bytes(iss.console)!r}" - if not faulted: - assert bool(int(dut.tohost_valid.value)) == (iss.tohost is not None) - if iss.tohost is not None: - assert int(dut.tohost_data.value) == iss.tohost - assert int(dut.dbg_pc.value) == iss.pc - # single-cycle core: one instruction per clock, from reset release to the halting retire - assert cycles == iss.instret == int(dut.dbg_instret.value), ( - f"CPI != 1: cycles={cycles} instret={iss.instret} rtl_instret={int(dut.dbg_instret.value)}") - return iss, bytes(console), cycles +from lockstep_lib import EXPECTED, IMEM_SIZE, e, images, lockstep, prog_bytes, stem_path # --------------------------------------------------------------------------- images @cocotb.test() @@ -179,9 +37,6 @@ async def test_image(dut, name): # --------------------------------------------------------------------------- directed -TOHOST_HI = 0x20000 - - @cocotb.test() async def ecall_and_ebreak_halt_after_retiring(dut): for halt_insn in (e("ecall"), e("ebreak")): diff --git a/sim/test_mem_sync.py b/sim/test_mem_sync.py new file mode 100644 index 0000000..ce09cdb --- /dev/null +++ b/sim/test_mem_sync.py @@ -0,0 +1,83 @@ +import random + +import cocotb +from cocotb.clock import Clock +from cocotb.triggers import FallingEdge, Timer + +READ_NEW = int(__import__("os").environ.get("MEM_READ_NEW", "1")) + + +async def setup(dut): + cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) + dut.re.value, dut.raddr.value, dut.wmask.value = 1, 0, 0 + dut.waddr.value = dut.wdata.value = dut.ld_en.value = dut.ld_addr.value = dut.ld_data.value = 0 + await FallingEdge(dut.clk) + + +async def edge(dut, **kw): + """Apply inputs at the falling edge; the following rising edge samples them.""" + for k, v in kw.items(): + getattr(dut, k).value = v + await FallingEdge(dut.clk) + dut.wmask.value, dut.ld_en.value = 0, 0 + return int(dut.rdata.value) + + +@cocotb.test() +async def read_data_arrives_one_clock_after_the_address(dut): + await setup(dut) + await edge(dut, ld_en=1, ld_addr=3, ld_data=0xAABBCCDD) + await edge(dut, ld_en=1, ld_addr=4, ld_data=0x11223344) + assert await edge(dut, raddr=3) == 0xAABBCCDD + assert await edge(dut, raddr=4) == 0x11223344 + assert await edge(dut, raddr=4) == 0x11223344 # address unchanged -> data unchanged + + +@cocotb.test() +async def re_low_holds_the_output(dut): + await setup(dut) + await edge(dut, ld_en=1, ld_addr=1, ld_data=0x1111) + await edge(dut, ld_en=1, ld_addr=2, ld_data=0x2222) + assert await edge(dut, raddr=1) == 0x1111 + assert await edge(dut, re=0, raddr=2) == 0x1111 # held, not updated + assert await edge(dut, re=1, raddr=2) == 0x2222 + + +@cocotb.test() +async def byte_masked_writes(dut): + await setup(dut) + await edge(dut, ld_en=1, ld_addr=7, ld_data=0x44332211) + await edge(dut, waddr=7, wdata=0xAABBCCDD, wmask=0b0110) + assert await edge(dut, raddr=7) == 0x44BBCC11 + + +@cocotb.test() +async def same_edge_read_and_write(dut): + """The store-then-load case: read and write the same word on one edge.""" + await setup(dut) + await edge(dut, ld_en=1, ld_addr=9, ld_data=0x44332211) + got = await edge(dut, raddr=9, waddr=9, wdata=0xAABBCCDD, wmask=0b0101) + want_new, want_old = 0x44BB22DD, 0x44332211 + assert got == (want_new if READ_NEW else want_old), f"{got:#x}" + assert await edge(dut, raddr=9) == want_new # the write itself happened either way + # a write to a different word never disturbs the read + assert await edge(dut, raddr=9, waddr=10, wdata=0xFFFFFFFF, wmask=0xF) == want_new + + +@cocotb.test() +async def random_traffic_matches_a_model(dut): + await setup(dut) + rng = random.Random(42) + model = [0] * 16 + for i in range(16): + await edge(dut, ld_en=1, ld_addr=i, ld_data=0) + for _ in range(500): + ra, wa = rng.randrange(16), rng.randrange(16) + wmask, wdata = rng.randrange(16), rng.getrandbits(32) + old = list(model) + for b in range(4): + if wmask >> b & 1: + model[wa] = (model[wa] & ~(0xFF << 8 * b)) | (wdata & (0xFF << 8 * b)) + got = await edge(dut, raddr=ra, waddr=wa, wdata=wdata, wmask=wmask) + want = model[ra] if (READ_NEW and ra == wa) else old[ra] + assert got == want, f"read {ra} write {wa}/{wmask:#x}: {got:#x} != {want:#x}" diff --git a/sim/test_pipe_lockstep.py b/sim/test_pipe_lockstep.py new file mode 100644 index 0000000..9686117 --- /dev/null +++ b/sim/test_pipe_lockstep.py @@ -0,0 +1,171 @@ +"""5-stage core (CORE="PIPE") vs the golden ISS, Phase 3: no forwarding, no load-use stall. + +Programs here are hazard-free by construction (golden/hazard_free.py). The riscv-tests images need +forwarding and join in Phase 4. Run with TINYCPU_CORE=PIPE and -GCORE="PIPE" (sim/run.sh pipe). +""" + +import cocotb +from cocotb.triggers import ReadOnly, RisingEdge + +from golden.hazard_free import PROGRAMS, random_program +from golden.progbuild import Prog +from lockstep_lib import CORE, IMEM_SIZE, e, lockstep, prog_bytes, reset_and_load + +assert CORE == "PIPE", "run through `bash sim/run.sh pipe` (sets TINYCPU_CORE=PIPE and -GCORE=\"PIPE\")" + + +@cocotb.parametrize(name=sorted(PROGRAMS)) +async def test_hazard_free_program(dut, name): + imem, dmem = PROGRAMS[name]().build() + iss, console, _ = await lockstep(dut, imem, dmem, expect_fault=False) + assert iss.passed + if name == "console_and_csr": + assert console == b"Hi, pipe!\n!" + + +@cocotb.parametrize(seed=list(range(60))) +async def test_random_hazard_free(dut, seed): + imem, dmem = random_program(seed).build() + iss, _, _ = await lockstep(dut, imem, dmem, expect_fault=False) + assert iss.passed + + +# --------------------------------------------------------------------------- halt and faults +@cocotb.test() +async def ecall_ebreak_halt(dut): + for insn in (e("ecall"), e("ebreak")): + p = Prog().addi(1, 0, 7) + p.raw(insn).addi(1, 0, 9) + iss, _, _ = await lockstep(dut, p.build()[0], expect_fault=False) + assert iss.regs[1] == 7 and iss.tohost is None + + +def fault_cases(): + def build(*items, tail=()): + p = Prog() + for it in items: + it(p) + return p.build()[0] + + return { + "illegal instruction": build(lambda p: p.addi(1, 0, 5), lambda p: p.raw(0x00000000)), + "csrrs with rs1 != x0": build(lambda p: p.raw(e("csrrs", 1, 1, csr=0xC00))), + "unknown csr": build(lambda p: p.raw(e("csrrs", 1, 0, csr=0x300))), + "load from IMEM": build(lambda p: p.lw(1, 0, 0)), + "store to IMEM": build(lambda p: p.sw(0, 0, 0)), + "unmapped load": build(lambda p: p.lui(1, 0x80000), lambda p: p.lw(2, 1, 0)), + "tohost byte store": build(lambda p: p.lui(1, 0x20), lambda p: p.sb(0, 1, 4)), + "store to CYCLE": build(lambda p: p.lui(1, 0x20), lambda p: p.sw(0, 1, 8)), + "misaligned jal": build(lambda p: p.raw(e("jal", 0, imm=6))), + "misaligned taken branch": build(lambda p: p.raw(e("beq", 0, 0, 0, imm=2))), + "jalr to odd word": build(lambda p: p.addi(1, 0, 6), lambda p: p.jalr(0, 1, 0)), + "jump past end of IMEM": build(lambda p: p.lui(1, 1), lambda p: p.jalr(0, 1, 0)), + "fall off end of IMEM": prog_bytes(e("jal", 0, imm=IMEM_SIZE - 4)), + } + + +@cocotb.test() +async def faults_stop_the_core_without_retiring(dut): + for label, imem in fault_cases().items(): + try: + await lockstep(dut, imem, expect_fault=True) + except AssertionError as exc: + raise AssertionError(f"[{label}] {exc}") from exc + + +def setup_console(p): + """x1 = CONSOLE, x2 = 'A', x3 = 'B', then 3 NOPs so the following dense code has no hazards.""" + p.li(1, 0x20000) + p.li(2, ord("A")) + p.li(3, ord("B")) + p.nop(3) + + +@cocotb.test() +async def younger_instructions_have_no_effect_after_a_fault(dut): + """Dense (pad=0) code: 'A' prints, the faulting instruction stops the core, and the stores right + behind it (already in EX/MEM when it faults) must NOT print.""" + for label, faulting in { + "illegal": lambda p: p.raw(0x00000000), + "bus fault": lambda p: p.lw(9, 0, 0), + "misaligned jal": lambda p: p.raw(e("jal", 0, imm=6)), + }.items(): + p = Prog(pad=0) + setup_console(p) + p.sb(2, 1, 0) + faulting(p) + p.sb(3, 1, 0).sb(3, 1, 0).sb(3, 1, 0).nop(3) + p.exit() + try: + iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=True) + except AssertionError as exc: + raise AssertionError(f"[{label}] {exc}") from exc + assert console == b"A", f"[{label}] younger stores leaked: {console!r}" + + +@cocotb.test() +async def younger_instructions_have_no_effect_after_halt(dut): + for label, halting in { + "tohost store": lambda p: (p.li(5, 0x20004), p.li(6, 1), p.nop(2), p.sw(6, 5, 0)), + "ecall": lambda p: p.raw(e("ecall")), + }.items(): + p = Prog(pad=0) + setup_console(p) + p.sb(2, 1, 0) + halting(p) + p.sb(3, 1, 0).sb(3, 1, 0).sb(3, 1, 0) + iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=False) + assert console == b"A", f"[{label}] instructions after the halt leaked output: {console!r}" + + +# --------------------------------------------------------------------------- pipeline behaviour +async def trace_run(dut, imem, dmem): + """Run to halt, recording every cycle's stage occupancy (after each clock edge).""" + await reset_and_load(dut, imem, dmem) + rows = [] + for _ in range(100_000): + await RisingEdge(dut.clk) + await ReadOnly() + valid = int(dut.dbg_stage_valid.value) + pcs = int(dut.dbg_stage_pc.value) + rows.append({"valid": valid, "pc": [(pcs >> (32 * s)) & 0xFFFFFFFF for s in range(5)], + "flush": int(dut.dbg_flush_ifid.value), "flush2": int(dut.dbg_flush_idex.value), + "retire": int(dut.rvfi_valid.value), "halted": int(dut.halted.value)}) + if rows[-1]["halted"]: + return rows + raise AssertionError("no halt") + + +@cocotb.test() +async def five_instructions_overlap_and_flushes_match_redirects(dut): + p = Prog(pad=0) + p.li(1, 3).li(2, 5).nop(3) + p.add(3, 1, 2).addi(4, 1, 1).sub(5, 2, 1).xor(6, 1, 2).nop(3) + p.jal(0, "skip").addi(7, 0, 99).addi(7, 0, 98).label("skip").nop(3) + p.exit() + imem, dmem = p.build() + rows = await trace_run(dut, imem, dmem) + + assert any(r["valid"] == 0b11111 and len(set(r["pc"])) == 5 for r in rows), "never had 5 instructions in flight" + # every flush flag comes with its twin and exactly one jal redirected + assert all(r["flush"] == r["flush2"] for r in rows) + assert sum(r["flush"] for r in rows) == 1 + + # the classic pipeline diagram, for the log + first = {} + lines = ["cycle IF ID EX MEM WB"] + for n, r in enumerate(rows[:24], 1): + cells = [] + for s in range(5): + if r["valid"] >> s & 1: + cells.append(f"{r['pc'][s]:#06x} ") + else: + cells.append(" -- ") + lines.append(f"{n:5d} " + " ".join(cells) + (" <- flush" if r["flush"] else "")) + dut._log.info("\n" + "\n".join(lines)) + + # in-order: a given pc advances one stage per cycle when it is not squashed + for n in range(len(rows) - 1): + for s in range(4): + if rows[n]["valid"] >> s & 1 and rows[n + 1]["valid"] >> (s + 1) & 1 and not rows[n]["flush"]: + assert rows[n + 1]["pc"][s + 1] == rows[n]["pc"][s], f"cycle {n + 1}: stage {s}->{s + 1} pc mismatch" From ce0058da3b60368bb24c2667d0d41e375042a36a Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sun, 25 Oct 2026 12:23:21 +0530 Subject: [PATCH 05/10] feat: Phase 4 forwarding and hazard handling --- .github/workflows/rtl-nightly.yml | 54 ++++++++++++++ AGENTS.md | 4 +- CLAUDE.md | 4 +- CONTEXT.md | 14 +++- README.md | 2 +- docs/ARCHITECTURE.md | 38 ++++++++-- docs/PLAN.md | 4 +- rtl/core_pipe.sv | 114 ++++++++++++++++++++++-------- rtl/forward_unit.sv | 44 ++++++++++++ rtl/hazard_unit.sv | 43 +++++++++++ rtl/tiny_cpu_top.sv | 24 ++++--- scripts/lint.sh | 2 +- sim/golden/hazard_cases.py | 37 ++++++++++ sim/golden/hazard_random.py | 64 +++++++++++++++++ sim/golden/iss.py | 6 ++ sim/golden/perf.py | 74 +++++++++++++++++++ sim/golden/pipemodel.py | 76 ++++++++++++++++++++ sim/golden/test_iss.py | 9 +++ sim/golden/test_perf_baseline.py | 32 +++++++++ sim/golden/test_pipemodel.py | 72 +++++++++++++++++++ sim/lockstep_lib.py | 34 ++++----- sim/perf_baseline.json | 86 ++++++++++++++++++++++ sim/run.sh | 9 ++- sim/test_forward_unit.py | 49 +++++++++++++ sim/test_hazard_unit.py | 37 ++++++++++ sim/test_pipe_hazards.py | 63 +++++++++++++++++ sim/test_pipe_lockstep.py | 85 +++++++++++++++------- 27 files changed, 978 insertions(+), 102 deletions(-) create mode 100644 .github/workflows/rtl-nightly.yml create mode 100644 rtl/forward_unit.sv create mode 100644 rtl/hazard_unit.sv create mode 100644 sim/golden/hazard_cases.py create mode 100644 sim/golden/hazard_random.py create mode 100644 sim/golden/perf.py create mode 100644 sim/golden/pipemodel.py create mode 100644 sim/golden/test_perf_baseline.py create mode 100644 sim/golden/test_pipemodel.py create mode 100644 sim/perf_baseline.json create mode 100644 sim/test_forward_unit.py create mode 100644 sim/test_hazard_unit.py create mode 100644 sim/test_pipe_hazards.py diff --git a/.github/workflows/rtl-nightly.yml b/.github/workflows/rtl-nightly.yml new file mode 100644 index 0000000..4abb279 --- /dev/null +++ b/.github/workflows/rtl-nightly.yml @@ -0,0 +1,54 @@ +name: RTL Nightly + +# Long random sweeps: hazard-dense programs in lockstep with the golden ISS, forwarding on and off. +# A failing seed is reproducible locally: RANDOM_SEEDS= bash sim/run.sh pipe + +on: + schedule: + - cron: '17 3 * * *' + workflow_dispatch: + inputs: + seeds: + description: 'Random seeds per mode' + default: '5000' + +jobs: + random-sweep: + runs-on: ubuntu-24.04 + timeout-minutes: 120 + + steps: + - uses: actions/checkout@v4 + + - name: Cache Verilator build + id: cache-verilator + uses: actions/cache@v4 + with: + path: ~/verilator-install + key: verilator-v5.036-ubuntu-24.04 + + - name: Build Verilator v5.036 from source + if: steps.cache-verilator.outputs.cache-hit != 'true' + run: | + sudo apt-get update -qq + sudo apt-get install -y autoconf flex bison libfl2 libfl-dev help2man perl + git clone --depth 1 --branch v5.036 https://github.com/verilator/verilator.git /tmp/verilator + cd /tmp/verilator + autoconf + ./configure --prefix="$HOME/verilator-install" + make -j$(nproc) + make install + + - name: Add Verilator to PATH + run: echo "$HOME/verilator-install/bin" >> $GITHUB_PATH + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - run: pip install -r sim/requirements.txt + + - name: Random hazard sweep (pipelined core) + run: bash sim/run.sh pipe + env: + RANDOM_SEEDS: ${{ github.event.inputs.seeds || '5000' }} diff --git a/AGENTS.md b/AGENTS.md index 4380051..5f698ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,9 @@ Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (C bash scripts/lint.sh # verilator -Wall on every module standalone + the full design bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) -bash sim/run.sh pipe # core_pipe vs golden ISS over the hazard-free programs (no toolchain needed) +bash sim/run.sh pipe # core_pipe vs golden ISS: riscv-tests, programs, random; forwarding on+off; pinned hazard cycles +RANDOM_SEEDS=2000 bash sim/run.sh pipe # bigger random sweep (default 200 per mode) +cd sim && python -m golden.perf # CPI table; --update rewrites sim/perf_baseline.json (deliberately!) bash sim/run.sh all # everything above ``` Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` diff --git a/CLAUDE.md b/CLAUDE.md index 5a31909..80bf097 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,9 @@ Set `REQUIRE_RISCV_TESTS=1` to make a missing `sw/build` fail instead of skip (C bash scripts/lint.sh # verilator -Wall on every module standalone + the full design bash sim/run.sh unit # cocotb unit suites (one SIM_BUILD per suite) bash sim/run.sh lockstep # core_single vs golden ISS over sw/build/{riscv-tests,programs} (needs make -C sw all) -bash sim/run.sh pipe # core_pipe vs golden ISS over the hazard-free programs (no toolchain needed) +bash sim/run.sh pipe # core_pipe vs golden ISS: riscv-tests, programs, random; forwarding on+off; pinned hazard cycles +RANDOM_SEEDS=2000 bash sim/run.sh pipe # bigger random sweep (default 200 per mode) +cd sim && python -m golden.perf # CPI table; --update rewrites sim/perf_baseline.json (deliberately!) bash sim/run.sh all # everything above ``` Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` diff --git a/CONTEXT.md b/CONTEXT.md index 9d9c17a..01ecfc1 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -20,7 +20,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Phase 3 (5-stage pipeline, no forwarding/stall): DONE. `mem_sync.sv`, `core_pipe.sv`, `CORE="PIPE"` in the top, uniform per-stage `dbg_*` ports on both cores, `golden/progbuild.py` + `hazard_free.py`. 74 pipe lockstep tests pass; 7 mutants killed. Taken-branch flush landed here (not Phase 4) because lockstep needs it. See docs/ARCHITECTURE.md. -- Next: Phase 4 (`forward_unit`, `hazard_unit`: load-use stall + `cfg_forward_en`, pinned hazard cycle counts, riscv-tests on PIPE). +- Phase 4 (hazards): DONE. `forward_unit`, `hazard_unit`, `cfg_forward_en` pin, dbg stall/load-use/fwd flags. The pipeline runs the + 40 riscv-tests + all programs + 260 random programs in lockstep in BOTH modes, cycle-exact against `golden/pipemodel.py`. + 11 mutants killed. `sim/perf_baseline.json` pins cycles per core/mode. +- Next: Phase 5 (WASM harness: dual-model build, `TinyCpuSim` API, `docs/STATE_SCHEMA.md`, Node test). ## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) - Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. @@ -42,7 +45,7 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on | Memory | Harvard, synchronous read. IMEM `0x0000_0000` 4 KiB, DMEM `0x0001_0000` 4 KiB. | | MMIO | `CONSOLE 0x0002_0000`, `TOHOST 0x0002_0004` (1 = PASS), `CYCLE 0x0002_0008` | | Branches | Resolved in EX, predict not-taken, 2-cycle flush penalty | -| Forwarding | EX/MEM->EX and MEM/WB->EX for rs1 and rs2; runtime pin `cfg_forward_en` | +| Forwarding | EX/MEM->EX and MEM/WB->EX for rs1 and rs2; runtime pin `cfg_forward_en` (off: stall on every RAW until WB; costs 2/1/0 for distance 1/2/3, not the 3 first planned) | | Hazards | Only stall: 1-cycle load-use. Regfile has write-first bypass. `x0` hard-wired to 0 | | Verification | Unit (cocotb) + golden ISS lockstep + riscv-tests rv32ui-p-* + random programs | | Retire trace | RVFI-lite record on every core | @@ -75,3 +78,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Cycle-dependent registers must be masked in the final register compare too, not only per retire. - Dynamic hazard distance != static: instructions at jump targets skip the jump's padding (use `raw_hazards()` on the ISS trace). - Hazard-free means >= 3 instructions between producer and consumer (2 NOPs) for this pipeline; `Prog.li/exit/putc` add gap NOPs at pad<2. + +## Gotchas found in Phase 4 +- A taken branch to pc+4 is invisible in `pc_wdata`; `Retire.taken` (ISS) carries it so the timing model stays exact. +- Hand-derived expectations must separate setup from the hazard under test (`lui; lw` is itself a distance-1 RAW). +- A flush hides the stall of the squashed instruction (`jal rd; use rd` costs 2 either way). +- `RANDOM_SEEDS=N bash sim/run.sh pipe` scales the random sweep (default 200 per mode); mutation runs use 40. +- The root filesystem filled during a long mutation sweep (home caches, not this repo): keep `/tmp` and logs small. diff --git a/README.md b/README.md index ffa248e..c0342e9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 3 done (5-stage pipeline datapath).** Both RV32I cores retire instructions identically to the Python golden ISS: the single-cycle core on all 40 official `rv32ui-p-*` tests plus 5 programs, the 5-stage core on hazard-free programs. Next: forwarding and hazard stalls so the pipeline runs arbitrary code. +> **Status: Phase 4 done (hazards).** Both RV32I cores retire instructions identically to the Python golden ISS on all 40 official `rv32ui-p-*` tests, 5 programs and hundreds of random programs. The 5-stage core has forwarding and load-use stalls (switchable at runtime) and matches an independent timing model cycle for cycle. Next: the WASM harness. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8755821..43e7968 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -75,13 +75,36 @@ cycle IF ID EX MEM WB 22 0x0058 0x0054 -- -- 0x0048 ``` -Phase 3 scope: datapath, flush, faults and halt. **No forwarding and no load-use stall yet** - producers must be ->= 3 instructions ahead of consumers (the register file's write-first bypass covers WB->ID). Phase 4 adds the -hazard/forward units, after which the riscv-tests images run on this core too. - -Deviation from the original plan: the taken-branch flush is in Phase 3, not 4. Without it the NOPs after a taken +Deviation from the original plan: the taken-branch flush landed in Phase 3, not 4. Without it the NOPs after a taken branch retire on the wrong path and the lockstep stream can never match the ISS. +### Hazards (Phase 4) + +| Producer ahead of the consumer | forwarding **on** | forwarding **off** (`cfg_forward_en = 0`) | +|---|---|---| +| ALU / link, distance 1 | forwarded EX/MEM -> EX, **0 stalls** | 2 stalls | +| ALU / link, distance 2 | forwarded MEM/WB -> EX, **0 stalls** | 1 stall | +| load or CSR read, distance 1 | **1 stall** (load-use), then MEM/WB | 2 stalls | +| load or CSR read, distance 2 | **0 stalls** (MEM/WB) | 1 stall | +| any producer, distance >= 3 | register file write-first bypass, 0 | 0 | +| taken branch / jump | flush IF/ID + ID/EX: **2 cycles** (predict not-taken); a flush hides any stall on the squashed instruction | same | + +* `forward_unit` picks `dbg_fwd_a/b` per operand (0 none, 1 EX/MEM, 2 MEM/WB; the nearer producer wins; never x0). +* `hazard_unit` raises `dbg_stall` (hold `pc_q`, the IF/ID register and IMEM's output register via `imem_re = 0`, and put a + bubble in ID/EX) and `dbg_load_use`. A redirect from EX always wins over a stall. +* **Loads and CSR reads are "late" producers.** Load data exists only after MEM, and a CSR is read in WB, so neither can be + forwarded from EX/MEM; their consumers wait one cycle and take the value from MEM/WB. +* **With forwarding off the cost is 2, not 3**, because the register file's write-first bypass delivers a value written + in WB to a reader in ID in the same cycle. (The original plan's "3" assumed no bypass.) +* **Exact timing is specified outside the RTL** by `sim/golden/pipemodel.py`: with `T(i)` the cycle instruction `i` is in + EX, `T(i) >= T(i-1)+1`, `>= T(branch)+3` after a taken branch, `>= T(p)+2` for a late producer / `+1` otherwise with + forwarding, `>= T(p)+3` without. The RTL must match it to the cycle on every program (`cycles = T(last) + 2`). +* Pinned numbers: `sim/golden/hazard_cases.py` (paper-derived, checked against the model *and* the RTL, including the debug + flags) and `sim/perf_baseline.json` (cycles per program, per core and mode; `python -m golden.perf`). + +What the baseline shows: on loop-heavy code CPI with forwarding is 1.4-1.65, and almost all of the excess is the 2-cycle +penalty on every taken loop branch (predict-not-taken), not data hazards - the case for a branch predictor (PLAN stretch #2). + ### Timing contract * **Synchronous memories.** `pc_q` is the IMEM address; the BRAM output register *is* the instruction in ID, with `id_pc` carried beside it. DMEM is addressed in EX, so load data is back in MEM, where the LSU extracts and extends it. @@ -115,7 +138,10 @@ runs dense code with stores directly behind the faulting/halting instruction and | Lockstep | `sim/test_core_lockstep.py` | 45 images (40 riscv-tests + 5 programs) retire-for-retire against the ISS; 13 fault cases; halt/frozen-core checks; CPI = 1 | | Lint | `scripts/lint.sh` | `-Wall` clean for every module standalone and the whole design | -| Pipeline lockstep | `sim/test_pipe_lockstep.py` | 9 hazard-free programs + 60 random ones retire-for-retire vs the ISS; 13 fault cases; younger-instruction gating after fault/halt; exact cycle formula; 5-deep overlap; flush count | +| Pipeline lockstep | `sim/test_pipe_lockstep.py` | In **both forwarding modes**: the 40 riscv-tests + 5 programs, 9 hazard-free programs, 60 hazard-free and 200 hazard-dense random programs (`RANDOM_SEEDS=N` scales it; nightly runs 5000) retire-for-retire vs the ISS; 13 fault cases; younger-instruction gating after fault/halt; cycle count == the independent model; 5-deep overlap | +| Hazard patterns | `sim/test_pipe_hazards.py` | 17 paper-derived patterns x 2 modes: pinned extra cycles, `dbg_stall` count == model, one `dbg_load_use` pulse where expected, forwarding source observed | +| Hazard units | `sim/test_forward_unit.py`, `test_hazard_unit.py` | Exhaustive over (control bits x small register space): 5184 and 20736 combinations | +| Timing model | `sim/golden/test_pipemodel.py` | Paper-derived cases + random-program sanity, no RTL | | Program builder | `sim/golden/progbuild.py`, `hazard_free.py` | `Prog(pad=N)` mini-assembler; `raw_hazards()` proves a program hazard-free from its dynamic ISS trace (no toolchain needed) | Run: `bash scripts/lint.sh && bash sim/run.sh all` (after `make -C sw all`). `all` = unit + lockstep (single-cycle) + pipe. diff --git a/docs/PLAN.md b/docs/PLAN.md index 8a93571..9fc5035 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -206,8 +206,8 @@ Four layers. Each phase below says which layers must be green. `cfg_forward_en` ∈ {0, 1}. CI runs 200 seeds; nightly runs 5000. **Directed hazard tests** (`sim/programs/hazards_*.S` + `test_hazards.py`) check **exact -cycle counts**, not just results. Example: `lw x1,0(x2); add x3,x1,x1` must take exactly 1 -extra cycle with forwarding on and 3 with it off. These numbers are the ones the Hazard Lab +cycle counts**, not just results. Example: `lw x1,0(x2); add x3,x1,x1` takes exactly 1 +extra cycle with forwarding on and 2 with it off (Phase 4 measured: the write-first regfile bypass saves the third). These numbers are the ones the Hazard Lab displays, so they get pinned. **Performance assertions:** fib(20) and bubble sort on 32 elements have pinned cycle counts per diff --git a/rtl/core_pipe.sv b/rtl/core_pipe.sv index e5fa15d..e8fad82 100644 --- a/rtl/core_pipe.sv +++ b/rtl/core_pipe.sv @@ -1,16 +1,17 @@ // 5-stage pipelined RV32I core: IF -> ID -> EX -> MEM -> WB. // -// Phase 3 scope: the datapath, branch/jump flush, faults and halt. There is NO forwarding and NO -// load-use stall yet (Phase 4), so correct results require producers to be >= 3 instructions -// ahead of consumers (the register file's write-first bypass covers WB -> ID). The programs in -// sim/golden/hazard_free.py are built that way. +// Hazards (Phase 4): forward_unit forwards EX/MEM and MEM/WB results into EX; hazard_unit stalls +// ID for the load-use hazard (loads and CSR reads have their result only in WB). The pin +// cfg_forward_en = 0 disables forwarding and makes hazard_unit stall on every RAW dependency until +// the producer reaches WB (the register file's write-first bypass delivers it). Exact cycle counts +// are specified by the independent model in sim/golden/pipemodel.py. // // Timing (docs/ARCHITECTURE.md): // IF pc_q addresses IMEM (synchronous read). The instruction word is the BRAM output register -// and is decoded in ID the next cycle, alongside id_pc. -// ID decode, register read (write-first bypass from WB), immediate. -// EX ALU, branch decision, jump target. Taken branches/jumps (predict not-taken) flush IF/ID -// and ID/EX: a 2-cycle penalty. DMEM is ADDRESSED here (synchronous read). +// and is decoded in ID the next cycle, alongside id_pc. A stall holds pc_q and IMEM's output. +// ID decode, register read (write-first bypass from WB), immediate. Stalls here. +// EX operand forwarding, ALU, branch decision, jump target. Taken branches/jumps (predict +// not-taken) flush IF/ID and ID/EX: a 2-cycle penalty. DMEM is ADDRESSED here. // MEM load data is back from DMEM; LSU extracts/extends. Stores, console and TOHOST write here. // WB register write, CSR read, retire (RVFI), halt/fault decision. // @@ -23,6 +24,7 @@ module core_pipe ( input logic clk, input logic rst_n, + input logic cfg_forward_en, // 1: forward + load-use stall; 0: stall on every RAW output logic imem_re, output logic [IMEM_AW-1:0] imem_raddr, @@ -66,7 +68,11 @@ module core_pipe output logic [5*32-1:0] dbg_stage_pc, output logic [5*32-1:0] dbg_stage_insn, output logic dbg_flush_ifid, - output logic dbg_flush_idex + output logic dbg_flush_idex, + output logic dbg_stall, + output logic dbg_load_use, + output logic [1:0] dbg_fwd_a, // 0 none, 1 EX/MEM, 2 MEM/WB + output logic [1:0] dbg_fwd_b ); // ====================================================================== state @@ -83,6 +89,7 @@ module core_pipe // ID/EX logic ex_valid, ex_illegal, ex_a_pc, ex_b_imm, ex_reg_we, ex_mem_re, ex_mem_we; logic ex_mem_unsigned, ex_is_branch, ex_is_jal, ex_is_jalr, ex_is_halt; + logic ex_uses_rs1, ex_uses_rs2; logic [31:0] ex_pc, ex_insn, ex_rs1_data, ex_rs2_data, ex_imm; alu_op_t ex_alu_op; wb_sel_t ex_wb_sel; @@ -104,7 +111,8 @@ module core_pipe // ====================================================================== IF logic if_fault; - assign imem_re = 1'b1; + logic stall; // from hazard_unit (declared with ID) + assign imem_re = !stall; // hold the instruction word in ID while stalled assign imem_raddr = pc_q[IMEM_AW+1:2]; assign if_fault = |pc_q[31:IMEM_AW+2]; // pc outside IMEM @@ -116,9 +124,7 @@ module core_pipe mem_size_t id_mem_size; logic id_a_pc, id_b_imm, id_reg_we, id_mem_re, id_mem_we, id_mem_unsigned; logic id_is_branch, id_is_jal, id_is_jalr, id_is_halt, id_illegal; - /* verilator lint_off UNUSEDSIGNAL */ - logic id_uses_rs1, id_uses_rs2; // consumed by the hazard unit in Phase 4 - /* verilator lint_on UNUSEDSIGNAL */ + logic id_uses_rs1, id_uses_rs2; assign id_insn = id_fetch_fault ? 32'b0 : imem_rdata; @@ -132,10 +138,8 @@ module core_pipe imm_gen u_imm_gen (.insn(id_insn), .sel(id_imm_sel), .imm(id_imm)); - // WB-stage write port (declared with WB below) - logic wr_en; + // WB-stage write port (wr_en and wb_wdata are declared with the forwarding logic in EX) logic [4:0] wb_rd; - logic [31:0] wb_wdata; regfile #(.BYPASS(1'b1)) u_regfile ( .clk(clk), .rst_n(rst_n), .raddr1(id_insn[19:15]), .raddr2(id_insn[24:20]), @@ -147,11 +151,49 @@ module core_pipe logic [31:0] ex_alu_a, ex_alu_b, ex_alu_y, ex_pc_plus4, ex_pc_target, ex_next_pc, ex_val; logic ex_br_taken, ex_redirect_raw, ex_target_misaligned, ex_redirect, ex_fault; - assign ex_alu_a = ex_a_pc ? ex_pc : ex_rs1_data; - assign ex_alu_b = ex_b_imm ? ex_imm : ex_rs2_data; + // ---- forwarding (EX/MEM and MEM/WB -> EX) and hazard control (ID stalls) + logic [1:0] fwd_a, fwd_b; + logic [31:0] ex_rs1_v, ex_rs2_v; // operands after forwarding + logic mm_wr, ex_wr, ex_late, load_use; + logic [31:0] wb_wdata; // value being written back (declared with WB) + logic wr_en; + + assign mm_wr = mm_valid && mm_reg_we && (mm_insn[11:7] != 5'd0); + assign ex_wr = ex_valid && ex_reg_we && (ex_insn[11:7] != 5'd0); + assign ex_late = ex_mem_re || (ex_wb_sel == WB_CSR); // result only exists in WB + + forward_unit u_fwd ( + .forward_en(cfg_forward_en), .ex_valid(ex_valid), .ex_uses_rs1(ex_uses_rs1), + .ex_uses_rs2(ex_uses_rs2), .ex_rs1(ex_insn[19:15]), .ex_rs2(ex_insn[24:20]), + .mm_wr(mm_wr), .mm_rd(mm_insn[11:7]), .wb_wr(wr_en), .wb_rd(wb_rd), + .fwd_a(fwd_a), .fwd_b(fwd_b) + ); + + always_comb begin + case (fwd_a) + 2'd1: ex_rs1_v = mm_val; + 2'd2: ex_rs1_v = wb_wdata; + default: ex_rs1_v = ex_rs1_data; + endcase + case (fwd_b) + 2'd1: ex_rs2_v = mm_val; + 2'd2: ex_rs2_v = wb_wdata; + default: ex_rs2_v = ex_rs2_data; + endcase + end + + hazard_unit u_hazard ( + .forward_en(cfg_forward_en), .redirect(ex_redirect), .id_valid(id_valid), + .id_uses_rs1(id_uses_rs1), .id_uses_rs2(id_uses_rs2), .id_rs1(id_insn[19:15]), + .id_rs2(id_insn[24:20]), .ex_wr(ex_wr), .ex_late(ex_late), .ex_rd(ex_insn[11:7]), + .mm_wr(mm_wr), .mm_rd(mm_insn[11:7]), .stall(stall), .load_use(load_use) + ); + + assign ex_alu_a = ex_a_pc ? ex_pc : ex_rs1_v; + assign ex_alu_b = ex_b_imm ? ex_imm : ex_rs2_v; alu u_alu (.a(ex_alu_a), .b(ex_alu_b), .op(ex_alu_op), .y(ex_alu_y)); - branch_unit u_branch (.a(ex_rs1_data), .b(ex_rs2_data), .funct3(ex_insn[14:12]), .taken(ex_br_taken)); + branch_unit u_branch (.a(ex_rs1_v), .b(ex_rs2_v), .funct3(ex_insn[14:12]), .taken(ex_br_taken)); assign ex_pc_plus4 = ex_pc + 32'd4; assign ex_pc_target = ex_pc + ex_imm; @@ -233,7 +275,7 @@ module core_pipe ex_alu_op <= ALU_ADD; ex_a_pc <= 1'b0; ex_b_imm <= 1'b0; ex_reg_we <= 1'b0; ex_wb_sel <= WB_ALU; ex_mem_re <= 1'b0; ex_mem_we <= 1'b0; ex_mem_size <= SZ_WORD; ex_mem_unsigned <= 1'b0; ex_is_branch <= 1'b0; ex_is_jal <= 1'b0; - ex_is_jalr <= 1'b0; ex_is_halt <= 1'b0; + ex_is_jalr <= 1'b0; ex_is_halt <= 1'b0; ex_uses_rs1 <= 1'b0; ex_uses_rs2 <= 1'b0; mm_valid <= 1'b0; mm_fault <= 1'b0; mm_reg_we <= 1'b0; mm_mem_re <= 1'b0; mm_mem_we <= 1'b0; mm_mem_unsigned <= 1'b0; mm_is_halt <= 1'b0; @@ -245,15 +287,21 @@ module core_pipe wb_val <= 32'b0; wb_next_pc <= 32'b0; wb_mem_addr <= 32'b0; wb_mem_rdata <= 32'b0; wb_mem_wdata <= 32'b0; wb_mem_rmask <= 4'b0; wb_mem_wmask <= 4'b0; wb_sel <= WB_ALU; end else begin - // ---- IF: always fetch; a taken redirect from EX replaces the pc and squashes the - // instruction being fetched - pc_q <= ex_redirect ? ex_next_pc : pc_q + 32'd4; - id_valid <= !ex_redirect; - id_pc <= pc_q; - id_fetch_fault <= if_fault; - - // ---- ID -> EX (squashed by a redirect) - ex_valid <= id_valid && !ex_redirect; + // ---- IF/ID: fetch every cycle unless stalled (hold pc and the IF/ID register). A taken + // redirect from EX replaces the pc and squashes the instruction being fetched; it + // always wins over a stall (hazard_unit never stalls during a redirect). + if (ex_redirect) begin + pc_q <= ex_next_pc; + id_valid <= 1'b0; + end else if (!stall) begin + pc_q <= pc_q + 32'd4; + id_valid <= 1'b1; + id_pc <= pc_q; + id_fetch_fault <= if_fault; + end + + // ---- ID -> EX: a redirect squashes the instruction in ID; a stall issues a bubble + ex_valid <= id_valid && !ex_redirect && !stall; ex_illegal <= id_illegal || id_fetch_fault; ex_pc <= id_pc; ex_insn <= id_insn; @@ -273,6 +321,8 @@ module core_pipe ex_is_jal <= id_is_jal; ex_is_jalr <= id_is_jalr; ex_is_halt <= id_is_halt; + ex_uses_rs1 <= id_uses_rs1; + ex_uses_rs2 <= id_uses_rs2; // ---- EX -> MEM mm_valid <= ex_valid; @@ -280,7 +330,7 @@ module core_pipe mm_pc <= ex_pc; mm_insn <= ex_insn; mm_val <= ex_val; - mm_rs2_data <= ex_rs2_data; + mm_rs2_data <= ex_rs2_v; mm_next_pc <= ex_next_pc; mm_reg_we <= ex_reg_we; mm_wb_sel <= ex_wb_sel; @@ -364,5 +414,9 @@ module core_pipe assign dbg_stage_insn = {wb_insn, mm_insn, ex_insn, id_insn, 32'b0}; assign dbg_flush_ifid = ex_redirect; assign dbg_flush_idex = ex_redirect; + assign dbg_stall = stall; + assign dbg_load_use = load_use; + assign dbg_fwd_a = fwd_a; + assign dbg_fwd_b = fwd_b; endmodule diff --git a/rtl/forward_unit.sv b/rtl/forward_unit.sv new file mode 100644 index 0000000..5e59ab4 --- /dev/null +++ b/rtl/forward_unit.sv @@ -0,0 +1,44 @@ +// Operand forwarding into EX. +// +// A consumer in EX takes its operand from the instruction one ahead (now in MEM; its EX/MEM result) +// or two ahead (now in WB; the value being written back, including load data and CSR reads) instead +// of the stale ID/EX copy. A producer three ahead is handled by the register file's write-first +// bypass. The nearer producer wins. Never forwards x0. With forward_en = 0 nothing is forwarded; +// the hazard unit then stalls so that the register file bypass is the only path. +// +// The MEM-stage source is only valid for results that exist at the end of EX (ALU, pc+4). Loads +// and CSR reads are "late": hazard_unit stalls their consumer one cycle so it arrives when they are +// in WB, and this unit then forwards from WB. +module forward_unit ( + input logic forward_en, + + input logic ex_valid, + input logic ex_uses_rs1, + input logic ex_uses_rs2, + input logic [4:0] ex_rs1, + input logic [4:0] ex_rs2, + + input logic mm_wr, // instruction in MEM writes a register + input logic [4:0] mm_rd, + input logic wb_wr, // instruction in WB writes a register (will retire) + input logic [4:0] wb_rd, + + output logic [1:0] fwd_a, // 0 = ID/EX value, 1 = EX/MEM, 2 = MEM/WB + output logic [1:0] fwd_b +); + + localparam logic [1:0] FWD_NONE = 2'd0; + localparam logic [1:0] FWD_EXMEM = 2'd1; + localparam logic [1:0] FWD_MEMWB = 2'd2; + + function automatic logic [1:0] pick(input logic uses, input logic [4:0] rs); + if (!forward_en || !ex_valid || !uses || rs == 5'd0) return FWD_NONE; + if (mm_wr && mm_rd == rs) return FWD_EXMEM; + if (wb_wr && wb_rd == rs) return FWD_MEMWB; + return FWD_NONE; + endfunction + + assign fwd_a = pick(ex_uses_rs1, ex_rs1); + assign fwd_b = pick(ex_uses_rs2, ex_rs2); + +endmodule diff --git a/rtl/hazard_unit.sv b/rtl/hazard_unit.sv new file mode 100644 index 0000000..e3cd526 --- /dev/null +++ b/rtl/hazard_unit.sv @@ -0,0 +1,43 @@ +// Data-hazard stall control for the instruction in ID. +// +// * forward_en = 1: the only stall is the load-use hazard - the instruction in EX is a load (or a +// CSR read) whose result the instruction in ID needs. One bubble; the consumer then reaches EX +// as the producer is in WB and takes it from there. +// * forward_en = 0: nothing is forwarded, so ID waits until every producer it depends on has +// left EX and MEM. A producer in WB is read through the register file's write-first bypass. +// This costs 2 stalls for a distance-1 dependency, 1 for distance 2, 0 for distance 3+. +// +// A redirect from EX squashes ID, so a stall for a wrong-path instruction is suppressed. +module hazard_unit ( + input logic forward_en, + input logic redirect, + + input logic id_valid, + input logic id_uses_rs1, + input logic id_uses_rs2, + input logic [4:0] id_rs1, + input logic [4:0] id_rs2, + + input logic ex_wr, // instruction in EX writes a register (rd != x0) + input logic ex_late, // ... and its result is only available in WB (load / CSR read) + input logic [4:0] ex_rd, + input logic mm_wr, + input logic [4:0] mm_rd, + + output logic stall, + output logic load_use +); + + logic ex_hit, mm_hit; + + function automatic logic needs(input logic uses, input logic [4:0] rs, input logic [4:0] rd); + return uses && (rs != 5'd0) && (rs == rd); + endfunction + + assign ex_hit = ex_wr && (needs(id_uses_rs1, id_rs1, ex_rd) || needs(id_uses_rs2, id_rs2, ex_rd)); + assign mm_hit = mm_wr && (needs(id_uses_rs1, id_rs1, mm_rd) || needs(id_uses_rs2, id_rs2, mm_rd)); + + assign load_use = id_valid && !redirect && ex_hit && ex_late; + assign stall = id_valid && !redirect && (forward_en ? (ex_hit && ex_late) : (ex_hit || mm_hit)); + +endmodule diff --git a/rtl/tiny_cpu_top.sv b/rtl/tiny_cpu_top.sv index 4426a3f..019e9d6 100644 --- a/rtl/tiny_cpu_top.sv +++ b/rtl/tiny_cpu_top.sv @@ -14,6 +14,7 @@ module tiny_cpu_top ) ( input logic clk, input logic rst_n, + input logic cfg_forward_en, // PIPE: 1 = forwarding on; 0 = stall on every RAW hazard input logic load_en, input logic load_sel, @@ -49,8 +50,7 @@ module tiny_cpu_top output logic [31:0] dbg_instret, // Per-stage view for the visualizer: stage index 0=IF 1=ID 2=EX 3=MEM 4=WB. "SINGLE" only - // ever populates IF. Hazard flags: Phase 3 has flushes only; stall/load-use/forwarding arrive - // in Phase 4 and read 0 until then. + // ever populates IF and reports no hazards. dbg_fwd_a/b: 0 none, 1 EX/MEM, 2 MEM/WB. output logic [4:0] dbg_stage_valid, output logic [5*32-1:0] dbg_stage_pc, output logic [5*32-1:0] dbg_stage_insn, @@ -81,8 +81,17 @@ module tiny_cpu_top .dbg_stage_valid, .dbg_stage_pc, .dbg_stage_insn ); + // The single-cycle core has no hazards and ignores cfg_forward_en. + /* verilator lint_off UNUSEDSIGNAL */ + logic unused_cfg; + assign unused_cfg = cfg_forward_en; + /* verilator lint_on UNUSEDSIGNAL */ assign dbg_flush_ifid = 1'b0; assign dbg_flush_idex = 1'b0; + assign dbg_stall = 1'b0; + assign dbg_load_use = 1'b0; + assign dbg_fwd_a = 2'b00; + assign dbg_fwd_b = 2'b00; mem_comb #(.WORDS(IMEM_WORDS)) u_imem ( .clk, .raddr(imem_raddr), .rdata(imem_rdata), @@ -104,7 +113,7 @@ module tiny_cpu_top logic [3:0] dmem_wmask; core_pipe u_core ( - .clk, .rst_n, + .clk, .rst_n, .cfg_forward_en, .imem_re, .imem_raddr, .imem_rdata, .dmem_raddr, .dmem_rdata, .dmem_wmask, .dmem_waddr, .dmem_wdata, .rvfi_valid, .rvfi_order, .rvfi_pc, .rvfi_insn, .rvfi_rd_addr, .rvfi_rd_wdata, @@ -112,7 +121,8 @@ module tiny_cpu_top .rvfi_mem_wdata, .rvfi_misaligned, .rvfi_halt, .rvfi_cycle_dep, .console_valid, .console_data, .tohost_valid, .tohost_data, .halted, .fault, .dbg_pc, .dbg_regs, .dbg_cycle, .dbg_instret, - .dbg_stage_valid, .dbg_stage_pc, .dbg_stage_insn, .dbg_flush_ifid, .dbg_flush_idex + .dbg_stage_valid, .dbg_stage_pc, .dbg_stage_insn, .dbg_flush_ifid, .dbg_flush_idex, + .dbg_stall, .dbg_load_use, .dbg_fwd_a, .dbg_fwd_b ); // IMEM is read-only to the core. DMEM read-new: a load in EX sees the store ahead of it in MEM. @@ -131,10 +141,4 @@ module tiny_cpu_top $error("tiny_cpu_top: unknown CORE=\"%s\" (use \"SINGLE\" or \"PIPE\")", CORE); end - // Phase 4 drives these; until then no core stalls, forwards, or detects load-use. - assign dbg_stall = 1'b0; - assign dbg_load_use = 1'b0; - assign dbg_fwd_a = 2'b00; - assign dbg_fwd_b = 2'b00; - endmodule diff --git a/scripts/lint.sh b/scripts/lint.sh index 8f269de..80703e3 100644 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,7 +5,7 @@ set -uo pipefail cd "$(dirname "$0")/.." PKG=rtl/tiny_cpu_pkg.sv -LEAVES="alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb mem_sync" +LEAVES="alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb mem_sync forward_unit hazard_unit" ALL="$PKG"; for m in $LEAVES core_single core_pipe tiny_cpu_top; do ALL+=" rtl/$m.sv"; done status=0 diff --git a/sim/golden/hazard_cases.py b/sim/golden/hazard_cases.py new file mode 100644 index 0000000..e9055a4 --- /dev/null +++ b/sim/golden/hazard_cases.py @@ -0,0 +1,37 @@ +"""Hand-derived hazard cases shared by the ISS-only model tests and the RTL tests. + +Each case is (instructions, extra cycles with forwarding on, extra cycles with forwarding off), where +"extra" means cycles beyond the ideal N + 4 (N instructions + pipeline fill). The numbers were derived +on paper from the stage diagrams in docs/ARCHITECTURE.md, not from the RTL. SETUP puts the base +register 3+ instructions ahead of the loads so the only hazard in each case is the one under test. +""" + +from golden.decode import encode as e + +DMEM = (0x10008).to_bytes(4, "little") + bytes(60) # word 0 holds a valid pointer into DMEM + + +ADD = e("addi", 1, 0, imm=5) +NOP = e("addi", 9, 0, imm=0) +SETUP = [e("lui", 3, imm=0x10000), NOP, NOP, NOP] +CASES = { + "independent": ([ADD, e("addi", 2, 0, imm=6)], 0, 0), + "alu->alu dist 1": ([ADD, e("addi", 2, 1, imm=1)], 0, 2), + "alu->alu dist 2": ([ADD, NOP, e("addi", 2, 1, imm=1)], 0, 1), + "alu->alu dist 3": ([ADD, NOP, NOP, e("addi", 2, 1, imm=1)], 0, 0), + "load->use dist 1": (SETUP + [e("lw", 1, 3, imm=0), e("addi", 2, 1, imm=1)], 1, 2), + "load->use dist 2": (SETUP + [e("lw", 1, 3, imm=0), NOP, e("addi", 2, 1, imm=1)], 0, 1), + "load->use dist 3": (SETUP + [e("lw", 1, 3, imm=0), NOP, NOP, e("addi", 2, 1, imm=1)], 0, 0), + "load->store-data dist 1": (SETUP + [e("lw", 1, 3, imm=0), e("sw", 0, 3, 1, imm=4)], 1, 2), + "load->store-address dist 1": (SETUP + [e("lw", 1, 3, imm=0), e("sw", 0, 1, 3, imm=4)], 1, 2), + "csr->use dist 1": ([e("csrrs", 1, 0, csr=0xC02), e("addi", 2, 1, imm=1)], 1, 2), + "jal link->use dist 1": ([e("jal", 1, imm=4), e("addi", 2, 1, imm=1)], 2, 2), # flush gap subsumes the stall + "both operands late": (SETUP + [e("lw", 1, 3, imm=0), e("lw", 2, 3, imm=4), e("add", 4, 1, 2)], 1, 2), + "taken jal": ([e("jal", 0, imm=8), e("addi", 7, 0, imm=1), e("addi", 8, 0, imm=2)], 2, 2), + "taken branch": ([ADD, NOP, NOP, e("beq", 0, 1, 1, imm=8), e("addi", 7, 0, imm=1), e("addi", 8, 0, imm=2)], 2, 2), + "taken branch to pc+4": ([ADD, NOP, NOP, e("beq", 0, 1, 1, imm=4), e("addi", 7, 0, imm=1)], 2, 2), + "not-taken branch": ([ADD, NOP, NOP, e("bne", 0, 1, 1, imm=8)], 0, 0), + "writes x0 never hazard": ([e("addi", 0, 0, imm=5), e("addi", 2, 0, imm=1)], 0, 0), +} + + diff --git a/sim/golden/hazard_random.py b/sim/golden/hazard_random.py new file mode 100644 index 0000000..ffc2d18 --- /dev/null +++ b/sim/golden/hazard_random.py @@ -0,0 +1,64 @@ +"""Hazard-heavy random programs for Phase 4: dense code (pad=0), few registers so nearly every +instruction depends on a recent one, loads feeding uses, store->load on the same word, forward branches, +jumps, indirect jumps and instret reads. Always terminates (branches only go forward).""" + +from __future__ import annotations + +import random + +from golden.hazard_free import BRANCH_OPS, I_OPS, R_OPS, SH_OPS +from golden.progbuild import Prog + +POOL = [1, 2, 3, 4, 5] # small register pool => constant dependencies +BASE = 8 # holds the DMEM base address; never written after setup + + +def random_hazard_program(seed: int, n: int = 70) -> Prog: + rng = random.Random(seed) + p = Prog(pad=0) + buf = p.space(128) + p.li(BASE, buf) + for r in POOL: + p.li(r, rng.getrandbits(32)) + p.nop(3) + pending: list[tuple[int, str]] = [] # (emit label before instruction #, name) + for i in range(n): + for lab in [x for x in pending if x[0] <= i]: + p.label(lab[1]) + pending.remove(lab) + rd, a, b = (rng.choice(POOL) for _ in range(3)) + kind = rng.choices( + ["r", "i", "sh", "lui", "load", "store", "branch", "jal", "jalr", "csr"], + [6, 5, 2, 1, 5, 4, 3, 1, 1, 1], + )[0] + if kind == "r": + p.op(rng.choice(R_OPS), rd, a, b) + elif kind == "i": + p.op(rng.choice(I_OPS), rd, a, rng.randint(-2048, 2047)) + elif kind == "sh": + p.op(rng.choice(SH_OPS), rd, a, rng.randrange(32)) + elif kind == "lui": + p.op(rng.choice(["lui", "auipc"]), rd, rng.getrandbits(20)) + elif kind == "load": + p.op(rng.choice(["lb", "lbu", "lh", "lhu", "lw"]), rd, BASE, rng.randrange(0, 124)) + elif kind == "store": + p.op(rng.choice(["sb", "sh", "sw"]), a, BASE, rng.randrange(0, 124)) + elif kind == "branch": + name = f"B{i}" + pending.append((i + rng.randint(2, 4), name)) # >= 2 ahead: a taken branch is never +4 + p.op(rng.choice(BRANCH_OPS), a, b, name) + elif kind == "jal": + name = f"J{i}" + pending.append((i + rng.randint(2, 4), name)) + p.jal(rng.choice([0, rd]), name) + elif kind == "jalr": + name = f"R{i}" + pending.append((i + rng.randint(3, 5), name)) + p.li_label(6, name, rng.choice([0, 1])) # x6 is the jump register: odd targets exercise bit 0 + p.jalr(rng.choice([0, rd]), 6, 0) + else: + p.csrrs(rd, 0xC02) # instret: exact in the ISS, so safe to consume + for _, name in pending: + p.label(name) + p.nop(3) + return p.exit() diff --git a/sim/golden/iss.py b/sim/golden/iss.py index 71f134a..57114c2 100644 --- a/sim/golden/iss.py +++ b/sim/golden/iss.py @@ -15,6 +15,8 @@ * Misaligned loads/stores are NOT supported: the address is forced into alignment and the retire record carries misaligned=True. Misaligned jump/branch targets raise. * ecall / ebreak / a TOHOST write halt the core; the halting instruction still retires. + * `Retire.taken` is informational (not compared in lockstep): it lets the pipeline timing model + see a taken branch whose target happens to be pc+4, which pc_wdata alone cannot reveal. * Cycle-dependent values (CYCLE MMIO, rdcycle/rdtime) return the retired-instruction count here, which is NOT what a pipelined core returns. The lockstep harness masks the destination value of those instructions (see `Retire.cycle_dependent`). @@ -93,6 +95,7 @@ class Retire: halt: bool = False halt_reason: str = "" # "", "ecall", "ebreak", "tohost"; informational, not compared cycle_dependent: bool = False # rd_wdata/mem_rdata depend on timing; lockstep must mask them + taken: bool = False # a taken branch or any jump: the pipeline flushes (even when the target is pc+4) def asdict(self) -> dict: return asdict(self) @@ -238,8 +241,10 @@ def _execute(self, x: Decoded, pc: int) -> Retire: rd_val = (pc + x.imm) & M32 elif n == "jal": rd_val, nxt = nxt, (pc + x.imm) & M32 + ret.taken = True elif n == "jalr": rd_val, nxt = nxt, ((a + x.imm) & M32) & ~1 + ret.taken = True elif n in BRANCHES: taken = { "beq": a == b, @@ -251,6 +256,7 @@ def _execute(self, x: Decoded, pc: int) -> Retire: }[n] if taken: nxt = (pc + x.imm) & M32 + ret.taken = True elif n in LOADS: val, ret.mem_addr, ret.mem_rmask, ret.mem_rdata, ret.misaligned = self._load( n, (a + x.imm) & M32, pc, x.insn diff --git a/sim/golden/perf.py b/sim/golden/perf.py new file mode 100644 index 0000000..a6782e9 --- /dev/null +++ b/sim/golden/perf.py @@ -0,0 +1,74 @@ +"""Pinned performance baseline: instructions, and cycles per core/mode, for the example programs. + + python -m golden.perf # print the table + python -m golden.perf --update # rewrite sim/perf_baseline.json (do this deliberately) + python -m golden.perf --check # exit 1 if the baseline is stale (pytest does this too) + +Cycles come from the ISS retire trace and the independent pipeline model. The RTL lockstep suites +assert RTL cycles == model for every one of these programs, so the baseline is pinned against hardware. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +from golden.hazard_free import PROGRAMS +from golden.iss import Iss +from golden.pipemodel import pipe_cycles + +ROOT = Path(__file__).resolve().parents[2] +BASELINE = ROOT / "sim" / "perf_baseline.json" +SW_PROGRAMS = ["hello", "fib", "sort", "memcpy", "primes"] + + +def sources() -> dict[str, tuple[bytes, bytes]]: + out = {} + for name in SW_PROGRAMS: + stem = ROOT / "sw" / "build" / "programs" / name + if stem.with_suffix(".imem.bin").exists(): + out[name] = (Path(str(stem) + ".imem.bin").read_bytes(), Path(str(stem) + ".dmem.bin").read_bytes()) + for name, build in PROGRAMS.items(): + out[f"hazard_free/{name}"] = build().build() + return out + + +def measure(imem: bytes, dmem: bytes) -> dict[str, int]: + iss = Iss(imem, dmem) + trace = iss.run(2_000_000) + n = len(trace) + return {"instret": n, "single": n, "pipe_fwd": pipe_cycles(trace, True), "pipe_nofwd": pipe_cycles(trace, False)} + + +def measure_all() -> dict[str, dict[str, int]]: + return {name: measure(*img) for name, img in sorted(sources().items())} + + +def table(data) -> str: + head = f"{'program':<28}{'instrs':>8}{'single':>8}{'pipe+fwd':>10}{'CPI':>7}{'pipe-fwd':>10}{'CPI':>7}" + rows = [head, "-" * len(head)] + for name, d in data.items(): + rows.append(f"{name:<28}{d['instret']:>8}{d['single']:>8}{d['pipe_fwd']:>10}" + f"{d['pipe_fwd'] / d['instret']:>7.2f}{d['pipe_nofwd']:>10}{d['pipe_nofwd'] / d['instret']:>7.2f}") + return "\n".join(rows) + + +def main(argv: list[str]) -> int: + data = measure_all() + if "--update" in argv: + BASELINE.write_text(json.dumps(data, indent=2) + "\n") + print(f"wrote {BASELINE}") + elif "--check" in argv: + pinned = json.loads(BASELINE.read_text()) + missing = [k for k in data if k not in pinned] + changed = {k: (pinned[k], data[k]) for k in data if k in pinned and pinned[k] != data[k]} + if missing or changed: + print(f"perf baseline is stale: missing={missing} changed={changed}", file=sys.stderr) + return 1 + print(table(data)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/sim/golden/pipemodel.py b/sim/golden/pipemodel.py new file mode 100644 index 0000000..e0fc28a --- /dev/null +++ b/sim/golden/pipemodel.py @@ -0,0 +1,76 @@ +"""Independent timing model of the 5-stage pipeline, computed from an ISS retire trace. + +This is the oracle for the RTL's cycle counts (Phase 4). It knows nothing about the RTL - only the +pipeline's rules - so agreement is real evidence and not the RTL checking itself. + +Let T(i) be the cycle in which instruction i is in EX (IF is T-2, ID is T-1, MEM is T+1, WB is T+2), +counting the first cycle after reset release as cycle 1; instruction 0 is in EX in cycle 3. + + * in order, one per cycle: T(i) >= T(i-1) + 1 + * after a taken branch/jump (flush): T(i) >= T(branch) + 3 (the 2-cycle penalty) + * RAW hazard on source register s whose latest writer is p, with forwarding ON: + loads and CSR reads (result only exists in WB): T(i) >= T(p) + 2 (the load-use stall) + everything else (forwarded from EX/MEM): T(i) >= T(p) + 1 (no stall) + * RAW hazard with forwarding OFF: T(i) >= T(p) + 3 - the consumer waits in ID until the producer + is in WB, where the register file's write-first bypass delivers the value. + +The halting instruction retires after WB, so the run takes T(last) + 2 cycles. +""" + +from __future__ import annotations + +from golden.decode import LOADS, decode +from golden.progbuild import reads + +EX_OF_FIRST = 3 # IF in cycle 1, ID in cycle 2, EX in cycle 3 +FLUSH_GAP = 3 # a redirecting instruction in EX at T lets its target reach EX at T + 3 +WB_AFTER_EX = 2 + + +def is_redirect(r) -> bool: + """Taken branch or any jump: the pipeline flushes IF/ID for each, even a taken branch to pc+4.""" + return r.taken + + +def is_late(insn: int) -> bool: + """Result is only available in WB: loads (data arrives in MEM, not forwarded) and CSR reads.""" + x = decode(insn) + return x.name in LOADS or x.name == "csrrs" + + +def ex_cycles(trace, forward: bool = True) -> list[int]: + """T(i) for every instruction in the trace.""" + T: list[int] = [] + last_writer: dict[int, int] = {} + for i, r in enumerate(trace): + t = EX_OF_FIRST if i == 0 else T[i - 1] + 1 + if i and is_redirect(trace[i - 1]): + t = max(t, T[i - 1] + FLUSH_GAP) + for s in reads(r.insn): + p = last_writer.get(s) + if p is None: + continue + if not forward: + need = T[p] + 3 + else: + need = T[p] + 2 if is_late(trace[p].insn) else T[p] + 1 + t = max(t, need) + T.append(t) + if r.rd_addr: + last_writer[r.rd_addr] = i + return T + + +def pipe_cycles(trace, forward: bool = True) -> int: + """Cycles from reset release until the last instruction's retire is visible.""" + return ex_cycles(trace, forward)[-1] + WB_AFTER_EX if trace else 0 + + +def stalls(trace, forward: bool = True) -> int: + """Cycles lost to data-hazard stalls (excludes the flush penalty and pipeline fill).""" + T = ex_cycles(trace, forward) + lost = 0 + for i in range(1, len(T)): + floor = T[i - 1] + (FLUSH_GAP if is_redirect(trace[i - 1]) else 1) + lost += T[i] - floor + return lost diff --git a/sim/golden/test_iss.py b/sim/golden/test_iss.py index 0edeb1b..92970d0 100644 --- a/sim/golden/test_iss.py +++ b/sim/golden/test_iss.py @@ -294,3 +294,12 @@ def test_retire_order_and_pc_chain(): assert [r.order for r in trace] == list(range(len(trace))) for a, b in zip(trace, trace[1:]): assert a.pc_wdata == b.pc + + +def test_taken_flag_marks_every_redirect_including_a_branch_to_the_next_instruction(): + _, trace = run(e("beq", 0, 0, 0, imm=4), # taken, but the target is pc+4 + e("bne", 0, 0, 0, imm=8), # not taken + e("jal", 0, imm=4), e("jalr", 0, 0, imm=0x10), e("addi", 1, 0, imm=1)) + assert [r.taken for r in trace[:4]] == [True, False, True, True] + assert not any(r.taken for r in trace[4:]) + assert trace[0].pc_wdata == trace[0].pc + 4 # which is exactly why pc_wdata alone is not enough diff --git a/sim/golden/test_perf_baseline.py b/sim/golden/test_perf_baseline.py new file mode 100644 index 0000000..f2707e1 --- /dev/null +++ b/sim/golden/test_perf_baseline.py @@ -0,0 +1,32 @@ +"""The pinned cycle counts must not drift unnoticed (change them deliberately: python -m golden.perf --update).""" + +import json +import os + +import pytest + +from golden.perf import BASELINE, SW_PROGRAMS, measure_all + + +def test_baseline_matches_measurement(): + pinned = json.loads(BASELINE.read_text()) + got = measure_all() + if not all(name in got for name in SW_PROGRAMS): + if os.environ.get("REQUIRE_RISCV_TESTS") == "1": + pytest.fail("sw programs not built (make -C sw programs)") + pytest.skip("sw programs not built; only checking hazard_free entries") + assert got == pinned + + +def test_baseline_entries_that_do_not_need_a_toolchain(): + pinned = json.loads(BASELINE.read_text()) + for name, d in measure_all().items(): + if name.startswith("hazard_free/"): + assert pinned[name] == d, name + + +def test_forwarding_never_hurts_and_pipeline_beats_nothing_it_cannot(): + for name, d in json.loads(BASELINE.read_text()).items(): + assert d["pipe_fwd"] <= d["pipe_nofwd"], name # forwarding only removes stalls + assert d["pipe_fwd"] >= d["instret"] + 4, name # never faster than one per cycle + fill + assert d["single"] == d["instret"], name # single-cycle CPI is exactly 1 diff --git a/sim/golden/test_pipemodel.py b/sim/golden/test_pipemodel.py new file mode 100644 index 0000000..bd30d7f --- /dev/null +++ b/sim/golden/test_pipemodel.py @@ -0,0 +1,72 @@ +"""The timing model, checked by hand-derived cases (no RTL) and against the hazard-free baseline.""" + +import pytest + +from golden.decode import encode as e +from golden.hazard_cases import ADD, CASES, DMEM, NOP, SETUP # noqa: F401 +from golden.hazard_free import PROGRAMS, random_program +from golden.hazard_random import random_hazard_program +from golden.iss import Iss +from golden.pipemodel import ex_cycles, is_late, pipe_cycles, stalls +from golden.progbuild import Prog, raw_hazards + + +def trace_of(*insns): + iss = Iss(b"".join(w.to_bytes(4, "little") for w in (*insns, e("ecall"))), DMEM) + return iss.run(1000) + + +def extra(trace, forward): + """Cycles beyond the ideal N + 4.""" + return pipe_cycles(trace, forward) - (len(trace) + 4) + + +@pytest.mark.parametrize("name", CASES) +def test_hand_derived_cycle_counts(name): + insns, with_fwd, without = CASES[name] + t = trace_of(*insns) + assert extra(t, True) == with_fwd, "forwarding on" + assert extra(t, False) == without, "forwarding off" + + +def test_taken_branch_is_a_flush_not_a_stall(): + t = trace_of(e("jal", 0, imm=8), e("addi", 7, 0, imm=1), e("addi", 8, 0, imm=2)) + assert stalls(t, True) == 0 and stalls(t, False) == 0 and extra(t, True) == 2 + + +def test_late_results_are_loads_and_csr_reads_only(): + assert is_late(e("lw", 1, 2, imm=0)) and is_late(e("lbu", 1, 2, imm=0)) and is_late(e("csrrs", 1, 0, csr=0xC00)) + assert not is_late(e("addi", 1, 2, imm=0)) and not is_late(e("jal", 1, imm=4)) and not is_late(e("sw", 0, 1, 2)) + + +def test_first_instruction_is_in_ex_on_cycle_3(): + assert ex_cycles(trace_of(e("addi", 1, 0, imm=1)))[0] == 3 + + +@pytest.mark.parametrize("name", sorted(PROGRAMS)) +def test_hazard_free_programs_have_no_data_stalls_either_way(name): + iss = Iss(*PROGRAMS[name]().build()) + t = iss.run(100_000) + if name == "mem_raw_adjacent": # hand-scheduled: also free of register hazards + pass + assert raw_hazards(t) == [] and stalls(t, True) == 0 and stalls(t, False) == 0 + + +@pytest.mark.parametrize("seed", range(20)) +def test_hazard_random_programs_terminate_and_have_real_hazards(seed): + iss = Iss(*random_hazard_program(seed).build()) + t = iss.run(100_000) + assert iss.passed + assert stalls(t, False) >= stalls(t, True) >= 0 + assert len(t) > 40 + + +def test_hazard_random_programs_actually_stress_the_pipeline(): + total_fwd = total_nofwd = hazards = 0 + for seed in range(40): + t = Iss(*random_hazard_program(seed).build()) + tr = t.run(100_000) + total_fwd += stalls(tr, True) + total_nofwd += stalls(tr, False) + hazards += len(raw_hazards(tr)) + assert hazards > 500 and total_fwd > 40 and total_nofwd > 5 * total_fwd diff --git a/sim/lockstep_lib.py b/sim/lockstep_lib.py index f62ceaa..a6d15e5 100644 --- a/sim/lockstep_lib.py +++ b/sim/lockstep_lib.py @@ -14,13 +14,11 @@ from cocotb.clock import Clock from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge, Timer -from golden.decode import BRANCHES, decode +from golden.pipemodel import pipe_cycles from golden.decode import encode as e # noqa: F401 (re-exported for tests) from golden.iss import IMEM_SIZE, DMEM_SIZE, Iss, IssError CORE = os.environ.get("TINYCPU_CORE", "SINGLE") -PIPE_FILL = 4 # cycles from reset release to the first retire is 5; N instructions take N + 4 -FLUSH_PENALTY = 2 # a taken branch/jump squashes IF and ID ROOT = Path(__file__).resolve().parent.parent BUILD = ROOT / "sw" / "build" @@ -68,10 +66,11 @@ def ensure_clock(dut): _clock = cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start()) -async def reset_and_load(dut, imem: bytes, dmem: bytes): +async def reset_and_load(dut, imem: bytes, dmem: bytes, forward: int = 1): ensure_clock(dut) await Timer(1, "ns") # a previous lockstep() ends in the ReadOnly phase, where writes are illegal dut.rst_n.value, dut.load_en.value = 0, 0 + dut.cfg_forward_en.value = forward dut.load_sel.value, dut.load_addr.value, dut.load_data.value = 0, 0, 0 await FallingEdge(dut.clk) for sel, data, count in ((0, imem, IMEM_SIZE // 4), (1, dmem, DMEM_SIZE // 4)): @@ -83,22 +82,12 @@ async def reset_and_load(dut, imem: bytes, dmem: bytes): dut.rst_n.value = 1 # first rising edge after this executes the instruction at pc 0 -def redirects(history) -> int: - """Taken branches and jumps in a retire history (each flushes the pipeline once).""" - n = 0 - for r in history: - x = decode(r.insn) - if x.name in ("jal", "jalr") or (x.name in BRANCHES and r.pc_wdata != (r.pc + 4) & 0xFFFFFFFF): - n += 1 - return n - - -def expected_cycles(instret: int, history) -> int: - """Cycles from reset release until the halting retire is visible. SINGLE: one per instruction. - PIPE (Phase 3, no stalls): pipeline fill + one per instruction + a flush penalty per redirect.""" +def expected_cycles(instret: int, history, forward: bool = True) -> int: + """Cycles from reset release until the halting retire is visible. + SINGLE: one per instruction. PIPE: the independent timing model (golden/pipemodel.py).""" if CORE == "SINGLE": return instret - return instret + PIPE_FILL + FLUSH_PENALTY * redirects(history) + return pipe_cycles(history, forward) def explain(history, msg): @@ -106,9 +95,9 @@ def explain(history, msg): return f"{msg}\n last ISS retires:\n{tail}" -async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | None = None): +async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | None = None, forward: int = 1): """Run RTL and ISS together to halt (or fault). Returns (iss, rtl_console, cycles).""" - await reset_and_load(dut, imem, dmem) + await reset_and_load(dut, imem, dmem, forward) iss = Iss(imem, dmem) history, console, cycles, faulted = [], bytearray(), 0, False @@ -179,8 +168,9 @@ async def lockstep(dut, imem: bytes, dmem: bytes = b"", expect_fault: bool | Non assert int(dut.dbg_pc.value) == iss.pc assert iss.instret == int(dut.dbg_instret.value), ( f"instret: ISS={iss.instret} RTL={int(dut.dbg_instret.value)}") - want = expected_cycles(iss.instret, history) - assert cycles == want, f"{CORE} cycle count: RTL={cycles} expected={want} (instret={iss.instret})" + want = expected_cycles(iss.instret, history, bool(forward)) + assert cycles == want, ( + f"{CORE} forward={forward} cycle count: RTL={cycles} model={want} (instret={iss.instret})") return iss, bytes(console), cycles diff --git a/sim/perf_baseline.json b/sim/perf_baseline.json new file mode 100644 index 0000000..11b1b67 --- /dev/null +++ b/sim/perf_baseline.json @@ -0,0 +1,86 @@ +{ + "fib": { + "instret": 3190, + "single": 3190, + "pipe_fwd": 4758, + "pipe_nofwd": 4824 + }, + "hazard_free/alu_zoo": { + "instret": 265, + "single": 265, + "pipe_fwd": 269, + "pipe_nofwd": 269 + }, + "hazard_free/branches": { + "instret": 229, + "single": 229, + "pipe_fwd": 263, + "pipe_nofwd": 263 + }, + "hazard_free/bubble_sort": { + "instret": 749, + "single": 749, + "pipe_fwd": 863, + "pipe_nofwd": 863 + }, + "hazard_free/calls": { + "instret": 75, + "single": 75, + "pipe_fwd": 97, + "pipe_nofwd": 97 + }, + "hazard_free/console_and_csr": { + "instret": 214, + "single": 214, + "pipe_fwd": 218, + "pipe_nofwd": 218 + }, + "hazard_free/fib": { + "instret": 361, + "single": 361, + "pipe_fwd": 407, + "pipe_nofwd": 407 + }, + "hazard_free/mem_raw_adjacent": { + "instret": 62, + "single": 62, + "pipe_fwd": 66, + "pipe_nofwd": 66 + }, + "hazard_free/mem_zoo": { + "instret": 316, + "single": 316, + "pipe_fwd": 320, + "pipe_nofwd": 320 + }, + "hazard_free/memcpy_sum": { + "instret": 1164, + "single": 1164, + "pipe_fwd": 1292, + "pipe_nofwd": 1292 + }, + "hello": { + "instret": 91, + "single": 91, + "pipe_fwd": 150, + "pipe_nofwd": 171 + }, + "memcpy": { + "instret": 552, + "single": 552, + "pipe_fwd": 816, + "pipe_nofwd": 1042 + }, + "primes": { + "instret": 1963, + "single": 1963, + "pipe_fwd": 2836, + "pipe_nofwd": 3652 + }, + "sort": { + "instret": 1216, + "single": 1216, + "pipe_fwd": 1744, + "pipe_nofwd": 2117 + } +} diff --git a/sim/run.sh b/sim/run.sh index aa4cfc8..07fb13b 100644 --- a/sim/run.sh +++ b/sim/run.sh @@ -33,19 +33,24 @@ unit() { SIM_TAG=_nobypass EXTRA_COMPILE_ARGS="-GBYPASS=0" run test_regfile_nobypass regfile regfile.sv run test_csr_counters csr_counters csr_counters.sv run test_mmio mmio mmio.sv + run test_forward_unit forward_unit forward_unit.sv + run test_hazard_unit hazard_unit hazard_unit.sv run test_mem_sync mem_sync mem_sync.sv SIM_TAG=_oldread EXTRA_COMPILE_ARGS="-GREAD_NEW=0" MEM_READ_NEW=0 run test_mem_sync mem_sync mem_sync.sv } lockstep() { run test_core_lockstep tiny_cpu_top alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv \ - csr_counters.sv mmio.sv mem_comb.sv mem_sync.sv core_single.sv core_pipe.sv tiny_cpu_top.sv + csr_counters.sv mmio.sv mem_comb.sv mem_sync.sv forward_unit.sv hazard_unit.sv core_single.sv core_pipe.sv tiny_cpu_top.sv } pipe() { TINYCPU_CORE=PIPE EXTRA_COMPILE_ARGS="-G'CORE=\"PIPE\"'" SIM_TAG=_pipe run test_pipe_lockstep tiny_cpu_top \ alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv csr_counters.sv mmio.sv mem_comb.sv \ - mem_sync.sv core_single.sv core_pipe.sv tiny_cpu_top.sv + mem_sync.sv forward_unit.sv hazard_unit.sv core_single.sv core_pipe.sv tiny_cpu_top.sv + TINYCPU_CORE=PIPE EXTRA_COMPILE_ARGS="-G'CORE=\"PIPE\"'" SIM_TAG=_pipe run test_pipe_hazards tiny_cpu_top \ + alu.sv imm_gen.sv branch_unit.sv lsu.sv decoder.sv regfile.sv csr_counters.sv mmio.sv mem_comb.sv \ + mem_sync.sv forward_unit.sv hazard_unit.sv core_single.sv core_pipe.sv tiny_cpu_top.sv } case "${1:-all}" in diff --git a/sim/test_forward_unit.py b/sim/test_forward_unit.py new file mode 100644 index 0000000..0c15378 --- /dev/null +++ b/sim/test_forward_unit.py @@ -0,0 +1,49 @@ +import itertools + +import cocotb +from cocotb.triggers import Timer + +NONE, EXMEM, MEMWB = 0, 1, 2 + + +def want(en, ex_valid, uses, rs, mm_wr, mm_rd, wb_wr, wb_rd): + if not en or not ex_valid or not uses or rs == 0: + return NONE + if mm_wr and mm_rd == rs: + return EXMEM + if wb_wr and wb_rd == rs: + return MEMWB + return NONE + + +@cocotb.test() +async def exhaustive_over_small_register_space(dut): + regs = (0, 1, 2) + n = 0 + for en, ex_valid, u1, u2, mm_wr, wb_wr in itertools.product((0, 1), repeat=6): + for rs1, rs2, mm_rd, wb_rd in itertools.product(regs, repeat=4): + dut.forward_en.value, dut.ex_valid.value = en, ex_valid + dut.ex_uses_rs1.value, dut.ex_uses_rs2.value = u1, u2 + dut.ex_rs1.value, dut.ex_rs2.value = rs1, rs2 + dut.mm_wr.value, dut.mm_rd.value, dut.wb_wr.value, dut.wb_rd.value = mm_wr, mm_rd, wb_wr, wb_rd + await Timer(1, "ns") + a = want(en, ex_valid, u1, rs1, mm_wr, mm_rd, wb_wr, wb_rd) + b = want(en, ex_valid, u2, rs2, mm_wr, mm_rd, wb_wr, wb_rd) + ctx = f"en={en} v={ex_valid} uses={u1}{u2} rs={rs1},{rs2} mm={mm_wr}:{mm_rd} wb={wb_wr}:{wb_rd}" + assert int(dut.fwd_a.value) == a, f"fwd_a {ctx}" + assert int(dut.fwd_b.value) == b, f"fwd_b {ctx}" + n += 1 + assert n == 64 * 81 + + +@cocotb.test() +async def the_nearer_producer_wins_and_x0_never_forwards(dut): + dut.forward_en.value = dut.ex_valid.value = dut.ex_uses_rs1.value = dut.ex_uses_rs2.value = 1 + dut.ex_rs1.value, dut.ex_rs2.value = 7, 0 + dut.mm_wr.value = dut.wb_wr.value = 1 + dut.mm_rd.value = dut.wb_rd.value = 7 + await Timer(1, "ns") + assert int(dut.fwd_a.value) == EXMEM # both match: the younger result (EX/MEM) wins + dut.mm_rd.value = dut.wb_rd.value = 0 + await Timer(1, "ns") + assert int(dut.fwd_b.value) == NONE # x0 diff --git a/sim/test_hazard_unit.py b/sim/test_hazard_unit.py new file mode 100644 index 0000000..1d41b15 --- /dev/null +++ b/sim/test_hazard_unit.py @@ -0,0 +1,37 @@ +import itertools + +import cocotb +from cocotb.triggers import Timer + + +def want(fwd, redirect, id_valid, u1, u2, r1, r2, ex_wr, ex_late, ex_rd, mm_wr, mm_rd): + def needs(u, rs, rd): + return bool(u and rs != 0 and rs == rd) + + ex_hit = ex_wr and (needs(u1, r1, ex_rd) or needs(u2, r2, ex_rd)) + mm_hit = mm_wr and (needs(u1, r1, mm_rd) or needs(u2, r2, mm_rd)) + live = id_valid and not redirect + load_use = live and ex_hit and ex_late + stall = live and ((ex_hit and ex_late) if fwd else (ex_hit or mm_hit)) + return int(stall), int(load_use) + + +@cocotb.test() +async def exhaustive(dut): + regs = (0, 1, 2) + n = 0 + for fwd, redirect, id_valid, u1, u2, ex_wr, ex_late, mm_wr in itertools.product((0, 1), repeat=8): + for r1, r2, ex_rd, mm_rd in itertools.product(regs, repeat=4): + dut.forward_en.value, dut.redirect.value, dut.id_valid.value = fwd, redirect, id_valid + dut.id_uses_rs1.value, dut.id_uses_rs2.value = u1, u2 + dut.id_rs1.value, dut.id_rs2.value = r1, r2 + dut.ex_wr.value, dut.ex_late.value, dut.ex_rd.value = ex_wr, ex_late, ex_rd + dut.mm_wr.value, dut.mm_rd.value = mm_wr, mm_rd + await Timer(1, "ns") + s, lu = want(fwd, redirect, id_valid, u1, u2, r1, r2, ex_wr, ex_late, ex_rd, mm_wr, mm_rd) + ctx = (f"fwd={fwd} redir={redirect} v={id_valid} uses={u1}{u2} id=({r1},{r2}) " + f"ex={ex_wr}/late{ex_late}:{ex_rd} mm={mm_wr}:{mm_rd}") + assert int(dut.stall.value) == s, f"stall {ctx}" + assert int(dut.load_use.value) == lu, f"load_use {ctx}" + n += 1 + assert n == 256 * 81 diff --git a/sim/test_pipe_hazards.py b/sim/test_pipe_hazards.py new file mode 100644 index 0000000..dbb515a --- /dev/null +++ b/sim/test_pipe_hazards.py @@ -0,0 +1,63 @@ +"""Directed hazard patterns on the 5-stage core: PINNED cycle counts and the hazard debug flags. + +The expected "extra cycles" in golden/hazard_cases.py were derived on paper (not from the RTL) and are +checked against the independent model in the ISS-only tests; here the RTL must hit the same numbers in +both forwarding modes, and its dbg_stall / dbg_load_use / dbg_fwd_* pins must tell the same story. +""" + +import cocotb +from cocotb.triggers import ReadOnly, RisingEdge + +from golden.hazard_cases import CASES, DMEM +from golden.iss import Iss +from golden.pipemodel import is_redirect, stalls +from lockstep_lib import CORE, e, lockstep, reset_and_load + +assert CORE == "PIPE", "run through `bash sim/run.sh pipe`" + +LOAD_USE_ONCE = {"load->use dist 1", "load->store-data dist 1", "load->store-address dist 1", "csr->use dist 1", + "both operands late"} +# forwarding sources seen (1 = EX/MEM, 2 = MEM/WB) when forwarding is ON; everything else must be quiet +FWD_SEEN = {"alu->alu dist 1": {1}, "alu->alu dist 2": {2}, "load->use dist 1": {2}, "load->use dist 2": {2}, + "load->store-data dist 1": {2}, "load->store-address dist 1": {2}, "csr->use dist 1": {2}, + "both operands late": {2}} + + +def image(insns) -> bytes: + return b"".join(w.to_bytes(4, "little") for w in (*insns, e("ecall"))) + + +async def observe(dut, imem, forward): + await reset_and_load(dut, imem, DMEM, forward) + rows = [] + for _ in range(2000): + await RisingEdge(dut.clk) + await ReadOnly() + rows.append({k: int(getattr(dut, f"dbg_{k}").value) for k in ("stall", "load_use", "fwd_a", "fwd_b", "flush_ifid")} + | {"halted": int(dut.halted.value)}) + if rows[-1]["halted"]: + return rows + raise AssertionError("no halt") + + +@cocotb.parametrize(name=sorted(CASES), forward=[1, 0]) +async def test_pinned_cycle_counts(dut, name, forward): + insns, extra_on, extra_off = CASES[name] + iss, _, cycles = await lockstep(dut, image(insns), DMEM, expect_fault=False, forward=forward) + want = extra_on if forward else extra_off + assert cycles - (iss.instret + 4) == want, ( + f"{name} forward={forward}: {cycles - (iss.instret + 4)} extra cycles, expected {want}") + + +@cocotb.parametrize(name=sorted(CASES), forward=[1, 0]) +async def test_debug_flags(dut, name, forward): + insns, _, _ = CASES[name] + imem = image(insns) + trace = Iss(imem, DMEM).run(1000) + rows = await observe(dut, imem, forward) + + assert sum(r["stall"] for r in rows) == stalls(trace, bool(forward)), f"{name}: stall cycles" + assert sum(r["flush_ifid"] for r in rows) == sum(is_redirect(r) for r in trace), f"{name}: flushes" + assert sum(r["load_use"] for r in rows) == (1 if name in LOAD_USE_ONCE else 0), f"{name}: load-use pulses" + seen = {v for r in rows for v in (r["fwd_a"], r["fwd_b"]) if v} + assert seen == (FWD_SEEN.get(name, set()) if forward else set()), f"{name} forward={forward}: fwd sources {seen}" diff --git a/sim/test_pipe_lockstep.py b/sim/test_pipe_lockstep.py index 9686117..f22c771 100644 --- a/sim/test_pipe_lockstep.py +++ b/sim/test_pipe_lockstep.py @@ -1,43 +1,77 @@ -"""5-stage core (CORE="PIPE") vs the golden ISS, Phase 3: no forwarding, no load-use stall. +"""5-stage core (CORE="PIPE") vs the golden ISS in lockstep, with forwarding ON and OFF. -Programs here are hazard-free by construction (golden/hazard_free.py). The riscv-tests images need -forwarding and join in Phase 4. Run with TINYCPU_CORE=PIPE and -GCORE="PIPE" (sim/run.sh pipe). +Everything runs in both modes (the cfg_forward_en pin is runtime): hazard-free programs, the official +riscv-tests images (back-to-back dependencies, load-use, store->load), the example programs, and +hazard-dense random programs. Cycle counts must equal the independent model golden/pipemodel.py. +Run with TINYCPU_CORE=PIPE and -GCORE="PIPE" (sim/run.sh pipe). RANDOM_SEEDS=N scales the random sweep. """ +import os + import cocotb from cocotb.triggers import ReadOnly, RisingEdge from golden.hazard_free import PROGRAMS, random_program +from golden.hazard_random import random_hazard_program from golden.progbuild import Prog -from lockstep_lib import CORE, IMEM_SIZE, e, lockstep, prog_bytes, reset_and_load +from lockstep_lib import CORE, EXPECTED, IMEM_SIZE, e, images, lockstep, prog_bytes, reset_and_load, stem_path +from pathlib import Path + +SEEDS = int(os.environ.get("RANDOM_SEEDS", "200")) +MODES = [1, 0] # cfg_forward_en assert CORE == "PIPE", "run through `bash sim/run.sh pipe` (sets TINYCPU_CORE=PIPE and -GCORE=\"PIPE\")" -@cocotb.parametrize(name=sorted(PROGRAMS)) -async def test_hazard_free_program(dut, name): +@cocotb.parametrize(name=sorted(PROGRAMS), forward=MODES) +async def test_hazard_free_program(dut, name, forward): imem, dmem = PROGRAMS[name]().build() - iss, console, _ = await lockstep(dut, imem, dmem, expect_fault=False) + iss, console, _ = await lockstep(dut, imem, dmem, expect_fault=False, forward=forward) assert iss.passed if name == "console_and_csr": assert console == b"Hi, pipe!\n!" -@cocotb.parametrize(seed=list(range(60))) -async def test_random_hazard_free(dut, seed): +@cocotb.parametrize(seed=list(range(60)), forward=MODES) +async def test_random_hazard_free(dut, seed, forward): imem, dmem = random_program(seed).build() - iss, _, _ = await lockstep(dut, imem, dmem, expect_fault=False) + iss, _, _ = await lockstep(dut, imem, dmem, expect_fault=False, forward=forward) + assert iss.passed + + +@cocotb.parametrize(seed=list(range(SEEDS)), forward=MODES) +async def test_random_hazard_dense(dut, seed, forward): + """Dense code over 5 registers: RAW at every distance, load-use, store->load, jumps, CSR reads.""" + imem, dmem = random_hazard_program(seed).build() + iss, _, _ = await lockstep(dut, imem, dmem, expect_fault=False, forward=forward) assert iss.passed +# --------------------------------------------------------------------------- official tests + programs +NAMES = images() +if NAMES: + + @cocotb.parametrize(name=NAMES, forward=MODES) + async def test_image(dut, name, forward): + """rv32ui-p-* and sim/programs/*.S, unmodified: only possible now that hazards are handled.""" + stem = stem_path(name) + imem, dmem = Path(str(stem) + ".imem.bin").read_bytes(), Path(str(stem) + ".dmem.bin").read_bytes() + iss, console, _ = await lockstep(dut, imem, dmem, expect_fault=False, forward=forward) + assert iss.passed, f"{name}: failed test case #{(iss.tohost or 0) >> 1}" + if not name.startswith("rv32ui"): + assert console.decode() == EXPECTED[name]["console"] + assert iss.instret == EXPECTED[name]["instret"] + + # --------------------------------------------------------------------------- halt and faults @cocotb.test() async def ecall_ebreak_halt(dut): for insn in (e("ecall"), e("ebreak")): p = Prog().addi(1, 0, 7) p.raw(insn).addi(1, 0, 9) - iss, _, _ = await lockstep(dut, p.build()[0], expect_fault=False) - assert iss.regs[1] == 7 and iss.tohost is None + for forward in MODES: + iss, _, _ = await lockstep(dut, p.build()[0], expect_fault=False, forward=forward) + assert iss.regs[1] == 7 and iss.tohost is None def fault_cases(): @@ -66,11 +100,12 @@ def build(*items, tail=()): @cocotb.test() async def faults_stop_the_core_without_retiring(dut): - for label, imem in fault_cases().items(): - try: - await lockstep(dut, imem, expect_fault=True) - except AssertionError as exc: - raise AssertionError(f"[{label}] {exc}") from exc + for forward in MODES: + for label, imem in fault_cases().items(): + try: + await lockstep(dut, imem, expect_fault=True, forward=forward) + except AssertionError as exc: + raise AssertionError(f"[{label}, forward={forward}] {exc}") from exc def setup_console(p): @@ -96,11 +131,12 @@ async def younger_instructions_have_no_effect_after_a_fault(dut): faulting(p) p.sb(3, 1, 0).sb(3, 1, 0).sb(3, 1, 0).nop(3) p.exit() - try: - iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=True) - except AssertionError as exc: - raise AssertionError(f"[{label}] {exc}") from exc - assert console == b"A", f"[{label}] younger stores leaked: {console!r}" + for forward in MODES: + try: + iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=True, forward=forward) + except AssertionError as exc: + raise AssertionError(f"[{label}, forward={forward}] {exc}") from exc + assert console == b"A", f"[{label}, forward={forward}] younger stores leaked: {console!r}" @cocotb.test() @@ -114,8 +150,9 @@ async def younger_instructions_have_no_effect_after_halt(dut): p.sb(2, 1, 0) halting(p) p.sb(3, 1, 0).sb(3, 1, 0).sb(3, 1, 0) - iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=False) - assert console == b"A", f"[{label}] instructions after the halt leaked output: {console!r}" + for forward in MODES: + iss, console, _ = await lockstep(dut, p.build()[0], expect_fault=False, forward=forward) + assert console == b"A", f"[{label}, forward={forward}] instructions after the halt leaked: {console!r}" # --------------------------------------------------------------------------- pipeline behaviour From ebb1f532e7ddedb0d6f21007fbcb0d77dccee68f Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sun, 25 Oct 2026 13:55:00 +0530 Subject: [PATCH 06/10] feat: Phase 5 WASM harness with both cores in one module wasm/harness.cpp owns both Verilated models of tiny_cpu_top (SINGLE and PIPE, distinct prefixes) behind one TinyCpuSim. build.sh builds the module and enforces a 400 KB gzipped budget (77 KB today). test.mjs checks every golden fixture on both cores, forwarding on and off, against the Python ISS: console, registers, data memory, retire-stream digest and pipeline-model cycle counts. Adds a hazard-dense fixture, the only one where the forwarding pin is observable. docs/STATE_SCHEMA.md defines the JavaScript contract. --- .github/workflows/wasm.yml | 42 +++- .gitignore | 4 +- AGENTS.md | 9 +- CLAUDE.md | 9 +- CONTEXT.md | 14 +- README.md | 8 +- docs/ARCHITECTURE.md | 19 ++ docs/STATE_SCHEMA.md | 110 +++++++++ sim/golden/test_wasm_fixtures.py | 52 +++++ sim/golden/wasm_fixtures.py | 120 ++++++++++ wasm/README.md | 17 +- wasm/bindings.cpp | 23 ++ wasm/build.sh | 62 +++++ wasm/harness.cpp | 386 +++++++++++++++++++++++++++++++ wasm/test.mjs | 132 +++++++++++ wasm/test/fixtures.json | 204 ++++++++++++++++ web/public/tiny_cpu.mjs | 2 + 17 files changed, 1187 insertions(+), 26 deletions(-) create mode 100644 docs/STATE_SCHEMA.md create mode 100644 sim/golden/test_wasm_fixtures.py create mode 100644 sim/golden/wasm_fixtures.py create mode 100644 wasm/bindings.cpp create mode 100755 wasm/build.sh create mode 100644 wasm/harness.cpp create mode 100644 wasm/test.mjs create mode 100644 wasm/test/fixtures.json create mode 100644 web/public/tiny_cpu.mjs diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml index ceabbfa..f56438c 100644 --- a/.github/workflows/wasm.yml +++ b/.github/workflows/wasm.yml @@ -2,9 +2,9 @@ name: WASM Build on: push: - paths: ['rtl/**', 'wasm/**', '.github/workflows/wasm.yml'] + paths: ['rtl/**', 'wasm/**', 'web/public/**', '.tools/**', '.github/workflows/wasm.yml'] pull_request: - paths: ['rtl/**', 'wasm/**', '.github/workflows/wasm.yml'] + paths: ['rtl/**', 'wasm/**', 'web/public/**', '.tools/**', '.github/workflows/wasm.yml'] jobs: wasm-build: @@ -13,10 +13,29 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install Verilator and build deps + # The harness needs the same Verilator as the RTL suites (cocotb's >= 5.036 rule does not apply + # here, but one version everywhere is simpler). Built from source and cached by version tag. + - name: Cache Verilator build + id: cache-verilator + uses: actions/cache@v4 + with: + path: ~/verilator-install + key: verilator-v5.036-ubuntu-24.04 + + - name: Build Verilator v5.036 from source + if: steps.cache-verilator.outputs.cache-hit != 'true' run: | - sudo apt-get update - sudo apt-get install -y verilator build-essential + sudo apt-get update -qq + sudo apt-get install -y autoconf flex bison libfl2 libfl-dev help2man perl + git clone --depth 1 --branch v5.036 https://github.com/verilator/verilator.git /tmp/verilator + cd /tmp/verilator + autoconf + ./configure --prefix="$HOME/verilator-install" + make -j$(nproc) + make install + + - name: Add Verilator to PATH + run: echo "$HOME/verilator-install/bin" >> $GITHUB_PATH - name: Set up Emscripten uses: mymindstorm/setup-emsdk@v14 @@ -25,13 +44,14 @@ jobs: with: node-version: '22' - - name: Build spike WASM + - name: Build tiny_cpu WASM (both cores, one module) + run: bash wasm/build.sh + + - name: Node test against the golden fixtures + run: node wasm/test.mjs + + - name: Build spike WASM (toolchain check) run: bash wasm/spike/build.sh - name: Run spike headless run: node wasm/spike/test.mjs - - - name: Report artifact size - run: | - test -f web/public/spike/counter.wasm - echo "spike wasm: $(du -h web/public/spike/counter.wasm | cut -f1)" diff --git a/.gitignore b/.gitignore index d0d4db8..c26d4cf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,12 +24,12 @@ sim/results.xml # WASM build intermediates and generated artifacts wasm/build/ wasm/spike/counter.mjs -web/public/tiny_cpu.mjs web/public/spike/ *.o *.wasm -!web/public/tiny_cpu.wasm +!web/public/tiny_cpu.wasm # shipped artifacts: the web app and Vercel build have no Emscripten +!web/public/tiny_cpu.mjs # Toolchain checkouts .emsdk/ diff --git a/AGENTS.md b/AGENTS.md index 5f698ee..20c54f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,11 +47,14 @@ bash sim/run.sh all # everything above Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` (add `COCOTB_TEST_FILTER=` to pick tests). -### WASM +### WASM (Phase 5) ```bash -bash wasm/spike/build.sh # Phase 0 toolchain spike -> web/public/spike/ -node wasm/spike/test.mjs # headless check of the spike build +bash wasm/build.sh # both cores -> web/public/tiny_cpu.{mjs,wasm}; enforces the 400 KB gzipped budget +node wasm/test.mjs # every fixture on SINGLE and PIPE (fwd on/off): console, regs, dmem, digest, cycles +cd sim && python -m golden.wasm_fixtures # regenerate wasm/test/fixtures.json after changing the golden model +bash wasm/spike/build.sh && node wasm/spike/test.mjs # Phase 0 toolchain check ``` +Both `web/public/tiny_cpu.{mjs,wasm}` are committed: the web app and Vercel have no Emscripten. ### Frontend (from `web/`) ```bash diff --git a/CLAUDE.md b/CLAUDE.md index 80bf097..8aa9611 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,11 +47,14 @@ bash sim/run.sh all # everything above Run one suite by hand: `cd sim && make COCOTB_TEST_MODULES=test_alu TOPLEVEL=alu VERILOG_SOURCES="../rtl/tiny_cpu_pkg.sv ../rtl/alu.sv" WAVES=0 SIM_BUILD=sim_build/test_alu` (add `COCOTB_TEST_FILTER=` to pick tests). -### WASM +### WASM (Phase 5) ```bash -bash wasm/spike/build.sh # Phase 0 toolchain spike -> web/public/spike/ -node wasm/spike/test.mjs # headless check of the spike build +bash wasm/build.sh # both cores -> web/public/tiny_cpu.{mjs,wasm}; enforces the 400 KB gzipped budget +node wasm/test.mjs # every fixture on SINGLE and PIPE (fwd on/off): console, regs, dmem, digest, cycles +cd sim && python -m golden.wasm_fixtures # regenerate wasm/test/fixtures.json after changing the golden model +bash wasm/spike/build.sh && node wasm/spike/test.mjs # Phase 0 toolchain check ``` +Both `web/public/tiny_cpu.{mjs,wasm}` are committed: the web app and Vercel have no Emscripten. ### Frontend (from `web/`) ```bash diff --git a/CONTEXT.md b/CONTEXT.md index 01ecfc1..78add3b 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -23,7 +23,10 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - Phase 4 (hazards): DONE. `forward_unit`, `hazard_unit`, `cfg_forward_en` pin, dbg stall/load-use/fwd flags. The pipeline runs the 40 riscv-tests + all programs + 260 random programs in lockstep in BOTH modes, cycle-exact against `golden/pipemodel.py`. 11 mutants killed. `sim/perf_baseline.json` pins cycles per core/mode. -- Next: Phase 5 (WASM harness: dual-model build, `TinyCpuSim` API, `docs/STATE_SCHEMA.md`, Node test). +- Phase 5 (WASM harness): DONE. `wasm/harness.cpp` owns both Verilated models (`Vcpu_single`, `Vcpu_pipe`, CORE param) behind + one `TinyCpuSim`. `wasm/build.sh` builds 194 KB raw / 77 KB gzipped. `wasm/test.mjs`: 534 checks over 4 fixtures on both cores. + The contract is `docs/STATE_SCHEMA.md`. +- Next: Phase 6 (assembler + core UI in `web/`: `assembler.ts`, `state-schema.ts` mirror, `useTinyCpu`, L2 pipeline view first). ## Interfaces Phase 2+ must honour (defined by the ISS, `sim/golden/iss.py`) - Retire record fields, lane/mask conventions, forced alignment, `cycle_dependent` masking: see the module docstring and PLAN 2.4. @@ -85,3 +88,12 @@ every pipeline register, forwarding path and stall comes from a `dbg_*` port on - A flush hides the stall of the squashed instruction (`jal rd; use rd` costs 2 either way). - `RANDOM_SEEDS=N bash sim/run.sh pipe` scales the random sweep (default 200 per mode); mutation runs use 40. - The root filesystem filled during a long mutation sweep (home caches, not this repo): keep `/tmp` and logs small. + +## Gotchas found in Phase 5 +- Hazard-free programs take the same cycles with forwarding on and off (that is what hazard-free means), so they cannot test + the forwarding pin. The fixture set includes one hazard-dense program that must differ, and the generator asserts it. +- A harness that re-implements data memory would be tested against itself. The shadow copy is fed only from retire records + (rvfi_mem_*), and `test.mjs` checks it against the ISS for every store the fixtures make. +- Emscripten's temporaries and Verilator output live under `wasm/build/` (on the project disk); the system disk was full twice + during this project, and `/tmp` writes broke tool output. +- Both models are held in reset while `loadImem`/`loadDmem` run, so an inactive model is never clocked by accident. diff --git a/README.md b/README.md index c0342e9..0fcbd97 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 4 done (hazards).** Both RV32I cores retire instructions identically to the Python golden ISS on all 40 official `rv32ui-p-*` tests, 5 programs and hundreds of random programs. The 5-stage core has forwarding and load-use stalls (switchable at runtime) and matches an independent timing model cycle for cycle. Next: the WASM harness. +> **Status: Phase 5 done (WASM harness).** Both cores run in one WebAssembly module (`wasm/build.sh`, 77 KB gzipped) and pass the golden fixtures in Node (`node wasm/test.mjs`). Next: the web app. +> +> *Previous: Phase 4 done (hazards).* Both RV32I cores retire instructions identically to the Python golden ISS on all 40 official `rv32ui-p-*` tests, 5 programs and hundreds of random programs. The 5-stage core has forwarding and load-use stalls (switchable at runtime) and matches an independent timing model cycle for cycle. Next: the WASM harness. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. Every pipeline register, forwarding path and stall the UI will draw is a live `dbg_*` signal from the @@ -51,8 +53,8 @@ pip install -r sim/requirements.txt verilator --lint-only -Wall --top-module counter rtl/spike/counter.sv bash scripts/lint.sh && bash sim/run.sh all # needs: make -C sw all -# WASM spike (needs Verilator + Emscripten) -bash wasm/spike/build.sh && node wasm/spike/test.mjs +# WASM (needs Verilator + Emscripten) +bash wasm/build.sh && node wasm/test.mjs # Web cd web && pnpm install && pnpm dev # open /spike after building the WASM diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 43e7968..1eaff3b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -145,3 +145,22 @@ runs dense code with stores directly behind the faulting/halting instruction and | Program builder | `sim/golden/progbuild.py`, `hazard_free.py` | `Prog(pad=N)` mini-assembler; `raw_hazards()` proves a program hazard-free from its dynamic ISS trace (no toolchain needed) | Run: `bash scripts/lint.sh && bash sim/run.sh all` (after `make -C sw all`). `all` = unit + lockstep (single-cycle) + pipe. + + +## WASM harness (Phase 5) + +`wasm/harness.cpp` Verilates `tiny_cpu_top` twice - `CORE="SINGLE"` and `CORE="PIPE"` - with distinct `--prefix`, and links +both into one module. `TinyCpuSim` owns both models and `reset(core)` selects one; program memory is written to both, and +both are held in reset while loading, so the inactive core is never clocked by accident. + +Every value comes from an output port of the Verilated model. The one exception is data memory, which the RTL does not +expose: the harness keeps a shadow copy fed from the retire records (`rvfi_mem_*`). The JavaScript contract is +`docs/STATE_SCHEMA.md`. + +Verification (`node wasm/test.mjs`, 534 checks): each fixture in `wasm/test/fixtures.json` runs on SINGLE and on PIPE with +forwarding on and off. It checks console output, final registers (timing-dependent ones excluded), data memory, instruction +count, the SHA-256 of the retire stream against the Python ISS, the halt/tohost behaviour, and the cycle count against the +pipeline model. Four mutations were caught: a forwarding pin ignored, a console byte dropped, a cycle counter off by one, and +the shadow memory's byte order reversed. + +Size: 194 KB raw, 77 KB gzipped (budget 400 KB), enforced by `build.sh`. diff --git a/docs/STATE_SCHEMA.md b/docs/STATE_SCHEMA.md new file mode 100644 index 0000000..1050017 --- /dev/null +++ b/docs/STATE_SCHEMA.md @@ -0,0 +1,110 @@ +# STATE_SCHEMA.md - the WASM per-cycle contract + +The JavaScript API of `wasm/harness.cpp`, as shipped in `web/public/tiny_cpu.mjs`. Every value is read from an +output port of the Verilated model - nothing is re-computed in C++ or JavaScript - except data memory, which is +kept as a shadow copy from the retire records (see below). `wasm/test.mjs` checks this contract. + +## Loading and running + +```js +const { default: createTinyCpu } = await import("/tiny_cpu.mjs"); +const mod = await createTinyCpu(); +const sim = new mod.TinyCpuSim(); // owns BOTH cores; one instance is one simulation +sim.setForwarding(true); // PIPE only; takes effect at the next reset() +sim.reset("PIPE"); // "SINGLE" or "PIPE"; false for any other name +sim.loadImem(words); // array of uint32, at most 1024 words +sim.loadDmem(bytes); // array of bytes, at most 4096, little-endian +sim.start(); // release reset; cycle 1 is the next step +const states = sim.run(200000); // every CycleState up to halt/fault (the last one is the halt) +sim.delete(); +``` + +| Method | Returns | Notes | +|---|---|---| +| `reset(core)` | `boolean` | Selects the core, holds reset on both. Clears console and cycle count. | +| `setForwarding(on)` | - | `cfg_forward_en` pin. Ignored by SINGLE (it has no hazards). | +| `loadImem(words)` / `loadDmem(bytes)` | `boolean` | `false` if too large. Both cores are held in reset while loading, and the whole memory is written (zero-padded), so a previous program never leaks. | +| `start()` | - | Releases reset. The next `step()` is cycle 1. | +| `step()` | CycleState \| `null` | One clock edge. `null` before `start()`. After halt or fault it returns the same state without advancing. | +| `run(maxCycles)` | CycleState[] | Steps until halt, fault or the limit. | +| `runFast(maxCycles)` | summary | `{cycles, instret, halted, fault, tohost, console}` without per-cycle states. | +| `getRegs()` | `number[32]` | Architectural registers from the register-file port. | +| `readDmem(addr, len)` | `number[]` | Data memory at absolute address `addr` (base `0x0001_0000`). | +| `getConsole()` | `string` | Bytes written to CONSOLE since `reset()`. | +| `getCycleCount()`, `isHalted()`, `isFault()` | number / boolean | | + +## CycleState + +Returned by `step()` and `run()`. Each value is the state **after** the clock edge that produced it. + +```ts +interface CycleState { + cycle: number; // clock edges since start(); 1 for the first step + halted: boolean; // the halting instruction has retired (ecall, ebreak or a TOHOST store) + fault: boolean; // illegal instruction, bus fault or misaligned jump; nothing retired + stages: Stage[5]; // index 0..4 = IF, ID, EX, MEM, WB + hazard: Hazard; + regs: number[32]; // x0..x31 from the register file + retired: Retire | null; // the instruction that retired on this edge, if any + console: number | null; // byte written to CONSOLE on this edge, else null + tohost: number | null; // TOHOST value once written (sticky), else null + counters: { cycle: number; instret: number }; +} + +interface Stage { + name: "IF" | "ID" | "EX" | "MEM" | "WB"; + valid: boolean; // a real instruction (false = bubble) + pc: number; // pc of the instruction in this stage + insn: number; // 32-bit instruction word; 0 for IF on PIPE (fetch is synchronous) +} + +interface Hazard { + stall: boolean; // ID is stalled this edge + loadUse: boolean; // the stall is a load-use stall (PIPE with forwarding) + flushIFID: boolean; // a taken branch or jump squashed IF/ID this edge + flushIDEX: boolean; // ... and ID/EX + fwdA: 0 | 1 | 2; // operand A forwarded from: 0 none, 1 EX/MEM, 2 MEM/WB + fwdB: 0 | 1 | 2; +} + +interface Retire { // RVFI-lite, docs/PLAN.md section 2.4 (field meanings match sim/golden/iss.py) + order: number; // index of this instruction in the retire stream, from 0 + pc: number; insn: number; + rdAddr: number; rdWdata: number; // rdAddr 0 = no register written + pcWdata: number; // address of the next instruction + memAddr: number; // word-aligned; 0 if no memory access + memRmask: number; memWmask: number; // byte lanes (4 bits) + memRdata: number; memWdata: number; // lane-positioned 32-bit words + misaligned: boolean; // a load/store that was forced into alignment + halt: boolean; + cycleDep: boolean; // value depends on timing (rdcycle/rdtime, CYCLE MMIO) +} +``` + +### Semantics the UI relies on + +* **Cycle numbering.** `cycle` counts clock edges from `start()`. The halting instruction is visible on the edge + that makes `halted` true, so a run's length in cycles is `run().length`. SINGLE: exactly `instret` cycles. PIPE: + the value from the timing model `golden/pipemodel.py`, which the RTL suites check cycle for cycle. +* **Stages.** Stage index 0 is IF through index 4 is WB. On SINGLE only IF is populated, and it shows the instruction + being executed, so SINGLE's `stages[0].insn` is the real word. +* **Console and TOHOST.** `console` is the byte written on that edge; `tohost` stays set after the write. + A PASS run has `tohost === 1`. +* **Timing-dependent values.** A register last written by a `cycleDep` retire holds a cycle count. Its value + differs between SINGLE and PIPE and from the ISS. Compare those with care. +* **States after halt or fault.** No instruction retires and no memory changes; the state is frozen. + +## Data memory + +The RTL keeps data memory inside its memories, so the harness keeps a shadow copy. It is updated from the retire +records (`memWmask` lanes and `memWdata`) for stores into DMEM, and from `loadDmem`. Stores to CONSOLE and TOHOST +are excluded by address. Stores that have not yet retired are not visible, which is the architectural view a +program sees at a halt. Loads never change memory, so the shadow is exact. + +## Sync requirements + +This document and the ISS (`sim/golden/iss.py`), the timing model (`sim/golden/pipemodel.py`) and the fixtures +(`wasm/test/fixtures.json`, generated by `sim/golden/wasm_fixtures.py`) must agree. `sim/golden/test_wasm_fixtures.py` +checks the fixtures; `wasm/test.mjs` checks the built module against them. + +The TypeScript mirror for the web app (`web/src/lib/state-schema.ts`) lands in Phase 6. diff --git a/sim/golden/test_wasm_fixtures.py b/sim/golden/test_wasm_fixtures.py new file mode 100644 index 0000000..ec4d7a1 --- /dev/null +++ b/sim/golden/test_wasm_fixtures.py @@ -0,0 +1,52 @@ +"""wasm/test/fixtures.json is current and agrees with the golden ISS's own answers.""" + +from golden.wasm_fixtures import OUT, build_fixture, render, retire_digest # noqa: F401 +from golden.iss import Iss +from golden.hazard_free import PROGRAMS +import json + + +def test_fixtures_are_current(): + from golden.wasm_fixtures import main + + assert main(["--check"]) == 0, "run: cd sim && python -m golden.wasm_fixtures" + + +def test_fixture_answers_are_the_expected_ones(): + data = json.loads(OUT.read_text()) + assert data["hazard_free/fib"]["expect"]["dmemHex"] == (6765).to_bytes(4, "little").hex() + assert data["hazard_free/console_and_csr"]["console"] == "Hi, pipe!\n!" + assert data["hazard_free/bubble_sort"]["instret"] > 0 + + +def test_fixture_cycles_agree_with_the_pinned_baseline(): + import json as _json + + from golden.perf import BASELINE + + pinned = _json.loads(BASELINE.read_text()) + data = _json.loads(OUT.read_text()) + shared = [k for k in data if k in pinned] + assert shared, "no fixture is in the perf baseline" + for name in shared: + assert data[name]["cycles"] == { + "single": pinned[name]["single"], + "pipeFwd": pinned[name]["pipe_fwd"], + "pipeNoFwd": pinned[name]["pipe_nofwd"], + }, name + + +def test_hazard_fixture_shows_the_forwarding_pin(): + data = json.loads(OUT.read_text()) + c = data["hazard_dense/3"]["cycles"] + assert c["pipeFwd"] != c["pipeNoFwd"] + + +def test_digest_depends_on_the_retire_stream(): + imem, dmem = PROGRAMS["fib"]().build() + trace = Iss(imem, dmem).run(200_000) + d = retire_digest(trace) + assert d == retire_digest(Iss(imem, dmem).run(200_000)) # deterministic + tampered = list(trace) + tampered[5] = type(trace[5])(**{**trace[5].__dict__, "rd_wdata": trace[5].rd_wdata ^ 1}) + assert retire_digest(tampered) != d diff --git a/sim/golden/wasm_fixtures.py b/sim/golden/wasm_fixtures.py new file mode 100644 index 0000000..a39a1a6 --- /dev/null +++ b/sim/golden/wasm_fixtures.py @@ -0,0 +1,120 @@ +"""Fixtures for the WASM harness tests (wasm/test.mjs), generated from the golden ISS. + + cd sim && python -m golden.wasm_fixtures # rewrite wasm/test/fixtures.json + cd sim && python -m golden.wasm_fixtures --check # exit 1 if stale (pytest does this too) + +Each fixture carries the ISS's expected console output, instruction count, final registers, memory +expectations and a SHA-256 digest of the retire stream. The Node test compares the WASM build against +all of them, so the browser build is checked against the Python reference, not against itself. + +Cycle counts come from the independent pipeline model (golden/pipemodel.py). They are stored per fixture +and must agree with sim/perf_baseline.json wherever both list the program (test_wasm_fixtures.py checks it). +""" + +from __future__ import annotations + +import hashlib +import json +import struct +import sys +from pathlib import Path + +from golden.hazard_free import PROGRAMS +from golden.hazard_random import random_hazard_program +from golden.iss import DMEM_BASE, Iss +from golden.pipemodel import pipe_cycles + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / "wasm" / "test" / "fixtures.json" +# Hazard-free programs take the same cycles with forwarding on and off (that is their point), so one +# hazard-dense program is included: it is the only fixture on which the forwarding pin is observable. +HAZARD_SEED = 3 +FIXTURE_PROGRAMS = ["fib", "console_and_csr", "bubble_sort", f"hazard_dense/{HAZARD_SEED}"] + +# The retire-record fields hashed into the digest, in order. Cycle-dependent values (rdcycle, +# rdtime, CYCLE MMIO) are replaced by 0 because they are timing, not architecture. +DIGEST_FMT = "<13I" + + +def retire_digest(trace) -> str: + h = hashlib.sha256() + for r in trace: + rd = 0 if r.cycle_dependent else r.rd_wdata + md = 0 if r.cycle_dependent else r.mem_rdata + h.update( + struct.pack( + DIGEST_FMT, + r.order, r.pc, r.insn, r.rd_addr, rd, r.pc_wdata, r.mem_addr, + r.mem_rmask, r.mem_wmask, md, r.mem_wdata, int(r.misaligned), int(r.halt), + ) + ) + return h.hexdigest() + + +def timing_registers(trace) -> list[int]: + """Registers whose final value depends on timing (last written by a cycle-dependent retire).""" + timing: set[int] = set() + for r in trace: + if r.rd_addr: + (timing.add if r.cycle_dependent else timing.discard)(r.rd_addr) + return sorted(timing) + + +def _program(name: str): + if name.startswith("hazard_dense/"): + return random_hazard_program(int(name.split("/")[1])) + return PROGRAMS[name]() + + +def build_fixture(name: str) -> dict: + imem, dmem = _program(name).build() + iss = Iss(imem, dmem) + trace = iss.run(200_000) + assert iss.passed, f"{name}: fixture program does not pass on the ISS" + cycles = { + "single": iss.instret, + "pipeFwd": pipe_cycles(trace, True), + "pipeNoFwd": pipe_cycles(trace, False), + } + if name.startswith("hazard_dense/"): + assert cycles["pipeFwd"] != cycles["pipeNoFwd"], "hazard fixture must show the forwarding pin" + expect = {} + if name == "fib": + expect = {"dmemAddr": DMEM_BASE, "dmemHex": (6765).to_bytes(4, "little").hex()} + elif name == "bubble_sort": + values = sorted([42, 7, 99, 3, 18, 64, 1, 27]) + expect = {"dmemAddr": DMEM_BASE, "dmemHex": struct.pack("<8I", *values).hex()} + return { + "imem": imem.hex(), + "dmem": dmem.hex(), + "console": iss.console_text, + "instret": iss.instret, + "cycles": cycles, + "regs": iss.regs, + "timingRegs": timing_registers(trace), + "digest": retire_digest(trace), + "expect": expect, + } + + +def render() -> str: + data = {(n if n.startswith("hazard_dense/") else f"hazard_free/{n}"): build_fixture(n) + for n in FIXTURE_PROGRAMS} + return json.dumps(data, indent=1, sort_keys=True) + "\n" + + +def main(argv: list[str]) -> int: + text = render() + if "--check" in argv: + if not OUT.exists() or OUT.read_text() != text: + print(f"{OUT} is stale: run `cd sim && python -m golden.wasm_fixtures`", file=sys.stderr) + return 1 + return 0 + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(text) + print(f"wrote {OUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/wasm/README.md b/wasm/README.md index 7e206b9..14eba38 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -1,8 +1,19 @@ # wasm/ -`spike/` is the Phase 0 proof that Verilator + Emscripten work here, including two Verilated -models (distinct `--prefix`) in one module. Phase 5 adds `harness.cpp`, `bindings.cpp`, `build.sh`. +`harness.cpp` and `bindings.cpp` build both microarchitectures of `tiny_cpu_top` into one WebAssembly module +(`web/public/tiny_cpu.{mjs,wasm}`). The JavaScript contract is `docs/STATE_SCHEMA.md`. ```bash -bash wasm/spike/build.sh && node wasm/spike/test.mjs +source ../scripts/env.sh # Verilator 5.036 and Emscripten (see CLAUDE.md for the one-time bootstrap) +bash wasm/build.sh # two Verilations (CORE=SINGLE, CORE=PIPE) + em++ + size budget (400 KB gzipped) +node wasm/test.mjs # every fixture on both cores, forwarding on and off: console, regs, dmem, digest, cycles ``` + +- `test/fixtures.json` is generated from the golden ISS: `cd sim && python -m golden.wasm_fixtures`. + `sim/golden/test_wasm_fixtures.py` fails if it is stale. +- `build.sh` keeps Emscripten's temporaries under `wasm/build/` so a full system disk can't break it. +- `spike/` is the Phase 0 toolchain check (one model per prefix, two counters); kept as a regression of the setup. +- `emscripten_stubs.cpp` supplies the pthread affinity calls that `verilated_threads.cpp` references. + +Mutation checks (see CONTEXT.md): dropping a console byte, a cycle off by one, ignoring the forwarding pin and +reversing the shadow-memory byte order each fail `wasm/test.mjs`. diff --git a/wasm/bindings.cpp b/wasm/bindings.cpp new file mode 100644 index 0000000..82411fa --- /dev/null +++ b/wasm/bindings.cpp @@ -0,0 +1,23 @@ +// Emscripten embind glue. Build with wasm/build.sh; the JS API is documented in docs/STATE_SCHEMA.md. +#include "harness.cpp" + +#include + +EMSCRIPTEN_BINDINGS(tiny_cpu) { + emscripten::class_("TinyCpuSim") + .constructor<>() + .function("reset", &TinyCpuSim::reset) + .function("setForwarding", &TinyCpuSim::setForwarding) + .function("loadImem", &TinyCpuSim::loadImem) + .function("loadDmem", &TinyCpuSim::loadDmem) + .function("start", &TinyCpuSim::start) + .function("step", &TinyCpuSim::step) + .function("run", &TinyCpuSim::run) + .function("runFast", &TinyCpuSim::runFast) + .function("getRegs", &TinyCpuSim::getRegs) + .function("readDmem", &TinyCpuSim::readDmem) + .function("getConsole", &TinyCpuSim::getConsole) + .function("getCycleCount", &TinyCpuSim::getCycleCount) + .function("isHalted", &TinyCpuSim::isHalted) + .function("isFault", &TinyCpuSim::isFault); +} diff --git a/wasm/build.sh b/wasm/build.sh new file mode 100755 index 0000000..0f98a3c --- /dev/null +++ b/wasm/build.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Phase 5: Verilate tiny_cpu_top twice - CORE="SINGLE" and CORE="PIPE", distinct --prefix - and link both +# into one WebAssembly module. Outputs web/public/tiny_cpu.mjs + tiny_cpu.wasm and checks the size budget. +# +# Prerequisites (run `source scripts/env.sh` first): Verilator >= 5.036 on PATH, Emscripten (em++). +# Emscripten's temporary files go under wasm/build, not /tmp, so a full system disk cannot break the build. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD="$SCRIPT_DIR/build" +OUT_DIR="$PROJECT_ROOT/web/public" +SIZE_BUDGET_KB=400 # gzipped .wasm, docs/PLAN.md section 7 + +export TMPDIR="$BUILD/tmp" +mkdir -p "$TMPDIR" "$OUT_DIR" + +VROOT="$(verilator --getenv VERILATOR_ROOT)" +echo "[info] VERILATOR_ROOT = $VROOT" + +RTL="" +for f in tiny_cpu_pkg alu imm_gen branch_unit lsu decoder regfile csr_counters mmio mem_comb mem_sync \ + forward_unit hazard_unit core_single core_pipe tiny_cpu_top; do + RTL+=" $PROJECT_ROOT/rtl/$f.sv" +done + +verilate() { # verilate + rm -rf "$3" + mkdir -p "$3" + # shellcheck disable=SC2086 + verilator --cc $RTL --top-module tiny_cpu_top --prefix "$2" -G"CORE=\"$1\"" \ + --Mdir "$3" -Wall -Wno-DECLFILENAME +} + +echo "[1/3] Verilating tiny_cpu_top for CORE=SINGLE and CORE=PIPE ..." +verilate SINGLE Vcpu_single "$BUILD/obj_single" +verilate PIPE Vcpu_pipe "$BUILD/obj_pipe" + +echo "[2/3] Compiling and linking WASM with Emscripten ..." +em++ -O2 -std=c++17 -DVL_IGNORE_UNKNOWN_ARCH \ + -I "$BUILD/obj_single" -I "$BUILD/obj_pipe" -I "$SCRIPT_DIR" -I "$VROOT/include" \ + "$SCRIPT_DIR/bindings.cpp" \ + "$BUILD"/obj_single/Vcpu_single*.cpp \ + "$BUILD"/obj_pipe/Vcpu_pipe*.cpp \ + "$VROOT/include/verilated.cpp" \ + "$VROOT/include/verilated_threads.cpp" \ + "$SCRIPT_DIR/emscripten_stubs.cpp" \ + -lembind \ + -s MODULARIZE=1 -s EXPORT_ES6=1 -s ALLOW_MEMORY_GROWTH=1 \ + -s EXPORT_NAME=createTinyCpu \ + -o "$OUT_DIR/tiny_cpu.mjs" + +echo "[3/3] Checking the size budget ..." +WASM="$OUT_DIR/tiny_cpu.wasm" +RAW=$(wc -c < "$WASM") +GZ=$(gzip -9 -c "$WASM" | wc -c) +echo " tiny_cpu.wasm: $RAW bytes raw, $GZ bytes gzipped (budget ${SIZE_BUDGET_KB} KB gzipped)" +if [ "$GZ" -gt $((SIZE_BUDGET_KB * 1024)) ]; then + echo "ERROR: gzipped WASM exceeds the ${SIZE_BUDGET_KB} KB budget" >&2 + exit 1 +fi +echo "Done: $OUT_DIR/tiny_cpu.mjs + tiny_cpu.wasm" diff --git a/wasm/harness.cpp b/wasm/harness.cpp new file mode 100644 index 0000000..42caeca --- /dev/null +++ b/wasm/harness.cpp @@ -0,0 +1,386 @@ +// Phase 5: both microarchitectures of tiny_cpu_top in one WebAssembly module. +// +// tiny_cpu_top is Verilated twice - CORE="SINGLE" and CORE="PIPE", distinct --prefix - and each +// model is wrapped by a Model. TinyCpuSim owns both and selects one with reset(core). +// Program memory is written into both, so either core can be selected after loading. +// +// Nothing here re-implements the design: every value returned comes from an output port of the +// Verilated model. The one exception is data memory. The RTL does not expose it, so the harness +// keeps a shadow copy updated from the retire records (rvfi_mem_*), which the RTL drives with the +// exact lane-positioned store data. Loads never change memory, so the shadow is exact. +#include "Vcpu_pipe.h" +#include "Vcpu_single.h" +#include "verilated.h" + +#include + +#include +#include +#include +#include +#include + +using emscripten::val; + +namespace { + +constexpr uint32_t IMEM_WORDS = 1024; +constexpr uint32_t DMEM_WORDS = 1024; +constexpr uint32_t DMEM_BASE = 0x0001'0000; +constexpr uint32_t DMEM_BYTES = DMEM_WORDS * 4; +constexpr uint32_t CONSOLE_ADDR = 0x0002'0000; + +// Everything one clock edge exposes, copied out of the model. +struct Snap { + bool halted = false, fault = false; + bool rvfi_valid = false, rvfi_halt = false, rvfi_cycle_dep = false, rvfi_misaligned = false; + uint32_t order = 0, pc = 0, insn = 0, rd_wdata = 0, pc_wdata = 0; + uint32_t mem_addr = 0, mem_rdata = 0, mem_wdata = 0; + uint8_t rd_addr = 0, mem_rmask = 0, mem_wmask = 0; + uint32_t dbg_pc = 0, instret = 0, cycle = 0; + uint8_t stage_valid = 0; + std::array stage_pc{}, stage_insn{}; + bool stall = false, load_use = false, flush_ifid = false, flush_idex = false; + uint8_t fwd_a = 0, fwd_b = 0; + std::array regs{}; + bool console_valid = false; + uint8_t console_data = 0; + bool tohost_valid = false; + uint32_t tohost_data = 0; +}; + +class ModelIface { +public: + virtual ~ModelIface() = default; + virtual void setForwarding(bool on) = 0; + virtual void assertReset() = 0; // async reset held; the clock may still run + virtual void releaseReset() = 0; // rst_n high; the next edge executes instruction 0 + virtual void writeWord(bool dmem, uint32_t index, uint32_t data) = 0; // requires reset held + virtual void tick() = 0; // one rising clock edge + virtual Snap snapshot() const = 0; + virtual void clearRun() = 0; // console and shadow tohost, for a new run + virtual std::vector readDmem(uint32_t offset, uint32_t len) const = 0; + virtual const std::string& console() const = 0; +}; + +template +class Model final : public ModelIface { +public: + Model() : ctx_(new VerilatedContext), top_(new Top(ctx_, "TOP")), dmem_(DMEM_BYTES, 0) { + top_->clk = 0; + top_->rst_n = 0; + top_->cfg_forward_en = 1; + top_->load_en = 0; + top_->load_sel = 0; + top_->load_addr = 0; + top_->load_data = 0; + top_->eval(); + } + + ~Model() override { + top_->final(); + delete top_; + delete ctx_; + } + + void setForwarding(bool on) override { top_->cfg_forward_en = on ? 1 : 0; } + + void assertReset() override { + top_->load_en = 0; + top_->rst_n = 0; + top_->clk = 0; + top_->eval(); + } + + void releaseReset() override { + top_->rst_n = 1; + top_->clk = 0; + top_->eval(); + } + + void writeWord(bool dmem, uint32_t index, uint32_t data) override { + top_->load_en = 1; + top_->load_sel = dmem ? 1 : 0; + top_->load_addr = index; + top_->load_data = data; + tick(); + top_->load_en = 0; + if (dmem) { + for (uint32_t b = 0; b < 4; ++b) dmem_[index * 4 + b] = (data >> (8 * b)) & 0xFF; + } + } + + void tick() override { + top_->clk = 0; + top_->eval(); + top_->clk = 1; + top_->eval(); + if (top_->console_valid) console_.push_back(static_cast(top_->console_data)); + if (top_->rvfi_valid && top_->rvfi_mem_wmask && top_->rvfi_mem_addr >= DMEM_BASE && + top_->rvfi_mem_addr < DMEM_BASE + DMEM_BYTES) { + const uint32_t off = top_->rvfi_mem_addr - DMEM_BASE; + for (uint32_t b = 0; b < 4; ++b) { + if (top_->rvfi_mem_wmask >> b & 1) dmem_[off + b] = (top_->rvfi_mem_wdata >> (8 * b)) & 0xFF; + } + } + } + + Snap snapshot() const override { + Snap s; + s.halted = top_->halted; + s.fault = top_->fault; + s.rvfi_valid = top_->rvfi_valid; + s.rvfi_halt = top_->rvfi_halt; + s.rvfi_cycle_dep = top_->rvfi_cycle_dep; + s.rvfi_misaligned = top_->rvfi_misaligned; + s.order = top_->rvfi_order; + s.pc = top_->rvfi_pc; + s.insn = top_->rvfi_insn; + s.rd_addr = top_->rvfi_rd_addr; + s.rd_wdata = top_->rvfi_rd_wdata; + s.pc_wdata = top_->rvfi_pc_wdata; + s.mem_addr = top_->rvfi_mem_addr; + s.mem_rmask = top_->rvfi_mem_rmask; + s.mem_wmask = top_->rvfi_mem_wmask; + s.mem_rdata = top_->rvfi_mem_rdata; + s.mem_wdata = top_->rvfi_mem_wdata; + s.dbg_pc = top_->dbg_pc; + s.instret = top_->dbg_instret; + s.cycle = top_->dbg_cycle; + s.stage_valid = top_->dbg_stage_valid; + for (uint32_t i = 0; i < 5; ++i) { + s.stage_pc[i] = top_->dbg_stage_pc[i]; + s.stage_insn[i] = top_->dbg_stage_insn[i]; + } + s.stall = top_->dbg_stall; + s.load_use = top_->dbg_load_use; + s.flush_ifid = top_->dbg_flush_ifid; + s.flush_idex = top_->dbg_flush_idex; + s.fwd_a = top_->dbg_fwd_a; + s.fwd_b = top_->dbg_fwd_b; + for (uint32_t i = 0; i < 32; ++i) s.regs[i] = top_->dbg_regs[i]; + s.console_valid = top_->console_valid; + s.console_data = top_->console_data; + s.tohost_valid = top_->tohost_valid; + s.tohost_data = top_->tohost_data; + return s; + } + + void clearRun() override { console_.clear(); } + + std::vector readDmem(uint32_t offset, uint32_t len) const override { + std::vector out; + for (uint32_t i = 0; i < len && offset + i < DMEM_BYTES; ++i) out.push_back(dmem_[offset + i]); + return out; + } + + const std::string& console() const override { return console_; } + +private: + VerilatedContext* ctx_; + Top* top_; + std::vector dmem_; + std::string console_; +}; + +// Length of a JS array-like (plain array or typed array). +uint32_t lengthOf(const val& arr) { return arr["length"].as(); } + +} // namespace + +class TinyCpuSim { +public: + TinyCpuSim() + : single_(std::make_unique>()), pipe_(std::make_unique>()) { + active_ = single_.get(); + } + + // Selects the core and asserts reset on both models. Returns false for an unknown core name. + bool reset(const std::string& core) { + if (core == "SINGLE") { + active_ = single_.get(); + } else if (core == "PIPE") { + active_ = pipe_.get(); + } else { + return false; + } + single_->assertReset(); + pipe_->assertReset(); + for (ModelIface* m : {static_cast(single_.get()), static_cast(pipe_.get())}) { + m->setForwarding(forward_); + m->clearRun(); + } + cycles_ = 0; + started_ = false; + return true; + } + + // Takes effect at the next reset(). Forwarding only matters for PIPE; SINGLE ignores it. + void setForwarding(bool on) { forward_ = on; } + + // Loads IMEM from a JS array of 32-bit words. Both models are held in reset while loading. + bool loadImem(const val& words) { + const uint32_t n = lengthOf(words); + if (n > IMEM_WORDS) return false; + single_->assertReset(); + pipe_->assertReset(); + for (uint32_t i = 0; i < IMEM_WORDS; ++i) { + const uint32_t w = i < n ? words[i].as() : 0; + single_->writeWord(false, i, w); + pipe_->writeWord(false, i, w); + } + return true; + } + + // Loads DMEM from a JS array of bytes (little-endian words), padded with zeros. + bool loadDmem(const val& bytes) { + const uint32_t n = lengthOf(bytes); + if (n > DMEM_BYTES) return false; + single_->assertReset(); + pipe_->assertReset(); + for (uint32_t i = 0; i < DMEM_WORDS; ++i) { + uint32_t w = 0; + for (uint32_t b = 0; b < 4; ++b) { + const uint32_t idx = i * 4 + b; + if (idx < n) w |= static_cast(bytes[idx].as()) << (8 * b); + } + single_->writeWord(true, i, w); + pipe_->writeWord(true, i, w); + } + return true; + } + + // Releases reset on the selected core. Cycle counting starts here. + void start() { + active_->releaseReset(); + cycles_ = 0; + started_ = true; + } + + // One clock edge. Returns the CycleState for that edge (see docs/STATE_SCHEMA.md). + val step() { + if (!started_) return val::null(); + if (!finished()) { + active_->tick(); + ++cycles_; + } + return toState(active_->snapshot()); + } + + // Steps until halted, faulted or maxCycles, returning every CycleState (the last is the halt). + val run(uint32_t maxCycles) { + val out = val::array(); + for (uint32_t i = 0; i < maxCycles && !finished(); ++i) { + out.call("push", step()); + } + return out; + } + + // Steps until halted, faulted or maxCycles without building per-cycle states. + val runFast(uint32_t maxCycles) { + if (started_) { + for (uint32_t i = 0; i < maxCycles && !finished(); ++i) { + active_->tick(); + ++cycles_; + } + } + const Snap s = active_->snapshot(); + val out = val::object(); + out.set("cycles", cycles_); + out.set("instret", s.instret); + out.set("halted", s.halted); + out.set("fault", s.fault); + out.set("tohost", s.tohost_valid ? val(s.tohost_data) : val::null()); + out.set("console", active_->console()); + return out; + } + + val getRegs() const { + const Snap s = active_->snapshot(); + val out = val::array(); + for (uint32_t i = 0; i < 32; ++i) out.call("push", s.regs[i]); + return out; + } + + // Bytes of data memory at an absolute address (DMEM_BASE-relative), from the shadow copy. + val readDmem(uint32_t addr, uint32_t len) const { + val out = val::array(); + if (addr < DMEM_BASE) return out; + for (uint8_t b : active_->readDmem(addr - DMEM_BASE, len)) out.call("push", static_cast(b)); + return out; + } + + std::string getConsole() const { return active_->console(); } + uint32_t getCycleCount() const { return cycles_; } + bool isHalted() const { return active_->snapshot().halted; } + bool isFault() const { return active_->snapshot().fault; } + +private: + bool finished() const { + const Snap s = active_->snapshot(); + return s.halted || s.fault; + } + + val toState(const Snap& s) const { + val st = val::object(); + st.set("cycle", cycles_); + st.set("halted", s.halted); + st.set("fault", s.fault); + val stages = val::array(); + static const char* kNames[5] = {"IF", "ID", "EX", "MEM", "WB"}; + for (uint32_t i = 0; i < 5; ++i) { + val stg = val::object(); + stg.set("name", std::string(kNames[i])); + stg.set("valid", static_cast(s.stage_valid >> i & 1)); + stg.set("pc", s.stage_pc[i]); + stg.set("insn", s.stage_insn[i]); + stages.call("push", stg); + } + st.set("stages", stages); + val hz = val::object(); + hz.set("stall", s.stall); + hz.set("loadUse", s.load_use); + hz.set("flushIFID", s.flush_ifid); + hz.set("flushIDEX", s.flush_idex); + hz.set("fwdA", static_cast(s.fwd_a)); + hz.set("fwdB", static_cast(s.fwd_b)); + st.set("hazard", hz); + val regs = val::array(); + for (uint32_t i = 0; i < 32; ++i) regs.call("push", s.regs[i]); + st.set("regs", regs); + if (s.rvfi_valid) { + val r = val::object(); + r.set("order", s.order); + r.set("pc", s.pc); + r.set("insn", s.insn); + r.set("rdAddr", static_cast(s.rd_addr)); + r.set("rdWdata", s.rd_wdata); + r.set("pcWdata", s.pc_wdata); + r.set("memAddr", s.mem_addr); + r.set("memRmask", static_cast(s.mem_rmask)); + r.set("memWmask", static_cast(s.mem_wmask)); + r.set("memRdata", s.mem_rdata); + r.set("memWdata", s.mem_wdata); + r.set("misaligned", s.rvfi_misaligned); + r.set("halt", s.rvfi_halt); + r.set("cycleDep", s.rvfi_cycle_dep); + st.set("retired", r); + } else { + st.set("retired", val::null()); + } + st.set("console", s.console_valid ? val(static_cast(s.console_data)) : val::null()); + st.set("tohost", s.tohost_valid ? val(s.tohost_data) : val::null()); + val counters = val::object(); + counters.set("cycle", cycles_); + counters.set("instret", s.instret); + st.set("counters", counters); + return st; + } + + std::unique_ptr> single_; + std::unique_ptr> pipe_; + ModelIface* active_ = nullptr; + bool forward_ = true; + bool started_ = false; + uint32_t cycles_ = 0; +}; diff --git a/wasm/test.mjs b/wasm/test.mjs new file mode 100644 index 0000000..aaec93a --- /dev/null +++ b/wasm/test.mjs @@ -0,0 +1,132 @@ +// Headless check of the WASM build (Phase 5). Run after `bash wasm/build.sh`: +// +// node wasm/test.mjs +// +// For every fixture in wasm/test/fixtures.json (generated from the golden ISS) and every core - SINGLE, +// and PIPE with forwarding on and off - it checks: the console output, the final registers (except +// timing-dependent ones), data memory, the instruction count, the SHA-256 of the retire stream, the +// halt/tohost behaviour, and the cycle count from the pipeline model. Exit status 0 = all good. +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const root = join(here, ".."); +const fixtures = JSON.parse(readFileSync(join(here, "test", "fixtures.json"), "utf8")); +const { default: createTinyCpu } = await import(pathToFileURL(join(root, "web", "public", "tiny_cpu.mjs")).href); +const mod = await createTinyCpu(); + +const MAX_CYCLES = 200_000; +let failures = 0; +let checks = 0; + +function check(cond, msg) { + checks += 1; + if (!cond) { + failures += 1; + console.error(` FAIL: ${msg}`); + } +} + +function words(hex) { + const b = Buffer.from(hex, "hex"); + const out = []; + for (let i = 0; i + 3 < b.length; i += 4) out.push(b.readUInt32LE(i)); + return out; +} + +// Must match sim/golden/wasm_fixtures.py retire_digest(): 13 little-endian u32 per retire record. +function retireDigest(states) { + const h = createHash("sha256"); + const buf = Buffer.alloc(13 * 4); + for (const s of states) { + const r = s.retired; + if (!r) continue; + const fields = [ + r.order, r.pc, r.insn, r.rdAddr, r.cycleDep ? 0 : r.rdWdata, r.pcWdata, r.memAddr, + r.memRmask, r.memWmask, r.cycleDep ? 0 : r.memRdata, r.memWdata, r.misaligned ? 1 : 0, r.halt ? 1 : 0, + ]; + fields.forEach((v, i) => buf.writeUInt32LE(v >>> 0, 4 * i)); + h.update(buf); + } + return h.digest("hex"); +} + +function runOne(name, fx, core, forward) { + const tag = `${name} ${core}${core === "PIPE" ? (forward ? " fwd" : " nofwd") : ""}`; + const sim = new mod.TinyCpuSim(); + try { + sim.setForwarding(forward); + check(sim.reset(core) === true, `${tag}: reset accepts ${core}`); + check(sim.loadImem(words(fx.imem)) === true, `${tag}: loadImem`); + check(sim.loadDmem(Array.from(Buffer.from(fx.dmem, "hex"))) === true, `${tag}: loadDmem`); + sim.start(); + const states = sim.run(MAX_CYCLES); + const last = states[states.length - 1]; + + check(last && last.halted && !last.fault, `${tag}: halted without fault`); + check(last.tohost === 1, `${tag}: tohost == 1 (PASS), got ${last.tohost}`); + check(states.every((s, i) => s.cycle === i + 1), `${tag}: cycle counter counts edges from 1`); + + const retired = states.filter((s) => s.retired).map((s) => s.retired); + check(retired.length === fx.instret, `${tag}: instret ${retired.length} != ${fx.instret}`); + check(last.counters.instret === fx.instret, `${tag}: counters.instret`); + + const console_ = states.filter((s) => s.console !== null).map((s) => String.fromCharCode(s.console)).join(""); + check(console_ === fx.console, `${tag}: console ${JSON.stringify(console_)} != ${JSON.stringify(fx.console)}`); + check(sim.getConsole() === fx.console, `${tag}: getConsole()`); + + const regs = sim.getRegs(); + const timing = new Set(fx.timingRegs); + for (let r = 0; r < 32; r++) { + if (!timing.has(r)) check(regs[r] === fx.regs[r], `${tag}: x${r} = ${regs[r]} != ${fx.regs[r]}`); + } + + if (fx.expect.dmemAddr !== undefined) { + const got = Buffer.from(sim.readDmem(fx.expect.dmemAddr, fx.expect.dmemHex.length / 2)).toString("hex"); + check(got === fx.expect.dmemHex, `${tag}: data memory ${got} != ${fx.expect.dmemHex}`); + } + + check(retireDigest(states) === fx.digest, `${tag}: retire digest mismatch`); + + const want = core === "SINGLE" ? fx.cycles.single : forward ? fx.cycles.pipeFwd : fx.cycles.pipeNoFwd; + check(states.length === want, `${tag}: ${states.length} cycles, model says ${want}`); + return states.length; + } finally { + sim.delete(); + } +} + +for (const [name, fx] of Object.entries(fixtures)) { + console.log(name); + runOne(name, fx, "SINGLE", true); + runOne(name, fx, "PIPE", true); + runOne(name, fx, "PIPE", false); +} + +// API edges: unknown core, oversized images, step() before start(), step() after halt. +{ + const sim = new mod.TinyCpuSim(); + check(sim.reset("MULTI") === false, "unknown core is rejected"); + check(sim.loadImem(new Array(1025).fill(0)) === false, "oversized IMEM is rejected"); + check(sim.loadDmem(new Array(4097).fill(0)) === false, "oversized DMEM is rejected"); + check(sim.step() === null, "step() before start() returns null"); + const fx = fixtures["hazard_free/fib"]; + sim.reset("SINGLE"); + sim.loadImem(words(fx.imem)); + sim.loadDmem(Array.from(Buffer.from(fx.dmem, "hex"))); + sim.start(); + const first = sim.step(); + check(first.cycle === 1 && first.halted === false, "first step is cycle 1"); + check(first.hazard.stall === false && first.stages[0].name === "IF", "state shape"); + const summary = sim.runFast(MAX_CYCLES); + check(summary.halted && summary.cycles === fixtures["hazard_free/fib"].cycles.single, "runFast cycle count"); + check(summary.tohost === 1 && summary.console === "", "runFast summary fields"); + const halted = sim.step(); + check(halted.halted && halted.cycle === summary.cycles, "step() after halt does not advance"); + sim.delete(); +} + +console.log(`${checks} checks, ${failures} failed`); +process.exit(failures === 0 ? 0 : 1); diff --git a/wasm/test/fixtures.json b/wasm/test/fixtures.json new file mode 100644 index 0000000..5186297 --- /dev/null +++ b/wasm/test/fixtures.json @@ -0,0 +1,204 @@ +{ + "hazard_dense/3": { + "console": "", + "cycles": { + "pipeFwd": 120, + "pipeNoFwd": 137, + "single": 107 + }, + "digest": "1f805214ead1ff4f7706b6ef0cd97545ed56ac44623342c85337c980e7808846", + "dmem": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "expect": {}, + "imem": "37040100130000001300000013040400b740eb3c13000000130000009380d0ff3751b797130000001300000013012109b7a1528b13000000130000009381a1b437626321130000001300000013029236b762b55e13000000130000009382421a130000001300000013000000b302520013f2c1709392e200a3215406b3005100a31d4404131272018310b4070352940493915201a30c340483411405b3d2314063963000a302340093e272299382811e830094070341840593a061411352624093d2f2408321b402934032c493b0c10e3382500093e1d12313b131072304440413b131fa8301d400230c3402a3081406930252916318310063e611000311b40393512200337221003703000013000000130000001303c312670003000342e406b3a032006f00c000b330120003018407b3e23000138200bd6f00800033b2420003516400638411008300d4068340840283206402f32120c003525406b3d022408302f404031254031362420a93a2608ea3173400a3261400b3f152009340b296b3d2500063843000830044002303540483207401130000001300000013000000b70f02001300000013000000938f4f00370f00001300000013000000130f1f00130000001300000023a0ef01", + "instret": 107, + "regs": [ + 0, + 0, + 0, + 0, + 164, + 4294965711, + 300, + 0, + 65536, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 131076 + ], + "timingRegs": [] + }, + "hazard_free/bubble_sort": { + "console": "", + "cycles": { + "pipeFwd": 863, + "pipeNoFwd": 863, + "single": 749 + }, + "digest": "50337c05646566cdc7fd48bba7efd065d3b71707a1b28125407878ffbb4fe53f", + "dmem": "2a0000000700000063000000030000001200000040000000010000001b000000", + "expect": { + "dmemAddr": 65536, + "dmemHex": "010000000300000007000000120000001b0000002a0000004000000063000000" + }, + "imem": "370401001300000013000000130404001300000013000000370900001300000013000000130979001300000013000000635a200b1300000013000000b7020000130000001300000093820200130000001300000013030400130000001300000063d6220713000000130000008325030013000000130000000326430013000000130000006352b60213000000130000002320c30013000000130000002322b30013000000130000009382120013000000130000001303430013000000130000006ff01ffa13000000130000001309f9ff13000000130000006ff09ff51300000013000000b70f02001300000013000000938f4f001300000013000000370f00001300000013000000130f1f00130000001300000023a0ef011300000013000000", + "instret": 749, + "regs": [ + 0, + 0, + 0, + 0, + 0, + 1, + 65540, + 0, + 65536, + 0, + 0, + 1, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 131076 + ], + "timingRegs": [] + }, + "hazard_free/console_and_csr": { + "console": "Hi, pipe!\n!", + "cycles": { + "pipeFwd": 218, + "pipeNoFwd": 218, + "single": 214 + }, + "digest": "6da00379929588d3923278b1ffdc8760d84c644b61ff270d0e04209dfa639386", + "dmem": "00000000000000000000000000000000", + "expect": {}, + "imem": "370a01001300000013000000130a0a001300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e8e0413000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e9e0613000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938ece0213000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e0e0213000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e0e0713000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e9e0613000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e0e0713000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e5e0613000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e1e0213000000130000002300de011300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938eae0013000000130000002300de011300000013000000f32a20c0130000001300000023205a011300000013000000732b00c01300000013000000f32b10c01300000013000000732c20c8130000001300000023228a011300000013000000b70c02001300000013000000938c8c00130000001300000003ad0c0013000000130000000f0000001300000013000000370e02001300000013000000130e0e001300000013000000b70e00001300000013000000938e1e0213000000130000002300de011300000013000000b70f02001300000013000000938f4f001300000013000000370f00001300000013000000130f1f00130000001300000023a0ef011300000013000000", + "instret": 214, + "regs": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 65536, + 156, + 162, + 165, + 0, + 131080, + 180, + 0, + 131072, + 33, + 1, + 131076 + ], + "timingRegs": [ + 22, + 23, + 26 + ] + }, + "hazard_free/fib": { + "console": "", + "cycles": { + "pipeFwd": 407, + "pipeNoFwd": 407, + "single": 361 + }, + "digest": "fef0c4b1e25420c741e7596fdbbcc81b6b398fb817689aeec6195c200cb51f80", + "dmem": "00000000", + "expect": { + "dmemAddr": 65536, + "dmemHex": "6d1a0000" + }, + "imem": "b70200001300000013000000938242011300000013000000370300001300000013000000130303001300000013000000b70300001300000013000000938313001300000013000000638402041300000013000000330e7300130000001300000013830300130000001300000093030e0013000000130000009382f2ff13000000130000006ff05ffc1300000013000000370401001300000013000000130404001300000013000000232064001300000013000000b70f02001300000013000000938f4f001300000013000000370f00001300000013000000130f1f00130000001300000023a0ef011300000013000000", + "instret": 361, + "regs": [ + 0, + 0, + 0, + 0, + 0, + 0, + 6765, + 10946, + 65536, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 10946, + 0, + 1, + 131076 + ], + "timingRegs": [] + } +} diff --git a/web/public/tiny_cpu.mjs b/web/public/tiny_cpu.mjs new file mode 100644 index 0000000..efff4ce --- /dev/null +++ b/web/public/tiny_cpu.mjs @@ -0,0 +1,2 @@ +async function createTinyCpu(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";if(ENVIRONMENT_IS_NODE){const{createRequire}=await(import("node:module"));var require=createRequire(import.meta.url)}var thisProgram="./this.program";var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"])return Module["locateFile"](path,scriptDirectory);return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("node:fs");if(_scriptName.startsWith("file:"))scriptDirectory=require("node:path").dirname(require("node:url").fileURLToPath(_scriptName))+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename);return ret};readAsync=async(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):filename;var ret=fs.readFileSync(filename,binary?void 0:"utf8");return ret};if(process.argv.length>1)thisProgram=process.argv[1].replace(/\\/g,"/");process.argv.slice(2);0}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}if(ENVIRONMENT_IS_WORKER)readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)};readAsync=async url=>{if(isFileURI(url))return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)});var response=await fetch(url,{credentials:"same-origin"});if(response.ok)return response.arrayBuffer();throw new Error(response.status+" : "+response.url)}}var out=console.log.bind(console);var err=console.error.bind(console);var ABORT=false;var isFileURI=filename=>filename.startsWith("file://");function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);HEAP16=new Int16Array(b);HEAPU8=new Uint8Array(b);HEAPU16=new Uint16Array(b);HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);HEAPF32=new Float32Array(b);HEAPF64=new Float64Array(b);HEAP64=new BigInt64Array(b);HEAPU64=new BigUint64Array(b)}function preRun(){var preRun=Module["preRun"];if(preRun){if(typeof preRun==="function")preRun=[preRun];onPreRuns.push(...preRun)}callRuntimeCallbacks(onPreRuns)}function initRuntime(){0;if(!Module["noFSInit"]&&!FS.initialized)FS.init();TTY.init();wasmExports["__wasm_call_ctors"]();FS.ignorePermissions=false}function postRun(){var postRun=Module["postRun"];if(postRun){if(typeof postRun==="function")postRun=[postRun];onPostRuns.push(...postRun)}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";throw new WebAssembly.RuntimeError(what)}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"])return locateFile("tiny_cpu.wasm");return new URL("tiny_cpu.wasm",import.meta.url).href}function getBinarySync(file){if(readBinary)return readBinary(file);throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(true)try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE)try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={env:wasmImports,wasi_snapshot_preview1:wasmImports};return imports}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0)callbacks.shift()(Module)};var onPostRuns=[];var onPreRuns=[];var stackRestore=val=>__emscripten_stack_restore(val);var stackSave=()=>_emscripten_stack_get_current();var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder)return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr));var str="";while(idx>10,56320|ch&1023)}}return str};var HEAPU8;var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=()=>{abort()};var HEAP32;var syscallGetVarargI=()=>{var ret=HEAP32[+SYSCALLS.varargs>>2];SYSCALLS.varargs+=4;return ret};var syscallGetVarargP=syscallGetVarargI;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(filename).slice(1),normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last===".")parts.splice(i,1);else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot)for(;up;up--)parts.unshift("..");return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.slice(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute)path=".";if(path&&trailingSlash)path+="/";return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir)return".";if(dir)dir=dir.slice(0,-1);return root+dir},basename:path=>path&&path.match(/([^\/]+|\/)\/*$/)[1],join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};var initRandomFill=()=>{if(ENVIRONMENT_IS_NODE){var nodeCrypto=require("node:crypto");return view=>(nodeCrypto.randomFillSync(view),0)}return view=>(crypto.getRandomValues(view),0)};var randomFill=view=>(randomFill=initRandomFill())(view);var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!=="string")throw new TypeError("Arguments to path.resolve must be strings");else if(!path)return"";resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).slice(1);to=PATH_FS.resolve(to).slice(1);function trim(arr){var start=0;for(;start=0;end--)if(arr[end]!=="")break;if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else len+=3}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var intArrayFromString=(stringy,dontAddNull,length)=>{var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array};var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var buf=Buffer.alloc(256);var bytesRead=0;try{bytesRead=fs.readSync(process.stdin.fd,buf,0,256)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0)result=buf.toString("utf-8",0,bytesRead)}else if(globalThis.window?.prompt){result=window.prompt("Input: ");if(result!==null)result+="\n"}if(!result)return null;FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty)throw new FS.ErrnoError(43);stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char)throw new FS.ErrnoError(60);var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output));tty.output=[]}else if(val!=0)tty.output.push(val)},fsync(tty){if(tty.output?.length>0){err(UTF8ArrayToString(tty.output));tty.output=[]}}}};var mmapAlloc=()=>{abort()};var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16895,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode))throw new FS.ErrnoError(63);MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=MEMFS.emptyFileContents??=new Uint8Array(0)}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.atime=node.mtime=node.ctime=Date.now();if(parent){parent.contents[name]=node;parent.atime=parent.mtime=parent.ctime=node.atime}return node},getFileDataAsTypedArray(node){return node.contents.subarray(0,node.usedBytes)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents.length;if(prevCapacity>=newCapacity)return;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity<1048576?2:1.125)>>>0);if(prevCapacity)newCapacity=Math.max(newCapacity,256);var oldContents=MEMFS.getFileDataAsTypedArray(node);node.contents=new Uint8Array(newCapacity);node.contents.set(oldContents)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;var oldContents=node.contents;node.contents=new Uint8Array(newSize);node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)));node.usedBytes=newSize},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode))attr.size=4096;else if(FS.isFile(node.mode))attr.size=node.usedBytes;else if(FS.isLink(node.mode))attr.size=node.link.length;else attr.size=0;attr.atime=new Date(node.atime);attr.mtime=new Date(node.mtime);attr.ctime=new Date(node.ctime);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){for(const key of["mode","atime","mtime","ctime"])if(attr[key]!=null)node[key]=attr[key];if(attr.size!==void 0)MEMFS.resizeFileStorage(node,attr.size)},lookup(parent,name){if(!MEMFS.doesNotExistError){MEMFS.doesNotExistError=new FS.ErrnoError(44);MEMFS.doesNotExistError.stack=""}throw MEMFS.doesNotExistError},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node&&FS.isDir(old_node.mode))for(var i in new_node.contents)throw new FS.ErrnoError(55);delete old_node.parent.contents[old_node.name];new_dir.contents[new_name]=old_node;old_node.name=new_name;new_dir.ctime=new_dir.mtime=old_node.parent.ctime=old_node.parent.mtime=Date.now()},unlink(parent,name){delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents)throw new FS.ErrnoError(55);delete parent.contents[name];parent.ctime=parent.mtime=Date.now()},readdir(node){return[".","..",...Object.keys(node.contents)]},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,41471,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode))throw new FS.ErrnoError(28);return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);buffer.set(contents.subarray(position,position+size),offset);return size},write(stream,buffer,offset,length,position,canOwn){if(buffer.buffer===HEAP8.buffer)canOwn=false;if(!length)return 0;var node=stream.node;node.mtime=node.ctime=Date.now();if(canOwn){node.contents=buffer.subarray(offset,offset+length);node.usedBytes=length}else if(!node.usedBytes&&!position){node.contents=buffer.slice(offset,offset+length);node.usedBytes=length}else{MEMFS.expandFileStorage(node,position+length);node.contents.set(buffer.subarray(offset,offset+length),position);node.usedBytes=Math.max(node.usedBytes,position+length)}return length},llseek(stream,offset,whence){var position=offset;if(whence===1)position+=stream.position;else if(whence===2)if(FS.isFile(stream.node.mode))position+=stream.node.usedBytes;if(position<0)throw new FS.ErrnoError(28);return position},mmap(stream,length,position,prot,flags){if(!FS.isFile(stream.node.mode))throw new FS.ErrnoError(43);var ptr;var allocated;var contents=stream.node.contents;if(!(flags&2)&&contents.buffer===HEAP8.buffer){allocated=false;ptr=contents.byteOffset}else{allocated=true;ptr=mmapAlloc();if(!ptr)throw new FS.ErrnoError(48);if(contents){if(position>0||position+length{if(typeof str!=="string")return str;var flags={r:0,"r+":2,w:577,"w+":578,a:1089,"a+":1090}[str];if(typeof flags==="undefined")throw new Error(`Unknown file open mode: ${str}`);return flags};var FS_fileDataToTypedArray=data=>{if(typeof data==="string")data=intArrayFromString(data,true);if(!data.subarray)data=new Uint8Array(data);return data};var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=365;if(canWrite)mode|=146;return mode};var asyncLoad=async url=>{var arrayBuffer=await readAsync(url);return new Uint8Array(arrayBuffer)};var FS_createDataFile=(...args)=>FS.createDataFile(...args);var getUniqueRunDependency=id=>id;var dependenciesPromise=null;var resolveRunDependencies=async()=>dependenciesPromise;var runDependencies=0;var dependenciesPromiseResolve=null;var removeRunDependency=()=>{runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(!runDependencies)dependenciesPromiseResolve()};var addRunDependency=()=>{if(!runDependencies)dependenciesPromise=new Promise(resolve=>dependenciesPromiseResolve=resolve);runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)};var preloadPlugins=[];var FS_handledByPreloadPlugin=async(byteArray,fullname)=>{if(typeof Browser!=="undefined")Browser.init();for(var plugin of preloadPlugins)if(plugin["canHandle"](fullname))return plugin["handle"](byteArray,fullname);return byteArray};var FS_preloadFile=async(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;getUniqueRunDependency(`cp ${fullname}`);addRunDependency();try{var byteArray=url;if(typeof url==="string")byteArray=await asyncLoad(url);byteArray=await FS_handledByPreloadPlugin(byteArray,fullname);preFinish?.();if(!dontCreateFile)FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}finally{removeRunDependency()}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:[],currentPath:"/",initialized:false,ignorePermissions:true,syncFSRequests:0,ErrnoError:class{name="ErrnoError";constructor(errno){this.errno=errno}},FSStream:class{shared={};get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{node_ops={};stream_ops={};readMode=365;writeMode=146;mounted=null;constructor(parent,name,mode,rdev){if(!parent)parent=this;this.parent=parent;this.mount=parent.mount;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.rdev=rdev;this.atime=this.mtime=this.ctime=Date.now()}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}addListener(cb,exclusive=false){var entry={cb,exclusive};var listeners=this.listeners??=new Set;listeners.add(entry);return{listeners,entry}}notifyListeners(flags){if(!this.listeners)return;var excl;for(var entry of this.listeners)if(entry.exclusive)(excl||=[]).push(entry);else entry.cb(flags);if(excl){var i=(this.exclTurn||0)%excl.length;this.exclTurn=i+1;excl[i].cb(flags)}}},lookupPath(path,opts={}){if(!path)throw new FS.ErrnoError(44);opts.follow_mount??=true;if(!PATH.isAbs(path))path=FS.cwd()+"/"+path;linkloop:for(var nlinks=0;nlinks<40;nlinks++){var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node)FS.nameTable[hash]=node.name_next;else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode)throw new FS.ErrnoError(errCode);var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name)return node}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512)perms+="w";return perms},nodePermissions(node,perms){if(FS.ignorePermissions)return 0;if(perms.includes("r")&&!(node.mode&292))return 2;if(perms.includes("w")&&!(node.mode&146))return 2;if(perms.includes("x")&&!(node.mode&73))return 2;return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){if(!FS.isDir(dir.mode))return 54;try{FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode)return errCode;if(isdir){if(!FS.isDir(node.mode))return 54;if(FS.isRoot(node)||FS.getPath(node)===FS.cwd())return 10}else if(FS.isDir(node.mode))return 31;return 0},mayOpen(node,flags){if(!node)return 44;if(FS.isLink(node.mode))return 32;var mode=FS.flagsToPermissionString(flags);if(FS.isDir(node.mode))if(mode!=="r"||flags&576)return 31;return FS.nodePermissions(node,mode)},checkOpExists(op,err){if(!op)throw new FS.ErrnoError(err);return op},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++)if(!FS.streams[fd])return fd;throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1)fd=FS.nextfd();stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},doSetAttr(stream,node,attr){var setattr=stream?.stream_ops.setattr;var arg=setattr?stream:node;setattr??=node.node_ops.setattr;FS.checkOpExists(setattr,63);try{setattr(arg,attr)}catch(e){if(e instanceof RangeError)throw new FS.ErrnoError(22);throw e}},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1)err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`);var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length)doCallback(null)}for(var mount of mounts)if(mount.type.syncfs)mount.type.syncfs(mount,populate,done);else done(null)},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root)throw new FS.ErrnoError(10);else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node))throw new FS.ErrnoError(10);if(!FS.isDir(node.mode))throw new FS.ErrnoError(54)}var mount={type,opts,mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root)FS.root=mountRoot;else if(node){node.mounted=mount;if(node.mount)node.mount.mounts.push(mount)}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node))throw new FS.ErrnoError(28);var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);for(var[hash,current]of Object.entries(FS.nameTable))while(current){var next=current.name_next;if(mounts.includes(current.mount))FS.destroyNode(current);current=next}node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name)throw new FS.ErrnoError(28);if(name==="."||name==="..")throw new FS.ErrnoError(20);var errCode=FS.mayCreate(parent,name);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.mknod)throw new FS.ErrnoError(63);return parent.node_ops.mknod(parent,name,mode,dev)},statfs(path){return FS.statfsNode(FS.lookupPath(path,{follow:true}).node)},statfsStream(stream){return FS.statfsNode(stream.node)},statfsNode(node){var rtn={bsize:4096,frsize:4096,blocks:1e6,bfree:5e5,bavail:5e5,files:FS.nextInode,ffree:FS.nextInode-1,fsid:42,flags:2,namelen:255};if(node.node_ops.statfs)Object.assign(rtn,node.node_ops.statfs(node.mount.opts.root));return rtn},create(path,mode=438){mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode=511){mode&=1023;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode=511){var dirs=path.split("/");var d="";for(var dir of dirs){if(!dir)continue;if(d||PATH.isAbs(path))d+="/";d+=dir;try{FS.mkdir(d,mode)}catch(e){if(e.errno!=20)throw e}}},mkdev(path,mode,dev){if(typeof dev==="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink(oldpath,newpath){if(!PATH_FS.resolve(oldpath))throw new FS.ErrnoError(44);var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent)throw new FS.ErrnoError(44);var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.symlink)throw new FS.ErrnoError(63);return parent.node_ops.symlink(parent,newname,oldpath)},link(oldpath,newpath,flags){var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent)throw new FS.ErrnoError(44);var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.link)throw new FS.ErrnoError(34);return parent.node_ops.link(parent,newname,oldpath,flags)},rename(old_path,new_path){var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount)throw new FS.ErrnoError(75);var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!==".")throw new FS.ErrnoError(28);relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!==".")throw new FS.ErrnoError(55);var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node)return;var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode)throw new FS.ErrnoError(errCode);errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode)throw new FS.ErrnoError(errCode);if(!old_dir.node_ops.rename)throw new FS.ErrnoError(63);if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node))throw new FS.ErrnoError(10);if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode)throw new FS.ErrnoError(errCode)}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name);if(new_node)FS.hashRemoveNode(new_node);old_node.parent=new_dir}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.rmdir)throw new FS.ErrnoError(63);if(FS.isMountpoint(node))throw new FS.ErrnoError(10);parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir(path){var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;var readdir=FS.checkOpExists(node.node_ops.readdir,54);return readdir(node)},unlink(path){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent)throw new FS.ErrnoError(44);var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode)throw new FS.ErrnoError(errCode);if(!parent.node_ops.unlink)throw new FS.ErrnoError(63);if(FS.isMountpoint(node))throw new FS.ErrnoError(10);parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink(path){var lookup=FS.lookupPath(path);var link=lookup.node;if(!link)throw new FS.ErrnoError(44);if(!link.node_ops.readlink)throw new FS.ErrnoError(28);return link.node_ops.readlink(link)},stat(path,dontFollow=false){var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;var getattr=FS.checkOpExists(node.node_ops.getattr,63);return getattr(node)},fstat(fd){var stream=FS.getStreamChecked(fd);var node=stream.node;var getattr=stream.stream_ops.getattr;var arg=getattr?stream:node;getattr??=node.node_ops.getattr;FS.checkOpExists(getattr,63);return getattr(arg)},lstat(path){return FS.stat(path,true)},doChmod(stream,node,mode,dontFollow=false){FS.doSetAttr(stream,node,{mode:mode&4095|node.mode&-4096,ctime:Date.now(),dontFollow})},chmod(path,mode,dontFollow=false){var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else node=path;FS.doChmod(null,node,mode,dontFollow)},lchmod(path,mode){FS.chmod(path,mode,true)},fchmod(fd,mode){var stream=FS.getStreamChecked(fd);FS.doChmod(stream,stream.node,mode,false)},doChown(stream,node,dontFollow=false){FS.doSetAttr(stream,node,{timestamp:Date.now(),dontFollow})},chown(path,uid,gid,dontFollow=false){var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else node=path;FS.doChown(null,node,dontFollow)},lchown(path,uid,gid){FS.chown(path,uid,gid,true)},fchown(fd,uid,gid){var stream=FS.getStreamChecked(fd);FS.doChown(stream,stream.node,false)},doTruncate(stream,node,len){if(FS.isDir(node.mode))throw new FS.ErrnoError(31);if(!FS.isFile(node.mode))throw new FS.ErrnoError(28);var errCode=FS.nodePermissions(node,"w");if(errCode)throw new FS.ErrnoError(errCode);FS.doSetAttr(stream,node,{size:len,timestamp:Date.now()})},truncate(path,len){if(len<0)throw new FS.ErrnoError(28);var node;if(typeof path==="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else node=path;FS.doTruncate(null,node,len)},ftruncate(fd,len){var stream=FS.getStreamChecked(fd);if(len<0||(stream.flags&2097155)===0)throw new FS.ErrnoError(28);FS.doTruncate(stream,stream.node,len)},utime(path,atime,mtime,dontFollow=false){var lookup=FS.lookupPath(path,{follow:!dontFollow});FS.doSetAttr(null,lookup.node,{atime,mtime,dontFollow})},open(path,flags,mode=438){if(path==="")throw new FS.ErrnoError(44);flags=FS_modeStringToFlags(flags);if(flags&64)mode=mode&4095|32768;else mode=0;var node;var isDirPath;if(typeof path==="object")node=path;else{isDirPath=path.endsWith("/");var lookup=FS.lookupPath(path,{follow:!(flags&131072),noent_okay:true});node=lookup.node;path=lookup.path}var created=false;if(flags&64)if(node){if(flags&128)throw new FS.ErrnoError(20)}else if(isDirPath)throw new FS.ErrnoError(31);else{node=FS.mknod(path,mode|511,0);created=true}if(!node)throw new FS.ErrnoError(44);if(FS.isChrdev(node.mode))flags&=-513;if(flags&65536&&!FS.isDir(node.mode))throw new FS.ErrnoError(54);if(!created){var errCode=FS.mayOpen(node,flags);if(errCode)throw new FS.ErrnoError(errCode)}if(flags&512&&!created)FS.truncate(node,0);flags&=-131713;var stream=FS.createStream({node,path:FS.getPath(node),flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open)stream.stream_ops.open(stream);if(created)FS.chmod(node,mode&511);return stream},close(stream){if(FS.isClosed(stream))throw new FS.ErrnoError(8);if(stream.getdents)stream.getdents=null;stream.node?.notifyListeners(32);try{if(stream.stream_ops.close)stream.stream_ops.close(stream)}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed(stream){return stream.fd===null},llseek(stream,offset,whence){if(FS.isClosed(stream))throw new FS.ErrnoError(8);if(!stream.seekable||!stream.stream_ops.llseek)throw new FS.ErrnoError(70);if(whence!=0&&whence!=1&&whence!=2)throw new FS.ErrnoError(28);stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read(stream,buffer,offset,length,position){if(length<0||position<0)throw new FS.ErrnoError(28);if(FS.isClosed(stream))throw new FS.ErrnoError(8);if((stream.flags&2097155)===1)throw new FS.ErrnoError(8);if(FS.isDir(stream.node.mode))throw new FS.ErrnoError(31);if(!stream.stream_ops.read)throw new FS.ErrnoError(28);var seeking=typeof position!=="undefined";if(!seeking)position=stream.position;else if(!stream.seekable)throw new FS.ErrnoError(70);var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write(stream,buffer,offset,length,position,canOwn){if(length<0||position<0)throw new FS.ErrnoError(28);if(FS.isClosed(stream))throw new FS.ErrnoError(8);if((stream.flags&2097155)===0)throw new FS.ErrnoError(8);if(FS.isDir(stream.node.mode))throw new FS.ErrnoError(31);if(!stream.stream_ops.write)throw new FS.ErrnoError(28);if(stream.seekable&&stream.flags&1024)FS.llseek(stream,0,2);var seeking=typeof position!=="undefined";if(!seeking)position=stream.position;else if(!stream.seekable)throw new FS.ErrnoError(70);var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},mmap(stream,length,position,prot,flags){if(prot&2&&!(flags&2)&&(stream.flags&2097155)!==2)throw new FS.ErrnoError(2);if((stream.flags&2097155)===1)throw new FS.ErrnoError(2);if(!stream.stream_ops.mmap)throw new FS.ErrnoError(43);if(!length)throw new FS.ErrnoError(28);return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync(stream,buffer,offset,length,mmapFlags){if(!stream.stream_ops.msync)return 0;return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},ioctl(stream,cmd,arg){if(!stream.stream_ops.ioctl)throw new FS.ErrnoError(59);return stream.stream_ops.ioctl(stream,cmd,arg)},readFile(path,opts={}){opts.flags??=0;opts.encoding??="binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary")abort(`Invalid encoding type "${opts.encoding}"`);var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8")buf=UTF8ArrayToString(buf);FS.close(stream);return buf},writeFile(path,data,opts={}){opts.flags??=577;var stream=FS.open(path,opts.flags,opts.mode);data=FS_fileDataToTypedArray(data);FS.write(stream,data,0,data.byteLength,void 0,opts.canOwn);FS.close(stream)},cwd:()=>FS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null)throw new FS.ErrnoError(44);if(!FS.isDir(lookup.node.mode))throw new FS.ErrnoError(54);var errCode=FS.nodePermissions(lookup.node,"x");if(errCode)throw new FS.ErrnoError(errCode);FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length)=>length,llseek:()=>0});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(!randomLeft){randomFill(randomBuffer);randomLeft=randomBuffer.byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16895,73);node.stream_ops={llseek:MEMFS.stream_ops.llseek};node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path},id:fd+1};ret.parent=ret;return ret},readdir(){return Array.from(FS.streams.entries()).filter(([k,v])=>v).map(([k,v])=>k.toString())}};return node}},{},"/proc/self/fd")},createStandardStreams(input,output,error){if(input)FS.createDevice("/dev","stdin",input);else FS.symlink("/dev/tty","/dev/stdin");if(output)FS.createDevice("/dev","stdout",null,output);else FS.symlink("/dev/tty","/dev/stdout");if(error)FS.createDevice("/dev","stderr",null,error);else FS.symlink("/dev/tty1","/dev/stderr");FS.open("/dev/stdin",0);FS.open("/dev/stdout",1);FS.open("/dev/stderr",1)},staticInit(){FS.nameTable.length=4096;FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories()},init(input,output,error){FS.initialized=true;input??=Module["stdin"];output??=Module["stdout"];error??=Module["stderr"];FS.createStandardStreams(input,output,error)},quit(){FS.initialized=false;for(var stream of FS.streams)if(stream)FS.close(stream)},analyzePath(path,dontResolveLastLink=false){try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath(parent,path,canRead,canWrite){parent=typeof parent==="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){if(e.errno!=20)throw e}parent=current}return current},createFile(parent,name,properties,canRead,canWrite){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile(parent,name,data,canRead,canWrite,canOwn){var path=name;if(parent){parent=typeof parent==="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS_getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){data=FS_fileDataToTypedArray(data);FS.chmod(node,mode|146);var stream=FS.open(node,577);FS.write(stream,data,0,data.length,0,canOwn);FS.close(stream);FS.chmod(node,mode)}},createDevice(parent,name,input,output){var path=PATH.join2(typeof parent==="string"?parent:FS.getPath(parent),name);var mode=FS_getMode(!!input,!!output);FS.createDevice.major??=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open(stream){stream.seekable=false},close(stream){if(output?.buffer?.length)output(10)},read(stream,buffer,offset,length,pos){var bytesRead=0;for(var i=0;ithis.length-1||idx<0)return;var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort(`Couldn't load ${url}. Status: ${xhr.status}`);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1048576;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)abort(`invalid range (${from}, ${to}) or no bytes requested!`);if(to>datalength-1)abort(`only ${datalength} bytes available! programmer error!`);var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range",`bytes=${from}-${to}`);xhr.responseType="arraybuffer";if(xhr.overrideMimeType)xhr.overrideMimeType("text/plain; charset=x-user-defined");xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))abort(`Couldn't load ${url}. Status: ${xhr.status}`);if(xhr.response!==void 0)return new Uint8Array(xhr.response||[]);return intArrayFromString(xhr.responseText??"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined")lazyArray.chunks[chunkNum]=doXHR(start,end);if(typeof lazyArray.chunks[chunkNum]==="undefined")abort("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown)this.cacheLength();return this._length}get chunkSize(){if(!this.lengthKnown)this.cacheLength();return this._chunkSize}}if(globalThis.XMLHttpRequest){if(!ENVIRONMENT_IS_WORKER)abort("Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc");var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else properties={isDevice:false,url};var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents)node.contents=properties.contents;else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};for(const[key,fn]of Object.entries(node.stream_ops))stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)};function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice)for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position)=>{FS.forceLoadFile(node);var ptr=mmapAlloc();if(!ptr)throw new FS.ErrnoError(48);writeChunks(stream,HEAP8,ptr,length,position);return{ptr,allocated:true}};node.stream_ops=stream_ops;return node}};var HEAP64;var SYSCALLS={currentUmask:18,calculateAt(dirfd,path,allowEmpty=false){if(PATH.isAbs(path))return path;var dir;if(dirfd===-100)dir=FS.cwd();else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty)throw new FS.ErrnoError(44);return dir}return dir+"/"+path},writeStat(buf,stat){HEAPU32[buf>>2]=stat.dev;HEAPU32[buf+4>>2]=stat.mode;HEAPU32[buf+8>>2]=stat.nlink;HEAPU32[buf+12>>2]=stat.uid;HEAPU32[buf+16>>2]=stat.gid;HEAPU32[buf+20>>2]=stat.rdev;HEAP64[buf+24>>3]=BigInt(stat.size);HEAP32[buf+32>>2]=4096;HEAP32[buf+36>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();HEAP64[buf+40>>3]=BigInt(Math.floor(atime/1e3));HEAPU32[buf+48>>2]=atime%1e3*1e3*1e3;HEAP64[buf+56>>3]=BigInt(Math.floor(mtime/1e3));HEAPU32[buf+64>>2]=mtime%1e3*1e3*1e3;HEAP64[buf+72>>3]=BigInt(Math.floor(ctime/1e3));HEAPU32[buf+80>>2]=ctime%1e3*1e3*1e3;HEAP64[buf+88>>3]=BigInt(stat.ino);return 0},writeStatFs(buf,stats){HEAPU32[buf+4>>2]=stats.bsize;HEAPU32[buf+60>>2]=stats.bsize;HEAP64[buf+8>>3]=BigInt(stats.blocks);HEAP64[buf+16>>3]=BigInt(stats.bfree);HEAP64[buf+24>>3]=BigInt(stats.bavail);HEAP64[buf+32>>3]=BigInt(stats.files);HEAP64[buf+40>>3]=BigInt(stats.ffree);HEAPU32[buf+48>>2]=stats.fsid;HEAPU32[buf+64>>2]=stats.flags;HEAPU32[buf+56>>2]=stats.namelen},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode))throw new FS.ErrnoError(43);if(flags&2)return 0;var buffer=HEAPU8.subarray(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:void 0,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};var HEAP16;var AsciiToString=ptr=>{var str="";while(1){var ch=HEAPU8[ptr++];if(!ch)return str;str+=String.fromCharCode(ch)}};var awaitingDependencies={};var registeredTypes={};var typeDependencies={};class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}}var throwBindingError=message=>{throw new BindingError(message)};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType)throwBindingError(`type "${name}" must have a positive integer typeid pointer`);if(registeredTypes.hasOwnProperty(rawType))if(options.ignoreDuplicateRegistrations)return;else throwBindingError(`Cannot register type '${name}' twice`);registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){return sharedRegisterType(rawType,registeredInstance,options)}var HEAPU16;var HEAPU64;var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];case 8:return signed?pointer=>HEAP64[pointer>>3]:pointer=>HEAPU64[pointer>>3];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{throwBindingError(function(handle){return handle.$$.ptrType.registeredClass.name}(obj)+" instance already deleted")};var finalizationRegistry=false;var detachFinalizer=()=>{};var runDestructor=$$=>{if($$.smartPtr)$$.smartPtrType.rawDestructor($$.smartPtr);else $$.ptrType.registeredClass.rawDestructor($$.ptr)};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete)runDestructor($$)};var attachFinalizer=handle=>{if(!globalThis.FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};function ClassHandle(){}var createNamedFunction=(name,func)=>Object.defineProperty(func,"name",{value:name});var registeredPointers={};var ensureOverloadTable=(proto,methodName,humanName)=>{if(void 0===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length))throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`);return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(void 0===numArguments||void 0!==Module[name].overloadTable&&void 0!==Module[name].overloadTable[numArguments])throwBindingError(`Cannot register public name '${name}' twice`);ensureOverloadTable(Module,name,name);if(Module[name].overloadTable.hasOwnProperty(numArguments))throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`);Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var makeLegalFunctionName=name=>{name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=48&&f<=57)return`_${name}`;return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast)throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`);ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};var embindRepr=v=>{if(v===null)return"null";var t=typeof v;if(t==="object"||t==="array"||t==="function")return v.toString();else return""+v};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference)throwBindingError(`null is not a valid ${this.name}`);return 0}if(!handle.$$)throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`);if(!handle.$$.ptr)throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`);var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference)throwBindingError(`null is not a valid ${this.name}`);if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null)destructors.push(this.rawDestructor,ptr);return ptr}else return 0}if(!handle||!handle.$$)throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`);if(!handle.$$.ptr)throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`);if(!this.isConst&&handle.$$.ptrType.isConst)throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`);var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(void 0===handle.$$.smartPtr)throwBindingError("Passing raw pointer to smart pointer is illegal");switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this)ptr=handle.$$.smartPtr;else throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`);break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this)ptr=handle.$$.smartPtr;else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null)destructors.push(this.rawDestructor,ptr)}break;default:throwBindingError("Unsupported sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference)throwBindingError(`null is not a valid ${this.name}`);return 0}if(!handle.$$)throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`);if(!handle.$$.ptr)throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`);if(handle.$$.ptrType.isConst)throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`);var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function readPointer(pointer){return this.fromWireType(HEAPU32[pointer>>2])}var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass)return ptr;if(void 0===desiredClass.baseClass)return null;var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null)return null;return desiredClass.downcast(rv)};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===void 0)throwBindingError("ptr should not be undefined");while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}}var throwInternalError=message=>{throw new InternalError(message)};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr)throwInternalError("makeClassHandle requires ptr and ptrType");var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr)throwInternalError("Both smartPtrType and smartPtr must be specified");record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(void 0!==registeredInstance)if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}function makeDefaultHandle(){if(this.isSmartPointer)return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr});else return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr})}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord)return makeDefaultHandle.call(this);var toType;if(this.isConst)toType=registeredPointerRecord.constPointerType;else toType=registeredPointerRecord.pointerType;var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null)return makeDefaultHandle.call(this);if(this.isSmartPointer)return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr});else return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===void 0)if(isConst){this.toWireType=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this.toWireType=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else this.toWireType=genericPointerToWireType}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name))throwInternalError("Replacing nonexistent public symbol");if(void 0!==Module[name].overloadTable&&void 0!==numArguments)Module[name].overloadTable[numArguments]=value;else{Module[name]=value;Module[name].argCount=numArguments}};var wasmTableMirror=[];var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func)wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr);return func};var embind__requireFunction=(signature,rawFunction)=>{signature=AsciiToString(signature);var fp=function(){var rtn=getWasmTableEntry(rawFunction);return rtn}();if(typeof fp!=="function")throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`);return fp};class UnboundTypeError extends Error{}var getTypeName=type=>{var ptr=___getTypeName(type);var rv=AsciiToString(ptr);_free(ptr);return rv};var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};types.forEach(function visit(type){if(seen[type])return;if(registeredTypes[type])return;if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true});throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(type=>typeDependencies[type]=dependentTypes);function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length)throwInternalError("Mismatched type converter count");for(var i=0;i{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length)onComplete(typeConverters)})}if(0===unregisteredTypes.length)onComplete(typeConverters)};var heap32VectorToArray=(count,firstElement)=>{var array=[];for(var i=0;i>2]);return array};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function usesDestructorStack(argTypes){for(var i=1;i{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex===-1)return signature;return signature.slice(0,argsIndex)};var emval_freelist=[];var emval_handles=[0,1,,1,null,1,true,1,false,1];var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle];emval_handles[handle]=void 0;emval_freelist.push(handle)}};var Emval={toValue:handle=>{if(!handle)throwBindingError(`Cannot use deleted val. handle = ${handle}`);return emval_handles[handle]},toHandle:value=>{switch(value){case void 0:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),readValueFromPointer:readPointer,destructorFunction:null};var HEAPF32;var HEAPF64;var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this.fromWireType(HEAPF32[pointer>>2])};case 8:return function(pointer){return this.fromWireType(HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var UTF16Decoder=globalThis.TextDecoder?new TextDecoder("utf-16le"):void 0;var UTF16ToString=(ptr,maxBytesToRead,ignoreNul)=>{var idx=ptr>>1;var endIdx=findStringEnd(HEAPU16,idx,maxBytesToRead/2,ignoreNul);if(endIdx-idx>16&&UTF16Decoder)return UTF16Decoder.decode(HEAPU16.subarray(idx,endIdx));var str="";for(var i=idx;i{if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead,ignoreNul)=>{var str="";var startIdx=ptr>>2;for(var i=0;!(i>=maxBytesToRead/4);i++){var utf32=HEAPU32[startIdx+i];if(!utf32&&!ignoreNul)break;str+=String.fromCodePoint(utf32)}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite=2147483647)=>{if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i65535)i++;HEAP32[outPtr>>2]=codePoint;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i65535)i++;len+=4}return len};var emval_methodCallers=[];var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(void 0===impl)throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`);return impl};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],`parameter ${i}`);return a};var emval_returnValue=(toReturnWire,destructorsRef,handle)=>{var destructors=[];var result=toReturnWire(destructors,handle);if(destructors.length)HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors);return result};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===void 0)return AsciiToString(address);return symbol};var _emscripten_get_now=()=>performance.now();var _emscripten_date_now=()=>Date.now();var checkWasiClock=clock_id=>clock_id>=0&&clock_id<=3;var bigintToI53Checked=num=>num<-9007199254740992||num>9007199254740992?NaN:Number(num);var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var ENV={};var getExecutableName=()=>thisProgram;var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV)if(ENV[x]===void 0)delete env[x];else env[x]=ENV[x];var strings=[];for(var x in env)strings.push(`${x}=${env[x]}`);getEnvStrings.strings=strings}return getEnvStrings.strings};var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;try{var curr=FS.read(stream,HEAP8,ptr,len,offset)}catch(e){if(ret>0&&e instanceof FS.ErrnoError&&(e.errno==6||e.errno==6))break;throw e}if(curr<0)return-1;ret+=curr;if(curr{if(iovcnt==1)return FS.write(stream,HEAP8,HEAPU32[iov>>2],HEAPU32[iov+4>>2],offset);var total=0;for(var i=0,p=iov;i>2];var view=new Uint8Array(total);var voff=0;for(i=0;i>2];var len=HEAPU32[iov+4>>2];view.set(HEAPU8.subarray(ptr,ptr+len),voff);voff+=len}return FS.write(stream,view,0,total,offset)};FS.createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{FS_preloadFile(parent,name,url,canRead,canWrite,dontCreateFile,canOwn,preFinish).then(onload).catch(onerror)};FS.preloadFile=FS_preloadFile;FS.staticInit();(()=>{let proto=ClassHandle.prototype;Object.assign(proto,{isAliasOf(other){if(!(this instanceof ClassHandle))return false;if(!(other instanceof ClassHandle))return false;var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr)throwInstanceAlreadyDeleted(this);if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr)throwInstanceAlreadyDeleted(this);if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete)throwBindingError("Object already scheduled for deletion");detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=void 0;this.$$.ptr=void 0}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr)throwInstanceAlreadyDeleted(this);if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete)throwBindingError("Object already scheduled for deletion");deletionQueue.push(this);if(deletionQueue.length===1&&void 0)(void 0)(flushPendingDeletes);this.$$.deleteScheduled=true;return this}});const symbolDispose=Symbol.dispose;if(symbolDispose)proto[symbolDispose]=proto["delete"]})();(()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee)ptr=this.rawGetPointee(ptr);return ptr},destructor(ptr){this.rawDestructor?.(ptr)},readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})})();if(Module["noExitRuntime"])Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["arguments"])Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit==="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0)preInit.shift()()}var _malloc,_free,___getTypeName,__emscripten_stack_restore,_emscripten_stack_get_current,wasmMemory,wasmTable;function assignWasmExports(wasmExports){_malloc=wasmExports["malloc"];_free=wasmExports["free"];___getTypeName=wasmExports["__getTypeName"];wasmExports["setThrew"];__emscripten_stack_restore=wasmExports["_emscripten_stack_restore"];wasmExports["_emscripten_stack_alloc"];_emscripten_stack_get_current=wasmExports["emscripten_stack_get_current"];wasmMemory=wasmExports["memory"];wasmTable=wasmExports["__indirect_function_table"]}var wasmImports={__assert_fail:(condition,filename,line,func)=>abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"]),__cxa_throw:(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException()},__syscall_fcntl64:function(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:var arg=syscallGetVarargI();if(arg<0)return-28;while(FS.streams[arg])arg++;var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd;case 1:case 2:return 0;case 3:return stream.flags;case 4:arg=syscallGetVarargI();stream.flags=stream.flags&-289793|arg&289792;return 0;case 12:arg=syscallGetVarargP();HEAP16[arg+0>>1]=2;return 0;case 13:case 14:return 0}return-28}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}},__syscall_ioctl:function(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:if(!stream.tty)return-59;return 0;case 21505:if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();HEAP32[argp>>2]=termios.c_iflag||0;HEAP32[argp+4>>2]=termios.c_oflag||0;HEAP32[argp+8>>2]=termios.c_cflag||0;HEAP32[argp+12>>2]=termios.c_lflag||0;for(var i=0;i<32;i++)HEAP8[argp+i+17]=termios.c_cc[i]||0;return 0}return 0;case 21510:case 21511:case 21512:if(!stream.tty)return-59;return 0;case 21506:case 21507:case 21508:if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){argp=syscallGetVarargP();var c_iflag=HEAP32[argp>>2];var c_oflag=HEAP32[argp+4>>2];var c_cflag=HEAP32[argp+8>>2];var c_lflag=HEAP32[argp+12>>2];var c_cc=[];for(i=0;i<32;i++)c_cc.push(HEAP8[argp+i+17]);return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag,c_oflag,c_cflag,c_lflag,c_cc})}return 0;case 21519:if(!stream.tty)return-59;argp=syscallGetVarargP();HEAP32[argp>>2]=0;return 0;case 21520:if(!stream.tty)return-59;return-28;case 21537:case 21531:argp=syscallGetVarargP();return FS.ioctl(stream,op,argp);case 21523:if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);argp=syscallGetVarargP();HEAP16[argp>>1]=winsize[0];HEAP16[argp+2>>1]=winsize[1]}return 0;case 21524:if(!stream.tty)return-59;return 0;case 21515:if(!stream.tty)return-59;return 0;default:return-28}}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}},__syscall_openat:function(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;if(flags&64)mode&=~SYSCALLS.currentUmask;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}},_abort_js:()=>abort(""),_embind_register_bigint:(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0n;let fromWireType=value=>value;if(isUnsignedType){const bitSize=size*8;fromWireType=value=>BigInt.asUintN(bitSize,value);maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>{if(typeof value==="number")value=BigInt(value);return value},readValueFromPointer:integerReadValueFromPointer(name,size,!isUnsignedType),destructorFunction:null})},_embind_register_bool:(rawType,name,trueValue,falseValue)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},readValueFromPointer:function(pointer){return this.fromWireType(HEAPU8[pointer])},destructorFunction:null})},_embind_register_class:(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=AsciiToString(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else basePrototype=ClassHandle.prototype;var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype)throw new BindingError(`Use 'new' to construct ${name}`);if(void 0===registeredClass.constructor_body)throw new BindingError(`${name} has no accessible constructor`);var body=registeredClass.constructor_body[args.length];if(void 0===body)throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`);return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})},_embind_register_class_constructor:(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(void 0===classType.registeredClass.constructor_body)classType.registeredClass.constructor_body=[];if(void 0!==classType.registeredClass.constructor_body[argCount-1])throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`);classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})},_embind_register_class_function:(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=AsciiToString(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@"))methodName=Symbol[methodName.substring(2)];if(isPureVirtual)classType.registeredClass.pureVirtualFunctions.push(methodName);function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(void 0===method||void 0===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(void 0===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else proto[methodName].overloadTable[argCount-2]=memberFunction;return[]});return[]})},_embind_register_emval:rawType=>registerType(rawType,EmValType),_embind_register_float:(rawType,name,size)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType:value=>value,toWireType:(destructors,value)=>value,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})},_embind_register_integer:(primitiveType,name,size,minRange,maxRange)=>{name=AsciiToString(name);const isUnsignedType=minRange===0;let fromWireType=value=>value;if(isUnsignedType){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift;maxRange=fromWireType(maxRange)}registerType(primitiveType,{name,fromWireType,toWireType:(destructors,value)=>value,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})},_embind_register_memory_view:(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=AsciiToString(name);registerType(rawType,{name,fromWireType:decodeMemoryView,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})},_embind_register_std_string:(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(true)str=UTF8ToString(payload,length,true);else{str="";for(var i=0;i>2]=length;if(valueIsOfTypeString)if(true)stringToUTF8(value,ptr,length+1);else for(var i=0;i255){_free(base);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}else HEAPU8.set(value,ptr);if(destructors!==null)destructors.push(_free,base);return base},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})},_embind_register_std_wstring:(rawType,charSize,name)=>{name=AsciiToString(name);var decodeString,encodeString,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16}else{decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32}registerType(rawType,{name,fromWireType:value=>{var length=HEAPU32[value>>2];var str=decodeString(value+4,length*charSize,true);_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value==="string"))throwBindingError(`Cannot pass non-string to C++ string type ${name}`);var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null)destructors.push(_free,ptr);return ptr},readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})},_embind_register_void:(rawType,name)=>{name=AsciiToString(name);registerType(rawType,{isVoid:true,name,fromWireType:()=>{},toWireType:()=>{}})},_emval_create_invoker:(argCount,argTypesPtr,kind)=>{var[retType,...argTypes]=emval_lookupTypes(argCount,argTypesPtr);var toReturnWire=retType.toWireType.bind(retType);var argFromPtr=argTypes.map(type=>type.readValueFromPointer.bind(type));argCount--;var captures={toValue:Emval.toValue};var args=argFromPtr.map((argFromPtr,i)=>{var captureName=`argFromPtr${i}`;captures[captureName]=argFromPtr;return`${captureName}(args${i?"+"+i*8:""})`});var functionBody;switch(kind){case 0:functionBody="toValue(handle)";break;case 2:functionBody="new (toValue(handle))";break;case 3:functionBody="";break;case 1:captures["getStringOrSymbol"]=getStringOrSymbol;functionBody="toValue(handle)[getStringOrSymbol(methodName)]";break}functionBody+=`(${args})`;if(!retType.isVoid){captures["toReturnWire"]=toReturnWire;captures["emval_returnValue"]=emval_returnValue;functionBody=`return emval_returnValue(toReturnWire, destructorsRef, ${functionBody})`}functionBody=`return function (handle, methodName, destructorsRef, args) {\n${functionBody}\n}`;var invokerFunction=new Function(Object.keys(captures),functionBody)(...Object.values(captures));var functionName=`methodCaller<(${argTypes.map(t=>t.name)}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))},_emval_decref:__emval_decref,_emval_get_property:(handle,key)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])},_emval_incref:handle=>{if(handle>9)emval_handles[handle+1]+=1},_emval_invoke:(caller,handle,methodName,destructorsRef,args)=>emval_methodCallers[caller](handle,methodName,destructorsRef,args),_emval_new_array:()=>Emval.toHandle([]),_emval_new_cstring:v=>Emval.toHandle(getStringOrSymbol(v)),_emval_new_object:()=>Emval.toHandle({}),_emval_run_destructors:handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)},_emval_set_property:(handle,key,value)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value},_tzset_js:(timezone,daylight,std_name,dst_name)=>{var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);var extractZone=timezoneOffset=>{var sign=timezoneOffset>=0?"-":"+";var absOffset=Math.abs(timezoneOffset);var hours=String(Math.floor(absOffset/60)).padStart(2,"0");var minutes=String(absOffset%60).padStart(2,"0");return`UTC${sign}${hours}${minutes}`};var winterName=extractZone(winterOffset);var summerName=extractZone(summerOffset);if(summerOffset>3]=BigInt(nsec);return 0},emscripten_get_heap_max:()=>getHeapMax(),emscripten_resize_heap:requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize)return false;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement)return true}return false},environ_get:(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,1/0)+1;envp+=4}return 0},environ_sizes_get:(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings)bufSize+=lengthBytesUTF8(string)+1;HEAPU32[penviron_buf_size>>2]=bufSize;return 0},fd_close:function(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}},fd_read:function(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doReadv(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}},fd_seek:function(fd,offset,whence,newOffset){offset=bigintToI53Checked(offset);try{if(isNaN(offset))return 22;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);HEAP64[newOffset>>3]=BigInt(stream.position);if(stream.getdents&&!offset&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}},fd_write:function(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}};var wasmExports;wasmExports=await async function(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm)return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))});wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(void 0,wasmBinaryFile,info);var exports=function(result){return receiveInstance(result["instance"])}(result);return exports}();await async function(){preRun();if(runDependencies)await resolveRunDependencies();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}(); +;return Module}export default createTinyCpu; From 09cb4a053c7f01b480f81cbf9afaadf5a6114ab4 Mon Sep 17 00:00:00 2001 From: Dineth Hettiarachchi Date: Sun, 25 Oct 2026 15:35:00 +0530 Subject: [PATCH 07/10] feat: Phase 6 web app with browser assembler and pipeline view Adds the /app page: an RV32I assembler (two passes, byte-identical to GNU as/ld on 26 sources), a disassembler, typed CycleState mirror, runner and run model, and React views for the source, pipeline, cycle controls, registers, data memory, console and statistics. Five presets run on the WASM core in tests. Vitest unit and integration tests and Playwright smoke tests; CI runs both. --- .github/workflows/web.yml | 18 +- AGENTS.md | 3 + CLAUDE.md | 3 + README.md | 8 +- docs/ARCHITECTURE.md | 17 + sim/golden/gen_asm_fixtures.py | 335 + sim/golden/test_gnu_asm_fixtures.py | 17 + web/.github-placeholder | 0 web/.gitignore | 2 + web/e2e/app.spec.ts | 48 + web/package.json | 8 +- web/playwright.config.ts | 18 + web/pnpm-lock.yaml | 7045 ++++++----------- web/src/components/AsmEditor.tsx | 59 + web/src/components/ConsolePanel.tsx | 10 + web/src/components/Controls.tsx | 53 + web/src/components/CpuApp.tsx | 180 + web/src/components/MemoryView.tsx | 39 + web/src/components/Panel.tsx | 13 + web/src/components/PipelineView.tsx | 121 + web/src/components/RegisterFile.tsx | 27 + web/src/components/StatsBar.tsx | 27 + web/src/hooks/useTinyCpu.ts | 100 + web/src/lib/__tests__/assembler.test.ts | 104 + web/src/lib/__tests__/disassembler.test.ts | 43 + web/src/lib/__tests__/gnu-asm.json | 132 + web/src/lib/__tests__/gnu-asm.test.ts | 41 + web/src/lib/__tests__/run-model.test.ts | 59 + .../lib/__tests__/wasm.integration.test.ts | 65 + web/src/lib/assembler.ts | 873 ++ web/src/lib/disassembler.ts | 91 + web/src/lib/format.ts | 2 + web/src/lib/presets/fib.s | 58 + web/src/lib/presets/hello.s | 24 + web/src/lib/presets/memcpy.s | 65 + web/src/lib/presets/primes.s | 89 + web/src/lib/presets/sort.s | 77 + web/src/lib/programs.ts | 23 + web/src/lib/run-model.ts | 97 + web/src/lib/runner.ts | 45 + web/src/lib/state-schema.ts | 94 + web/src/lib/wasm-loader.ts | 41 + web/src/pages/app.astro | 14 + web/src/pages/index.astro | 9 +- web/src/styles/global.css | 6 + web/vitest.config.ts | 9 + 46 files changed, 5737 insertions(+), 4475 deletions(-) create mode 100644 sim/golden/gen_asm_fixtures.py create mode 100644 sim/golden/test_gnu_asm_fixtures.py create mode 100644 web/.github-placeholder create mode 100644 web/e2e/app.spec.ts create mode 100644 web/playwright.config.ts create mode 100644 web/src/components/AsmEditor.tsx create mode 100644 web/src/components/ConsolePanel.tsx create mode 100644 web/src/components/Controls.tsx create mode 100644 web/src/components/CpuApp.tsx create mode 100644 web/src/components/MemoryView.tsx create mode 100644 web/src/components/Panel.tsx create mode 100644 web/src/components/PipelineView.tsx create mode 100644 web/src/components/RegisterFile.tsx create mode 100644 web/src/components/StatsBar.tsx create mode 100644 web/src/hooks/useTinyCpu.ts create mode 100644 web/src/lib/__tests__/assembler.test.ts create mode 100644 web/src/lib/__tests__/disassembler.test.ts create mode 100644 web/src/lib/__tests__/gnu-asm.json create mode 100644 web/src/lib/__tests__/gnu-asm.test.ts create mode 100644 web/src/lib/__tests__/run-model.test.ts create mode 100644 web/src/lib/__tests__/wasm.integration.test.ts create mode 100644 web/src/lib/assembler.ts create mode 100644 web/src/lib/disassembler.ts create mode 100644 web/src/lib/format.ts create mode 100644 web/src/lib/presets/fib.s create mode 100644 web/src/lib/presets/hello.s create mode 100644 web/src/lib/presets/memcpy.s create mode 100644 web/src/lib/presets/primes.s create mode 100644 web/src/lib/presets/sort.s create mode 100644 web/src/lib/programs.ts create mode 100644 web/src/lib/run-model.ts create mode 100644 web/src/lib/runner.ts create mode 100644 web/src/lib/state-schema.ts create mode 100644 web/src/lib/wasm-loader.ts create mode 100644 web/src/pages/app.astro create mode 100644 web/vitest.config.ts diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 0e3cae1..3375827 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -2,9 +2,9 @@ name: Web CI on: push: - paths: ['web/**', '.github/workflows/web.yml'] + paths: ['web/**', 'sim/golden/gen_asm_fixtures.py', '.github/workflows/web.yml'] pull_request: - paths: ['web/**', '.github/workflows/web.yml'] + paths: ['web/**', 'sim/golden/gen_asm_fixtures.py', '.github/workflows/web.yml'] jobs: web-ci: @@ -35,6 +35,20 @@ jobs: working-directory: web run: pnpm typecheck + # Unit and integration tests: assembler vs GNU bytes, disassembler round trip, run model, and every + # preset on the committed WASM module (web/public/tiny_cpu.mjs, rebuilt by the WASM workflow). + - name: Unit and integration tests + working-directory: web + run: pnpm test + - name: Build working-directory: web run: pnpm build + + - name: Install Chromium for the smoke tests + working-directory: web + run: pnpm exec playwright install --with-deps chromium + + - name: Browser smoke tests (production build) + working-directory: web + run: pnpm test:e2e diff --git a/AGENTS.md b/AGENTS.md index 20c54f2..5c1ab59 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,9 @@ Both `web/public/tiny_cpu.{mjs,wasm}` are committed: the web app and Vercel have ### Frontend (from `web/`) ```bash pnpm dev | build | lint | typecheck | format +pnpm test # Vitest: assembler vs GNU bytes, disassembler round trip, presets on the WASM module +pnpm test:e2e # Playwright smoke tests against the production build (run `pnpm build` first) +cd ../sim && python -m golden.gen_asm_fixtures # regenerate web/src/lib/__tests__/gnu-asm.json after changing presets/snippets ``` ## Commits and PRs: no AI attribution (hard rule) diff --git a/CLAUDE.md b/CLAUDE.md index 8aa9611..e61c3df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,9 @@ Both `web/public/tiny_cpu.{mjs,wasm}` are committed: the web app and Vercel have ### Frontend (from `web/`) ```bash pnpm dev | build | lint | typecheck | format +pnpm test # Vitest: assembler vs GNU bytes, disassembler round trip, presets on the WASM module +pnpm test:e2e # Playwright smoke tests against the production build (run `pnpm build` first) +cd ../sim && python -m golden.gen_asm_fixtures # regenerate web/src/lib/__tests__/gnu-asm.json after changing presets/snippets ``` ## Commits and PRs: no AI attribution (hard rule) diff --git a/README.md b/README.md index 0fcbd97..e73592e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ Third in the series after [tiny-gpu](https://github.com/deaneeth/tiny-gpu) and [ -> **Status: Phase 5 done (WASM harness).** Both cores run in one WebAssembly module (`wasm/build.sh`, 77 KB gzipped) and pass the golden fixtures in Node (`node wasm/test.mjs`). Next: the web app. +> **Status: Phase 6 done (web app, first slice).** The browser app at `/app` assembles RV32I, runs it on either core in the browser, and steps through every cycle with the pipeline, registers, memory and console. Next: the remaining views. +> +> *Previous: Phase 5 done (WASM harness).* Both cores run in one WebAssembly module (`wasm/build.sh`, 77 KB gzipped) and pass the golden fixtures in Node (`node wasm/test.mjs`). Next: the web app. > > *Previous: Phase 4 done (hazards).* Both RV32I cores retire instructions identically to the Python golden ISS on all 40 official `rv32ui-p-*` tests, 5 programs and hundreds of random programs. The 5-stage core has forwarding and load-use stalls (switchable at runtime) and matches an independent timing model cycle for cycle. Next: the WASM harness. > See [`docs/PLAN.md`](docs/PLAN.md) for the full roadmap. @@ -40,7 +42,7 @@ rtl/*.sv -> Verilator C++ -> Emscripten -> web/public/tiny_cpu.wasm -> React isl | `sim/` | golden ISS (`sim/golden/`), cocotb tests, asm programs (`sim/programs/`) | | `sw/` | linker script, crt0, riscv-tests port + Makefile | | `wasm/` | Verilator -> Emscripten harness | -| `web/` | Astro + React visualizer | +| `web/` | Astro + React app: assembler, editor, pipeline view, cycle controls | | `fpga/` | Yosys/nextpnr flows (Phase 9) | | `docs/` | plan, architecture, state schema | @@ -57,7 +59,7 @@ bash scripts/lint.sh && bash sim/run.sh all # needs: make -C sw all bash wasm/build.sh && node wasm/test.mjs # Web -cd web && pnpm install && pnpm dev # open /spike after building the WASM +cd web && pnpm install && pnpm dev # then open /app (or `pnpm build && pnpm test:e2e` for the smoke tests) ``` ## License diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 1eaff3b..29a612e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -164,3 +164,20 @@ pipeline model. Four mutations were caught: a forwarding pin ignored, a console the shadow memory's byte order reversed. Size: 194 KB raw, 77 KB gzipped (budget 400 KB), enforced by `build.sh`. + + +## Web app (Phase 6) + +`/app` is one React island (`CpuApp`) over `useTinyCpu`: + +* **Assembler** (`web/src/lib/assembler.ts`): two passes; labels, numeric local labels, `.equ`, the data directives and the + GNU pseudo-instructions. Output is byte-identical to GNU as/ld for every source in `gnu-asm.json` (the presets, the five macro + programs preprocessed, and 16 snippets covering every mnemonic and directive). +* **Run**: one `TinyCpuSim` per run. The full `CycleState[]` is kept (capped at 20,000 cycles), so stepping, scrubbing and + playback only move an index; nothing re-runs. `run-model.ts` derives statistics, console output and data memory at any cycle + from the states themselves (memory by replaying retire records, the same rule as the harness's shadow copy). +* **Views**: source editor with the executing line highlighted and errors marked; a pipeline view (five stage cards, hazard + badges, and a stage timeline over the last ten cycles); registers and data memory with changed values highlighted; console; + statistics. Single-cycle runs show one instruction per cycle; hazard badges appear only for the pipeline. +* **Checks**: `web/src/lib/__tests__/` (Vitest, 53 tests, including every preset on both cores with forwarding on and off, read + against the expected console output) and `web/e2e/` (Playwright, 4 tests against the production build). diff --git a/sim/golden/gen_asm_fixtures.py b/sim/golden/gen_asm_fixtures.py new file mode 100644 index 0000000..512f39a --- /dev/null +++ b/sim/golden/gen_asm_fixtures.py @@ -0,0 +1,335 @@ +"""GNU reference bytes for the web assembler (web/src/lib/assembler.ts). + + cd sim && python -m golden.gen_asm_fixtures # rewrite web/src/lib/__tests__/gnu-asm.json + cd sim && python -m golden.gen_asm_fixtures --check # exit 1 if stale + +Each source is compiled with riscv-none-elf-gcc (-mno-relax, so `call`/`la` keep their auipc+jalr/addi +form) and linked with sw/link.ld, so .text starts at 0 and .data at 0x10000, exactly as the assembler +lays them out. The JSON stores the source and the GNU bytes of .text and of the data span. The TypeScript +test assembles the same source and must reproduce both byte for byte. + +Sources: the web presets (web/src/lib/presets/*.s), the macro programs in sim/programs/*.S (preprocessed +with gcc -E first), and a corpus of snippets that touch every mnemonic, pseudo-instruction and directive. +""" + +from __future__ import annotations + +import json +import re +import subprocess +import sys +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / "web" / "src" / "lib" / "__tests__" / "gnu-asm.json" +PRESETS = ROOT / "web" / "src" / "lib" / "presets" +PROGRAMS = ROOT / "sim" / "programs" +LINK = ROOT / "sw" / "link.ld" +DATA_BASE = 0x10000 +ARCH = ["-march=rv32i_zicsr_zifencei", "-mabi=ilp32", "-mno-relax"] +GCC = "riscv-none-elf-gcc" + +SNIPPETS: dict[str, str] = { + "alu_register": """ +_start: + li a0, 0x7fffffff + li a1, -3 + add a2, a0, a1 + sub a3, a0, a1 + sll a4, a0, a1 + slt a5, a1, a0 + sltu a6, a1, a0 + xor a7, a0, a1 + srl t0, a1, a0 + sra t1, a1, a0 + or t2, a0, a1 + and t3, a0, a1 +""", + "alu_immediate": """ +_start: + addi a0, zero, -2048 + addi a1, a0, 2047 + slti a2, a0, -1 + sltiu a3, a0, -1 + xori a4, a0, 0x7f0 + ori a5, a0, -16 + andi a6, a0, 0x3c + slli a7, a0, 31 + srli t0, a0, 4 + srai t1, a0, 1 +""", + "loads_stores": """ +_start: + la a0, buf + lb a1, 0(a0) + lbu a2, 1(a0) + lh a3, 2(a0) + lhu a4, -2(a0) + lw a5, 4(a0) + sb a1, 8(a0) + sh a2, 10(a0) + sw a3, 12(a0) +.data +buf: .word 0x80ff7f01, 0xaabbccdd, 0, 0 +""", + "upper_immediates": """ +_start: + lui a0, 0x12345 + lui a1, 0xfffff + lui a2, 0x80000 + auipc a3, 0 + auipc a4, 0x1 +""", + "branches": """ +_start: + li a0, 5 + li a1, -3 + beq a0, a1, fwd + bne a0, a1, fwd + blt a1, a0, fwd + bge a0, a1, fwd + bltu a0, a1, fwd + bgeu a1, a0, fwd + nop +fwd: + beq a0, a0, back +back: + addi a0, a0, -1 + bne a0, zero, back +""", + "jumps": """ +_start: + jal target + jal ra, target + jal zero, target + la t0, target + jalr zero, 0(t0) + jalr ra, t0, 8 + jalr t1, 4(t0) + jalr t2 +target: + jalr zero, 0(ra) +""", + "system_and_counters": """ +_start: + fence + ecall + ebreak + csrrs a0, cycle, zero + csrrs a1, time, zero + csrrs a2, instret, zero + csrrs a3, cycleh, zero + csrrs a4, timeh, zero + csrrs a5, instreth, zero + csrrs a6, 0xc00, zero + csrr a7, instret + rdcycle t0 + rdtime t1 + rdinstret t2 + rdcycleh t3 + rdtimeh t4 + rdinstreth t5 +""", + "pseudo_arithmetic": """ +_start: + nop + mv a0, a1 + not a2, a3 + neg a4, a5 + seqz a6, a7 + snez t0, t1 + sltz t2, t3 + sgtz t4, t5 +""", + "pseudo_branches": """ +_start: + li a0, 4 + beqz a0, t + bnez a0, t + blez a0, t + bgez a0, t + bltz a0, t + bgtz a0, t + bgt a0, a1, t + ble a0, a1, t + bgtu a0, a1, t + bleu a0, a1, t +t: + j t + jr ra + ret +""", + "li_values": """ +_start: + li a0, 0 + li a1, 2047 + li a2, -2048 + li a3, 2048 + li a4, -2049 + li a5, 0x12345000 + li a6, 0x12345678 + li a7, 0x7fffffff + li t0, 0x80000000 + li t1, 0xffffffff + li t2, 0x800 + li t3, 0xfffff800 +""", + "la_and_calls": """ +_start: + la a0, far + call near + tail near + call far +near: + ret +.data +far: .word 1 +""", + "data_directives": """ +_start: + nop +.data + .word 1, -1, 0x7fffffff, 0xffffffff + .half 0x1234, -2, 65535 + .byte 0, 127, 255, 'A', '\\n' + .ascii "ab" + .asciz "cd" + .string "ef\\x21" + .space 3 + .space 2, 0xee + .zero 2 + .align 3 + .word 0xdeadbeef + .balign 8 + .byte 9 +""", + "rodata_and_sections": """ +_start: + la a0, msg + lbu a1, 0(a0) +.section .rodata +msg: .asciz "rodata" +.section .text + nop +""", + "equ_and_expressions": """ +.equ BASE, 0x10000 +.equ COUNT, 4 * 3 + 1 +_start: + li a0, BASE + COUNT + li a1, (1 << 12) - 1 + addi a2, zero, COUNT - 1 + li a3, -(COUNT) + li a4, 0xf0 | 0x0f + li a5, 0xff & ~0x0f + li a6, 7 % 4 + li a7, 'z' - 'a' +.data + .word BASE + 4, end - start_data +start_data: + .byte COUNT +end: +""", + "local_labels": """ +_start: + li a0, 3 +1: addi a0, a0, -1 + bnez a0, 1b + j 2f +2: li a1, 1 + beqz a1, 1f + nop +1: li a2, 2 +""", + "text_alignment": """ +_start: + nop + .byte 1 + .align 2 + nop + .byte 2, 3 + .align 2 + nop +""", +} + + +def _run(args: list[str]) -> bytes: + res = subprocess.run(args, capture_output=True) + if res.returncode != 0: + raise RuntimeError(f"{' '.join(args)} failed:\n{res.stderr.decode(errors='replace')}") + return res.stdout + + +def _sections(elf: Path) -> dict[str, tuple[int, int]]: + out: dict[str, tuple[int, int]] = {} + text = _run([f"riscv-none-elf-readelf", "-SW", str(elf)]).decode() + for line in text.splitlines(): + m = re.search(r"\[\s*\d+\]\s+(\S+)\s+\S+\s+([0-9a-f]+)\s+[0-9a-f]+\s+([0-9a-f]+)", line) + if m and m.group(1).startswith("."): + out[m.group(1)] = (int(m.group(2), 16), int(m.group(3), 16)) + return out + + +def _script(work: Path) -> Path: + """sw/link.ld minus its ALIGN(4) lines: those pad the output, and as/ld do not pad the program's bytes.""" + lines = [ln for ln in LINK.read_text().splitlines() if "ALIGN(4)" not in ln] + out = work / "gen.ld" + out.write_text("\n".join(lines) + "\n") + return out + + +def gnu_bytes(name: str, source: str, work: Path) -> dict: + src = work / f"{re.sub(r'[^A-Za-z0-9_]', '_', name)}.S" + src.write_text(source) + elf = src.with_suffix(".elf") + _run([GCC, *ARCH, "-static", "-nostdlib", "-nostartfiles", "-ffreestanding", + f"-Wl,-T,{_script(work)}", "-Wl,--no-warn-rwx-segments", str(src), "-o", str(elf)]) + secs = _sections(elf) + text = _run(["riscv-none-elf-objcopy", "-O", "binary", "-j", ".text", str(elf), "/dev/stdout"]) if ".text" in secs else b"" + data_end = DATA_BASE + for n, (addr, size) in secs.items(): + if addr >= DATA_BASE and size and n in (".data", ".rodata", ".sdata", ".bss", ".sbss", ".srodata"): + data_end = max(data_end, addr + size) + data = bytearray(data_end - DATA_BASE) + for n, (addr, size) in secs.items(): + if addr < DATA_BASE or not size or n not in (".data", ".rodata", ".sdata", ".srodata"): + continue + blob = _run(["riscv-none-elf-objcopy", "-O", "binary", "-j", n, str(elf), "/dev/stdout"]) + data[addr - DATA_BASE : addr - DATA_BASE + len(blob)] = blob + return {"source": source, "text": text.hex(), "data": bytes(data).hex()} + + +def preprocessed(path: Path) -> str: + return _run(["riscv-none-elf-gcc", "-E", "-P", f"-I{PROGRAMS}", str(path)]).decode() + + +def render() -> str: + sources: dict[str, str] = {} + for p in sorted(PRESETS.glob("*.s")): + sources[f"preset/{p.stem}"] = p.read_text() + for p in sorted(PROGRAMS.glob("*.S")): + sources[f"sim_programs/{p.stem}"] = preprocessed(p) + sources.update({f"snippet/{k}": v for k, v in SNIPPETS.items()}) + with tempfile.TemporaryDirectory(prefix="gnu-asm-") as tmp: + work = Path(tmp) + out = {name: gnu_bytes(name, src, work) for name, src in sources.items()} + return json.dumps(out, indent=1, sort_keys=True) + "\n" + + +def main(argv: list[str]) -> int: + text = render() + if "--check" in argv: + if not OUT.exists() or OUT.read_text() != text: + print(f"{OUT} is stale: run `cd sim && python -m golden.gen_asm_fixtures`", file=sys.stderr) + return 1 + return 0 + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(text) + print(f"wrote {OUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/sim/golden/test_gnu_asm_fixtures.py b/sim/golden/test_gnu_asm_fixtures.py new file mode 100644 index 0000000..a735c69 --- /dev/null +++ b/sim/golden/test_gnu_asm_fixtures.py @@ -0,0 +1,17 @@ +"""web/src/lib/__tests__/gnu-asm.json is current. Needs the RISC-V GNU toolchain (riscv-none-elf-gcc).""" + +import os +import shutil + +import pytest + +from golden.gen_asm_fixtures import main + + +def test_gnu_asm_fixtures_are_current(): + if shutil.which("riscv-none-elf-gcc") is None: + msg = "riscv-none-elf-gcc not on PATH (bash .tools/riscv-gcc.sh && source scripts/env.sh)" + if os.environ.get("REQUIRE_RISCV_TESTS") == "1": + pytest.fail(msg) + pytest.skip(msg) + assert main(["--check"]) == 0, "run: cd sim && python -m golden.gen_asm_fixtures" diff --git a/web/.github-placeholder b/web/.github-placeholder new file mode 100644 index 0000000..e69de29 diff --git a/web/.gitignore b/web/.gitignore index ab3d06f..acf423e 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,3 +1,5 @@ # Keep .vercel/project.json committed — it pins the project link so # re-linking is never needed. Only ignore the generated README inside. .vercel/README.txt +test-results/ +playwright-report/ diff --git a/web/e2e/app.spec.ts b/web/e2e/app.spec.ts new file mode 100644 index 0000000..28c04c6 --- /dev/null +++ b/web/e2e/app.spec.ts @@ -0,0 +1,48 @@ +// Smoke tests for the browser app, run against the production build. The first preset runs once the WASM +// core has loaded, so each test waits for "N cycles recorded" and then moves the cursor where it needs. +import { expect, type Page, test } from "@playwright/test"; + +const recorded = (page: Page) => page.getByText(/\d+ cycles recorded/); +const halted = (page: Page) => page.getByText(/Halted at cycle \d+ — PASS\./); + +test("the app loads the core and runs the default program to PASS", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (e) => errors.push(e.message)); + await page.goto("/app/"); + await expect(recorded(page)).toBeVisible({ timeout: 60_000 }); + await page.getByRole("button", { name: "Go to end" }).click(); + await expect(halted(page)).toBeVisible(); + await expect(page.locator("pre").filter({ hasText: "Hello, TinyCPU!" })).toBeVisible(); + expect(errors).toEqual([]); +}); + +test("the cycle controls step backwards and forwards", async ({ page }) => { + await page.goto("/app/"); + await expect(recorded(page)).toBeVisible({ timeout: 60_000 }); + await page.getByRole("button", { name: "Step forward" }).click(); + await expect(page.getByText(/^Cycle 2 of \d+\.$/)).toBeVisible(); + await page.getByRole("button", { name: "Step back" }).click(); + await expect(page.getByText(/^Cycle 1 of \d+\.$/)).toBeVisible(); + await page.getByRole("button", { name: "Go to end" }).click(); + await expect(halted(page)).toBeVisible(); + await page.getByRole("button", { name: "Go to start" }).click(); + await expect(page.getByText(/^Cycle 1 of \d+\.$/)).toBeVisible(); +}); + +test("an assembly error is reported with its line number", async ({ page }) => { + await page.goto("/app/"); + await expect(recorded(page)).toBeVisible({ timeout: 60_000 }); + await page.getByLabel("Assembly source").fill("_start:\n frobnicate a0\n"); + await page.getByRole("button", { name: "Assemble & run" }).click(); + await expect(page.getByRole("alert")).toContainText("line 2: unknown instruction 'frobnicate'"); +}); + +test("another preset prints its answer", async ({ page }) => { + await page.goto("/app/"); + await expect(recorded(page)).toBeVisible({ timeout: 60_000 }); + await page.getByLabel("Program").selectOption("fib"); + await page.getByRole("button", { name: "Assemble & run" }).click(); + await page.getByRole("button", { name: "Go to end" }).click(); + await expect(page.locator("pre").filter({ hasText: "6765" })).toBeVisible(); + await expect(halted(page)).toBeVisible(); +}); diff --git a/web/package.json b/web/package.json index 6214973..8e0e7c4 100644 --- a/web/package.json +++ b/web/package.json @@ -8,7 +8,9 @@ "preview": "astro preview", "lint": "eslint .", "typecheck": "astro check && tsc --noEmit", - "format": "prettier --write ." + "format": "prettier --write .", + "test": "vitest run", + "test:e2e": "playwright test" }, "dependencies": { "@astrojs/react": "^4.4.2", @@ -24,6 +26,7 @@ "devDependencies": { "@astrojs/check": "^0.9.4", "@eslint/js": "^9.13.0", + "@playwright/test": "1.56.1", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "^8.12.2", @@ -34,6 +37,7 @@ "globals": "^17.6.0", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.8", - "typescript": "^5.6.3" + "typescript": "^5.6.3", + "vitest": "3.2.4" } } diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 0000000..d2fc3a1 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig, devices } from "@playwright/test"; + +// The smoke test serves the production build (`pnpm build`) with `astro preview`. +// Browsers: `pnpm exec playwright install chromium` once (CI does this). +export default defineConfig({ + testDir: "e2e", + timeout: 60_000, + fullyParallel: true, + reporter: [["list"]], + use: { baseURL: "http://localhost:4321", trace: "retain-on-failure" }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: { + command: "pnpm preview --port 4321 --host localhost", + url: "http://localhost:4321/app/", + reuseExistingServer: true, + timeout: 120_000, + }, +}); diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 8128631..5060e3b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1,19 +1,20 @@ -lockfileVersion: "9.0" +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: + .: dependencies: - "@astrojs/react": + '@astrojs/react': specifier: ^4.4.2 version: 4.4.2(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(jiti@2.7.0)(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)(yaml@2.9.1) - "@fontsource-variable/inter": + '@fontsource-variable/inter': specifier: ^5.0.0 version: 5.3.0 - "@tailwindcss/vite": + '@tailwindcss/vite': specifier: ^4.0.0 version: 4.3.3(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1)) astro: @@ -35,22 +36,25 @@ importers: specifier: ^4.0.0 version: 4.3.3 devDependencies: - "@astrojs/check": + '@astrojs/check': specifier: ^0.9.4 version: 0.9.12(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3) - "@eslint/js": + '@eslint/js': specifier: ^9.13.0 version: 9.39.5 - "@types/react": + '@playwright/test': + specifier: 1.56.1 + version: 1.56.1 + '@types/react': specifier: ^18.3.12 version: 18.3.31 - "@types/react-dom": + '@types/react-dom': specifier: ^18.3.1 version: 18.3.7(@types/react@18.3.31) - "@typescript-eslint/eslint-plugin": + '@typescript-eslint/eslint-plugin': specifier: ^8.12.2 version: 8.72.0(@typescript-eslint/parser@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/parser": + '@typescript-eslint/parser': specifier: ^8.12.2 version: 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) eslint: @@ -74,1855 +78,1218 @@ importers: typescript: specifier: ^5.6.3 version: 5.9.3 + vitest: + specifier: 3.2.4 + version: 3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(supports-color@7.2.0)(yaml@2.9.1) packages: - "@astrojs/astro2tsx@0.1.3": - resolution: - { - integrity: sha512-GMDC7Eo0myBUA/QLBl0u+OcegJ8LiBhccmNZR34KmprQNP8gQUJlWlOOZWUsxNA2rNS6GuPYcGDyLoPnjmn4/Q==, - } - engines: { node: ^20.19.0 || >=22.12.0 } - - "@astrojs/check@0.9.12": - resolution: - { - integrity: sha512-hazpqxIfZWj2VhyPyR0pOEQ/IhFDM/2nIApL96zMXABdD8NrUKYDmhxTy7h+drTDl9MQG/Sd1ET/4amadVJUUA==, - } + + '@astrojs/astro2tsx@0.1.3': + resolution: {integrity: sha512-GMDC7Eo0myBUA/QLBl0u+OcegJ8LiBhccmNZR34KmprQNP8gQUJlWlOOZWUsxNA2rNS6GuPYcGDyLoPnjmn4/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@astrojs/check@0.9.12': + resolution: {integrity: sha512-hazpqxIfZWj2VhyPyR0pOEQ/IhFDM/2nIApL96zMXABdD8NrUKYDmhxTy7h+drTDl9MQG/Sd1ET/4amadVJUUA==} hasBin: true peerDependencies: typescript: ^5.0.0 || ^6.0.0 - "@astrojs/compiler@2.13.1": - resolution: - { - integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==, - } - - "@astrojs/internal-helpers@0.7.6": - resolution: - { - integrity: sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==, - } - - "@astrojs/language-server@2.17.3": - resolution: - { - integrity: sha512-QIAFu1e/4quJMTPdmBOm+ZPXc5NDGZE4/YQjgR3EgjhGLl/tdidaWJDr5WmO2lesVc+BW1qN/xNBURmuePDG2A==, - } + '@astrojs/compiler@2.13.1': + resolution: {integrity: sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==} + + '@astrojs/internal-helpers@0.7.6': + resolution: {integrity: sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==} + + '@astrojs/language-server@2.17.3': + resolution: {integrity: sha512-QIAFu1e/4quJMTPdmBOm+ZPXc5NDGZE4/YQjgR3EgjhGLl/tdidaWJDr5WmO2lesVc+BW1qN/xNBURmuePDG2A==} hasBin: true peerDependencies: prettier: ^3.0.0 - prettier-plugin-astro: ">=0.11.0" + prettier-plugin-astro: '>=0.11.0' peerDependenciesMeta: prettier: optional: true prettier-plugin-astro: optional: true - "@astrojs/markdown-remark@6.3.11": - resolution: - { - integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==, - } - - "@astrojs/prism@3.3.0": - resolution: - { - integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==, - } - engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } - - "@astrojs/react@4.4.2": - resolution: - { - integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==, - } - engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } + '@astrojs/markdown-remark@6.3.11': + resolution: {integrity: sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==} + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/react@4.4.2': + resolution: {integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} peerDependencies: - "@types/react": ^17.0.50 || ^18.0.21 || ^19.0.0 - "@types/react-dom": ^17.0.17 || ^18.0.6 || ^19.0.0 + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 react: ^17.0.2 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 - "@astrojs/telemetry@3.3.0": - resolution: - { - integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==, - } - engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0 } - - "@astrojs/yaml2ts@0.2.4": - resolution: - { - integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==, - } - - "@babel/code-frame@7.29.7": - resolution: - { - integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==, - } - engines: { node: ">=6.9.0" } - - "@babel/compat-data@7.29.7": - resolution: - { - integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==, - } - engines: { node: ">=6.9.0" } - - "@babel/core@7.29.7": - resolution: - { - integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==, - } - engines: { node: ">=6.9.0" } - - "@babel/generator@7.29.8": - resolution: - { - integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-compilation-targets@7.29.7": - resolution: - { - integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-globals@7.29.7": - resolution: - { - integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-module-imports@7.29.7": - resolution: - { - integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-module-transforms@7.29.7": - resolution: - { - integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==, - } - engines: { node: ">=6.9.0" } + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.4': + resolution: {integrity: sha512-8oddpOae35pJsXPQXhTkM0ypfKPskVsh2bCxRtbf7e+/Epw2nReakFYpLKjZMEr75CsoF203PMnCocpfz0s69A==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0 - - "@babel/helper-plugin-utils@7.29.7": - resolution: - { - integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-string-parser@7.29.7": - resolution: - { - integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-identifier@7.29.7": - resolution: - { - integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-option@7.29.7": - resolution: - { - integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==, - } - engines: { node: ">=6.9.0" } - - "@babel/helpers@7.29.10": - resolution: - { - integrity: sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==, - } - engines: { node: ">=6.9.0" } - - "@babel/parser@7.29.9": - resolution: - { - integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==, - } - engines: { node: ">=6.0.0" } + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.10': + resolution: {integrity: sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.9': + resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/plugin-transform-react-jsx-self@7.29.7": - resolution: - { - integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==, - } - engines: { node: ">=6.9.0" } + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-transform-react-jsx-source@7.29.7": - resolution: - { - integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/template@7.29.7": - resolution: - { - integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==, - } - engines: { node: ">=6.9.0" } - - "@babel/traverse@7.29.10": - resolution: - { - integrity: sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==, - } - engines: { node: ">=6.9.0" } - - "@babel/types@7.29.8": - resolution: - { - integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==, - } - engines: { node: ">=6.9.0" } - - "@capsizecss/unpack@4.0.1": - resolution: - { - integrity: sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==, - } - engines: { node: ">=18" } - - "@emmetio/abbreviation@2.3.3": - resolution: - { - integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==, - } - - "@emmetio/css-abbreviation@2.1.8": - resolution: - { - integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==, - } - - "@emmetio/css-parser@0.4.1": - resolution: - { - integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==, - } - - "@emmetio/html-matcher@1.3.0": - resolution: - { - integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==, - } - - "@emmetio/scanner@1.0.4": - resolution: - { - integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==, - } - - "@emmetio/stream-reader-utils@0.1.0": - resolution: - { - integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==, - } - - "@emmetio/stream-reader@2.2.0": - resolution: - { - integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==, - } - - "@emnapi/core@1.11.3": - resolution: - { - integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==, - } - - "@emnapi/runtime@1.11.3": - resolution: - { - integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==, - } - - "@emnapi/wasi-threads@1.2.3": - resolution: - { - integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==, - } - - "@esbuild/aix-ppc64@0.25.12": - resolution: - { - integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, - } - engines: { node: ">=18" } + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.10': + resolution: {integrity: sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@capsizecss/unpack@4.0.1': + resolution: {integrity: sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==} + engines: {node: '>=18'} + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/core@1.11.3': + resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@emnapi/wasi-threads@1.2.3': + resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - "@esbuild/aix-ppc64@0.27.7": - resolution: - { - integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==, - } - engines: { node: ">=18" } + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - "@esbuild/android-arm64@0.25.12": - resolution: - { - integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, - } - engines: { node: ">=18" } + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - "@esbuild/android-arm64@0.27.7": - resolution: - { - integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==, - } - engines: { node: ">=18" } + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - "@esbuild/android-arm@0.25.12": - resolution: - { - integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, - } - engines: { node: ">=18" } + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] - "@esbuild/android-arm@0.27.7": - resolution: - { - integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==, - } - engines: { node: ">=18" } + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} cpu: [arm] os: [android] - "@esbuild/android-x64@0.25.12": - resolution: - { - integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, - } - engines: { node: ">=18" } + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - "@esbuild/android-x64@0.27.7": - resolution: - { - integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==, - } - engines: { node: ">=18" } + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - "@esbuild/darwin-arm64@0.25.12": - resolution: - { - integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, - } - engines: { node: ">=18" } + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - "@esbuild/darwin-arm64@0.27.7": - resolution: - { - integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==, - } - engines: { node: ">=18" } + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - "@esbuild/darwin-x64@0.25.12": - resolution: - { - integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, - } - engines: { node: ">=18" } + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - "@esbuild/darwin-x64@0.27.7": - resolution: - { - integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==, - } - engines: { node: ">=18" } + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - "@esbuild/freebsd-arm64@0.25.12": - resolution: - { - integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-arm64@0.27.7": - resolution: - { - integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-x64@0.25.12": - resolution: - { - integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - "@esbuild/freebsd-x64@0.27.7": - resolution: - { - integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - "@esbuild/linux-arm64@0.25.12": - resolution: - { - integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - "@esbuild/linux-arm64@0.27.7": - resolution: - { - integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - "@esbuild/linux-arm@0.25.12": - resolution: - { - integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - "@esbuild/linux-arm@0.27.7": - resolution: - { - integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - "@esbuild/linux-ia32@0.25.12": - resolution: - { - integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, - } - engines: { node: ">=18" } + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - "@esbuild/linux-ia32@0.27.7": - resolution: - { - integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==, - } - engines: { node: ">=18" } + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - "@esbuild/linux-loong64@0.25.12": - resolution: - { - integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, - } - engines: { node: ">=18" } + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - "@esbuild/linux-loong64@0.27.7": - resolution: - { - integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==, - } - engines: { node: ">=18" } + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - "@esbuild/linux-mips64el@0.25.12": - resolution: - { - integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, - } - engines: { node: ">=18" } + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - "@esbuild/linux-mips64el@0.27.7": - resolution: - { - integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==, - } - engines: { node: ">=18" } + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - "@esbuild/linux-ppc64@0.25.12": - resolution: - { - integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, - } - engines: { node: ">=18" } + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - "@esbuild/linux-ppc64@0.27.7": - resolution: - { - integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==, - } - engines: { node: ">=18" } + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - "@esbuild/linux-riscv64@0.25.12": - resolution: - { - integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, - } - engines: { node: ">=18" } + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - "@esbuild/linux-riscv64@0.27.7": - resolution: - { - integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==, - } - engines: { node: ">=18" } + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - "@esbuild/linux-s390x@0.25.12": - resolution: - { - integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, - } - engines: { node: ">=18" } + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - "@esbuild/linux-s390x@0.27.7": - resolution: - { - integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==, - } - engines: { node: ">=18" } + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - "@esbuild/linux-x64@0.25.12": - resolution: - { - integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, - } - engines: { node: ">=18" } + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - "@esbuild/linux-x64@0.27.7": - resolution: - { - integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==, - } - engines: { node: ">=18" } + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - "@esbuild/netbsd-arm64@0.25.12": - resolution: - { - integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-arm64@0.27.7": - resolution: - { - integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-x64@0.25.12": - resolution: - { - integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - "@esbuild/netbsd-x64@0.27.7": - resolution: - { - integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - "@esbuild/openbsd-arm64@0.25.12": - resolution: - { - integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-arm64@0.27.7": - resolution: - { - integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-x64@0.25.12": - resolution: - { - integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - "@esbuild/openbsd-x64@0.27.7": - resolution: - { - integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - "@esbuild/openharmony-arm64@0.25.12": - resolution: - { - integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, - } - engines: { node: ">=18" } + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - "@esbuild/openharmony-arm64@0.27.7": - resolution: - { - integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==, - } - engines: { node: ">=18" } + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - "@esbuild/sunos-x64@0.25.12": - resolution: - { - integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, - } - engines: { node: ">=18" } + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - "@esbuild/sunos-x64@0.27.7": - resolution: - { - integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==, - } - engines: { node: ">=18" } + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - "@esbuild/win32-arm64@0.25.12": - resolution: - { - integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, - } - engines: { node: ">=18" } + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - "@esbuild/win32-arm64@0.27.7": - resolution: - { - integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==, - } - engines: { node: ">=18" } + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - "@esbuild/win32-ia32@0.25.12": - resolution: - { - integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, - } - engines: { node: ">=18" } + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - "@esbuild/win32-ia32@0.27.7": - resolution: - { - integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==, - } - engines: { node: ">=18" } + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - "@esbuild/win32-x64@0.25.12": - resolution: - { - integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, - } - engines: { node: ">=18" } + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - "@esbuild/win32-x64@0.27.7": - resolution: - { - integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==, - } - engines: { node: ">=18" } + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - "@eslint-community/eslint-utils@4.10.1": - resolution: - { - integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + '@eslint-community/eslint-utils@4.10.1': + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - "@eslint-community/regexpp@4.12.2": - resolution: - { - integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - "@eslint/config-array@0.21.2": - resolution: - { - integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/config-helpers@0.4.2": - resolution: - { - integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/core@0.17.0": - resolution: - { - integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/eslintrc@3.3.7": - resolution: - { - integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/js@9.39.5": - resolution: - { - integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/object-schema@2.1.7": - resolution: - { - integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/plugin-kit@0.4.1": - resolution: - { - integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@fontsource-variable/inter@5.3.0": - resolution: - { - integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==, - } - - "@humanfs/core@0.19.2": - resolution: - { - integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==, - } - engines: { node: ">=18.18.0" } - - "@humanfs/node@0.16.8": - resolution: - { - integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==, - } - engines: { node: ">=18.18.0" } - - "@humanfs/types@0.15.0": - resolution: - { - integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==, - } - engines: { node: ">=18.18.0" } - - "@humanwhocodes/module-importer@1.0.1": - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: ">=12.22" } - - "@humanwhocodes/retry@0.4.3": - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, - } - engines: { node: ">=18.18" } - - "@img/colour@1.1.0": - resolution: - { - integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==, - } - engines: { node: ">=18" } - - "@img/sharp-darwin-arm64@0.34.5": - resolution: - { - integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.7': + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@fontsource-variable/inter@5.3.0': + resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - "@img/sharp-darwin-x64@0.34.5": - resolution: - { - integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - "@img/sharp-libvips-darwin-arm64@1.2.4": - resolution: - { - integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==, - } + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] - "@img/sharp-libvips-darwin-x64@1.2.4": - resolution: - { - integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==, - } + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] - "@img/sharp-libvips-linux-arm64@1.2.4": - resolution: - { - integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==, - } + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] libc: [glibc] - "@img/sharp-libvips-linux-arm@1.2.4": - resolution: - { - integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==, - } + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] libc: [glibc] - "@img/sharp-libvips-linux-ppc64@1.2.4": - resolution: - { - integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==, - } + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] libc: [glibc] - "@img/sharp-libvips-linux-riscv64@1.2.4": - resolution: - { - integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==, - } + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] libc: [glibc] - "@img/sharp-libvips-linux-s390x@1.2.4": - resolution: - { - integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==, - } + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] libc: [glibc] - "@img/sharp-libvips-linux-x64@1.2.4": - resolution: - { - integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==, - } + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] libc: [glibc] - "@img/sharp-libvips-linuxmusl-arm64@1.2.4": - resolution: - { - integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==, - } + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] libc: [musl] - "@img/sharp-libvips-linuxmusl-x64@1.2.4": - resolution: - { - integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==, - } + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] libc: [musl] - "@img/sharp-linux-arm64@0.34.5": - resolution: - { - integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] libc: [glibc] - "@img/sharp-linux-arm@0.34.5": - resolution: - { - integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] libc: [glibc] - "@img/sharp-linux-ppc64@0.34.5": - resolution: - { - integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] libc: [glibc] - "@img/sharp-linux-riscv64@0.34.5": - resolution: - { - integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] libc: [glibc] - "@img/sharp-linux-s390x@0.34.5": - resolution: - { - integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] libc: [glibc] - "@img/sharp-linux-x64@0.34.5": - resolution: - { - integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] libc: [glibc] - "@img/sharp-linuxmusl-arm64@0.34.5": - resolution: - { - integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] libc: [musl] - "@img/sharp-linuxmusl-x64@0.34.5": - resolution: - { - integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] libc: [musl] - "@img/sharp-wasm32@0.34.5": - resolution: - { - integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - "@img/sharp-win32-arm64@0.34.5": - resolution: - { - integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - "@img/sharp-win32-ia32@0.34.5": - resolution: - { - integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - "@img/sharp-win32-x64@0.34.5": - resolution: - { - integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - "@jridgewell/gen-mapping@0.3.13": - resolution: - { - integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, - } - - "@jridgewell/remapping@2.3.5": - resolution: - { - integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, - } - - "@jridgewell/resolve-uri@3.1.2": - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/sourcemap-codec@1.6.0": - resolution: - { - integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==, - } - - "@jridgewell/trace-mapping@0.3.31": - resolution: - { - integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, - } - - "@napi-rs/lzma-linux-x64-gnu@1.5.1": - resolution: - { - integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==, - } - engines: { node: ^22.20 || ^24.12 || >=25 } + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} cpu: [x64] os: [linux] libc: [glibc] - "@napi-rs/wasm-runtime@1.2.4": - resolution: - { - integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=23.5.0 } + '@napi-rs/wasm-runtime@1.2.4': + resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==} + engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0} peerDependencies: - "@emnapi/core": ^1.7.1 || ^2.0.0-alpha.4 - "@emnapi/runtime": ^1.7.1 || ^2.0.0-alpha.4 - - "@oslojs/encoding@1.1.0": - resolution: - { - integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==, - } - - "@rolldown/pluginutils@1.0.0-beta.27": - resolution: - { - integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==, - } - - "@rollup/pluginutils@5.5.1": - resolution: - { - integrity: sha512-P9ZvnfouRu4MSC+ilERaw6FmZs8zqKhpHxEs4aGDGWQAMzQGXnhvCSvoNwokfNvdt7t3Y5Y4p6gStQgezeIqug==, - } - engines: { node: ">=14.0.0" } + '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4 + '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4 + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@playwright/test@1.56.1': + resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==} + engines: {node: '>=18'} + hasBin: true + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.5.1': + resolution: {integrity: sha512-P9ZvnfouRu4MSC+ilERaw6FmZs8zqKhpHxEs4aGDGWQAMzQGXnhvCSvoNwokfNvdt7t3Y5Y4p6gStQgezeIqug==} + engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - "@rollup/rollup-android-arm-eabi@4.65.0": - resolution: - { - integrity: sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==, - } + '@rollup/rollup-android-arm-eabi@4.65.0': + resolution: {integrity: sha512-t2AnVba25baFdq7gczzS13JyVcdubLVbFX1X04mxrggDrTQPUXNv3XM0k9H1hr5tqvg7AVvtQ720QHAj0iYqhg==} cpu: [arm] os: [android] - "@rollup/rollup-android-arm64@4.65.0": - resolution: - { - integrity: sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==, - } + '@rollup/rollup-android-arm64@4.65.0': + resolution: {integrity: sha512-dNONUaEwPGhl/nuO2KV8VABWlcjYyU6KtZGN8dQv5OS5JU7JSCxPijVNKXbRJd0iuTzTiJ4TjPt/4bbvDRNiMQ==} cpu: [arm64] os: [android] - "@rollup/rollup-darwin-arm64@4.65.0": - resolution: - { - integrity: sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==, - } + '@rollup/rollup-darwin-arm64@4.65.0': + resolution: {integrity: sha512-zWQqUH+9zavr2Bxuzfpuv2SIF3X31cr58TvSQC9o1GhpOvkqRwr60GkZdQcudyT/BEgdpdSafjgd2I13MbTL7g==} cpu: [arm64] os: [darwin] - "@rollup/rollup-darwin-x64@4.65.0": - resolution: - { - integrity: sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==, - } + '@rollup/rollup-darwin-x64@4.65.0': + resolution: {integrity: sha512-RQYreBFWtO4//hIuOaZkYUnF/lPU4lz35dveDimDshWCJvVisVtJ/shmT9bkXK8wFJY+FJF89X6F3Cq1qI3LCw==} cpu: [x64] os: [darwin] - "@rollup/rollup-freebsd-arm64@4.65.0": - resolution: - { - integrity: sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==, - } + '@rollup/rollup-freebsd-arm64@4.65.0': + resolution: {integrity: sha512-A0NfMj6Cuh/9BE5k3awJKv6HoxFVXIcLAb6SZqAMoxUHPA1ZNNK4RsGuUPD/vL7nEAm9LG+ZzXWWTrnC0XtkpA==} cpu: [arm64] os: [freebsd] - "@rollup/rollup-freebsd-x64@4.65.0": - resolution: - { - integrity: sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==, - } + '@rollup/rollup-freebsd-x64@4.65.0': + resolution: {integrity: sha512-BI6/H7q3aP0I7S7GOrjyxTjgEJlSnovXE38+bdxBpuKN6z+d//uF+T+VQ3Q7ltJL/71p17ufartARX9Ja5gOGQ==} cpu: [x64] os: [freebsd] - "@rollup/rollup-linux-arm-gnueabihf@4.65.0": - resolution: - { - integrity: sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==, - } + '@rollup/rollup-linux-arm-gnueabihf@4.65.0': + resolution: {integrity: sha512-W1vUprf09RWaVWnsFP1vlUkEWJzTZlpObOEAYZXt9Fq0UVAOYEkeGr7wBX7McKqqIQskXdBnpiOMgwPujordgA==} cpu: [arm] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm-musleabihf@4.65.0": - resolution: - { - integrity: sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==, - } + '@rollup/rollup-linux-arm-musleabihf@4.65.0': + resolution: {integrity: sha512-s8QTcZWgrl9haFzEGIqyE5BWyo89pFDGLMBFZ752WYxk8DJwNU/O1X6pZgPw829d9/sm6CCHyrMP3aurt76p1Q==} cpu: [arm] os: [linux] libc: [musl] - "@rollup/rollup-linux-arm64-gnu@4.65.0": - resolution: - { - integrity: sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==, - } + '@rollup/rollup-linux-arm64-gnu@4.65.0': + resolution: {integrity: sha512-ke1f6dLRzkqveyjjF2fkEwQYZvxHtNNwjegY8nJrwcO0Bdqr8CzFO/pKE0Bns13cx0qz2Ba2Y0Hs3dSwMdnHxA==} cpu: [arm64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-arm64-musl@4.65.0": - resolution: - { - integrity: sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==, - } + '@rollup/rollup-linux-arm64-musl@4.65.0': + resolution: {integrity: sha512-926SMeUcy4y18Rz/w2aJiUKOnyAnm6czg72bXkQEaeemsll+Wr0hnsjb2BGRpHvWgwAMYy5ACJCotbvfLCfj0A==} cpu: [arm64] os: [linux] libc: [musl] - "@rollup/rollup-linux-loong64-gnu@4.65.0": - resolution: - { - integrity: sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==, - } + '@rollup/rollup-linux-loong64-gnu@4.65.0': + resolution: {integrity: sha512-6T7JNRP1hc4hqF2dUsEoVLOt9K8tmODoCLldtQRYnrddGGQY8Gqe2pM/qMMUo7EMowpP7oa2N95k0/8sYK/ipw==} cpu: [loong64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-loong64-musl@4.65.0": - resolution: - { - integrity: sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==, - } + '@rollup/rollup-linux-loong64-musl@4.65.0': + resolution: {integrity: sha512-SImGFl1Je5kpyAgOCPONVT7h6ASV8mAUwGRjY7eZvVjPg//PfHOXgI+P241RF/+HqsA4PP3qD527ysWFKNQyhQ==} cpu: [loong64] os: [linux] libc: [musl] - "@rollup/rollup-linux-ppc64-gnu@4.65.0": - resolution: - { - integrity: sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==, - } + '@rollup/rollup-linux-ppc64-gnu@4.65.0': + resolution: {integrity: sha512-XeDTbmPF1Yu5asfXGPrMkWoxTDhgryQbmw5Q1+R582O51VNCk23J6f2pDrsAp5YkneBVBwtExx6ZGOb9D2aQ6A==} cpu: [ppc64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-ppc64-musl@4.65.0": - resolution: - { - integrity: sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==, - } + '@rollup/rollup-linux-ppc64-musl@4.65.0': + resolution: {integrity: sha512-VS55JUI/1nsljmV/jfqNkLZXFw8uMO8nNlfoHmSevNf6fO3neuhyDvx9dv3c/Ytt8r/431SL8Qax1dpYvOMZTg==} cpu: [ppc64] os: [linux] libc: [musl] - "@rollup/rollup-linux-riscv64-gnu@4.65.0": - resolution: - { - integrity: sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==, - } + '@rollup/rollup-linux-riscv64-gnu@4.65.0': + resolution: {integrity: sha512-Vblw84TbX7RS4la1Y4+yQ7aore/oSyaNtFbppuBr5Vs/4LfBeXKq9dIfoExw6zOvRERqd2rRAjnZRaMhH/0x5w==} cpu: [riscv64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-riscv64-musl@4.65.0": - resolution: - { - integrity: sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==, - } + '@rollup/rollup-linux-riscv64-musl@4.65.0': + resolution: {integrity: sha512-7+gHjtONvKgwR9InTKFaLq53f6YzBJkgeMRBENdWTsQr+spahGqQm8HF5aXpq8i4JaRrOqYyv6Zn1WltDTxaZQ==} cpu: [riscv64] os: [linux] libc: [musl] - "@rollup/rollup-linux-s390x-gnu@4.65.0": - resolution: - { - integrity: sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==, - } + '@rollup/rollup-linux-s390x-gnu@4.65.0': + resolution: {integrity: sha512-KvNDC0J6s4vVHhtJ6KrDXU0Er0au/hmRIBq/+1maCV8TseGupeKizq0bWcrbp2HE1T8xoknvN0Q/KZ/YF0qX3w==} cpu: [s390x] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-gnu@4.65.0": - resolution: - { - integrity: sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==, - } + '@rollup/rollup-linux-x64-gnu@4.65.0': + resolution: {integrity: sha512-GExdD15WPvu/x/drELjVam7vX8EsPgvTMOa/iJL/p/4m/oOqzbb7JXZg8fXR4hlXtg/sF2zyTZ3bR0AEtEMlhQ==} cpu: [x64] os: [linux] libc: [glibc] - "@rollup/rollup-linux-x64-musl@4.65.0": - resolution: - { - integrity: sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==, - } + '@rollup/rollup-linux-x64-musl@4.65.0': + resolution: {integrity: sha512-8sT9PemIZfIMED4Vd4KGoOXVDj5CMSWNcGdbheRUx9TYS03yOWdT2Q0lXPyEyNBB9YTilwewIQKNIBk6Nxi0Kg==} cpu: [x64] os: [linux] libc: [musl] - "@rollup/rollup-openbsd-x64@4.65.0": - resolution: - { - integrity: sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==, - } + '@rollup/rollup-openbsd-x64@4.65.0': + resolution: {integrity: sha512-pO6Ehi0jo5yE0R6PltKhJhv9mlIiyzBJk5N5NUF2OF6xtVKI+1bdXxj/rb0jGXy3hck1ELVT0QPY3tzkAm360w==} cpu: [x64] os: [openbsd] - "@rollup/rollup-openharmony-arm64@4.65.0": - resolution: - { - integrity: sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==, - } + '@rollup/rollup-openharmony-arm64@4.65.0': + resolution: {integrity: sha512-vo7N3/+cjjPDYr8vtxC+qSNgWa9sDvJ7g8N8R5SWqFY4WiNH1ULcAU70SKVyHeiIZOYYwNP9pTipmhN5sl1NPw==} cpu: [arm64] os: [openharmony] - "@rollup/rollup-win32-arm64-msvc@4.65.0": - resolution: - { - integrity: sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==, - } + '@rollup/rollup-win32-arm64-msvc@4.65.0': + resolution: {integrity: sha512-ArwvgFAbjKcDM7qqotZ4OVYf+UMT9xH2YzkCzK4+AuZSDihMlz1Ekoe4INwBVXzn2mnJzuIYOcSx07iTBijGRQ==} cpu: [arm64] os: [win32] - "@rollup/rollup-win32-ia32-msvc@4.65.0": - resolution: - { - integrity: sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==, - } + '@rollup/rollup-win32-ia32-msvc@4.65.0': + resolution: {integrity: sha512-QGMmK4/cxKE/36FoLMSHQBCyymv/eFTnsCvvsBF0rgmOvVbvX78VWZRerL3dHa77f4pM6Xcc2iGDhJ10w/svKQ==} cpu: [ia32] os: [win32] - "@rollup/rollup-win32-x64-gnu@4.65.0": - resolution: - { - integrity: sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==, - } + '@rollup/rollup-win32-x64-gnu@4.65.0': + resolution: {integrity: sha512-XfKYFWHIPs4HPODIMsGF1IKlS3H1e3WokUZIgpmn4+uNnZBlnRDDHEylNyazbQXwhMKZre68OjtRYbKqb5+j8w==} cpu: [x64] os: [win32] - "@rollup/rollup-win32-x64-msvc@4.65.0": - resolution: - { - integrity: sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==, - } + '@rollup/rollup-win32-x64-msvc@4.65.0': + resolution: {integrity: sha512-wPWi++K4Bs1VR4XihOhfGi4IGX6Cub7FlerTf8X2v+2BtDHddgSqWCXwGD+h2yAe4/niIhNDwAltioOC+wEogQ==} cpu: [x64] os: [win32] - "@shikijs/core@3.23.0": - resolution: - { - integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==, - } - - "@shikijs/engine-javascript@3.23.0": - resolution: - { - integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==, - } - - "@shikijs/engine-oniguruma@3.23.0": - resolution: - { - integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==, - } - - "@shikijs/langs@3.23.0": - resolution: - { - integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==, - } - - "@shikijs/themes@3.23.0": - resolution: - { - integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==, - } - - "@shikijs/types@3.23.0": - resolution: - { - integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==, - } - - "@shikijs/vscode-textmate@10.1.0": - resolution: - { - integrity: sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==, - } - - "@tailwindcss/node@4.3.3": - resolution: - { - integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==, - } - - "@tailwindcss/oxide-android-arm64@4.3.3": - resolution: - { - integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==, - } - engines: { node: ">= 20" } + '@shikijs/core@3.23.0': + resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} + + '@shikijs/engine-javascript@3.23.0': + resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} + + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} + + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + + '@shikijs/vscode-textmate@10.1.0': + resolution: {integrity: sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==} + + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} + + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - "@tailwindcss/oxide-darwin-arm64@4.3.3": - resolution: - { - integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - "@tailwindcss/oxide-darwin-x64@4.3.3": - resolution: - { - integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - "@tailwindcss/oxide-freebsd-x64@4.3.3": - resolution: - { - integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3": - resolution: - { - integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - "@tailwindcss/oxide-linux-arm64-gnu@4.3.3": - resolution: - { - integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - "@tailwindcss/oxide-linux-arm64-musl@4.3.3": - resolution: - { - integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - "@tailwindcss/oxide-linux-x64-gnu@4.3.3": - resolution: - { - integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - "@tailwindcss/oxide-linux-x64-musl@4.3.3": - resolution: - { - integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - "@tailwindcss/oxide-wasm32-wasi@4.3.3": - resolution: - { - integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==, - } - engines: { node: ">=14.0.0" } + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} + engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: - - "@napi-rs/wasm-runtime" - - "@emnapi/core" - - "@emnapi/runtime" - - "@tybys/wasm-util" - - "@emnapi/wasi-threads" + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' - tslib - "@tailwindcss/oxide-win32-arm64-msvc@4.3.3": - resolution: - { - integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - "@tailwindcss/oxide-win32-x64-msvc@4.3.3": - resolution: - { - integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==, - } - engines: { node: ">= 20" } + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - "@tailwindcss/oxide@4.3.3": - resolution: - { - integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==, - } - engines: { node: ">= 20" } - - "@tailwindcss/vite@4.3.3": - resolution: - { - integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==, - } + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 - "@tybys/wasm-util@0.10.4": - resolution: - { - integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==, - } - - "@types/babel__core@7.20.5": - resolution: - { - integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, - } - - "@types/babel__generator@7.27.0": - resolution: - { - integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, - } - - "@types/babel__template@7.4.4": - resolution: - { - integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, - } - - "@types/babel__traverse@7.28.0": - resolution: - { - integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, - } - - "@types/debug@4.1.13": - resolution: - { - integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==, - } - - "@types/estree@1.0.9": - resolution: - { - integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==, - } - - "@types/hast@3.0.5": - resolution: - { - integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==, - } - - "@types/json-schema@7.0.15": - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } - - "@types/mdast@4.0.4": - resolution: - { - integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, - } - - "@types/ms@2.1.0": - resolution: - { - integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, - } - - "@types/nlcst@2.0.3": - resolution: - { - integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==, - } - - "@types/prop-types@15.7.15": - resolution: - { - integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==, - } - - "@types/react-dom@18.3.7": - resolution: - { - integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==, - } + '@tybys/wasm-util@0.10.4': + resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/nlcst@2.0.3': + resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - "@types/react": ^18.0.0 - - "@types/react@18.3.31": - resolution: - { - integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==, - } - - "@types/unist@3.0.3": - resolution: - { - integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, - } - - "@typescript-eslint/eslint-plugin@8.72.0": - resolution: - { - integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@types/react': ^18.0.0 + + '@types/react@18.3.31': + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@typescript-eslint/eslint-plugin@8.72.0': + resolution: {integrity: sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - "@typescript-eslint/parser": ^8.72.0 + '@typescript-eslint/parser': ^8.72.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/parser@8.72.0": - resolution: - { - integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.72.0': + resolution: {integrity: sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/project-service@8.72.0": - resolution: - { - integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.72.0': + resolution: {integrity: sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/scope-manager@8.72.0": - resolution: - { - integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@typescript-eslint/tsconfig-utils@8.72.0": - resolution: - { - integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.72.0': + resolution: {integrity: sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.72.0': + resolution: {integrity: sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/type-utils@8.72.0": - resolution: - { - integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.72.0': + resolution: {integrity: sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/types@8.72.0": - resolution: - { - integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@typescript-eslint/typescript-estree@8.72.0": - resolution: - { - integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.72.0': + resolution: {integrity: sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.72.0': + resolution: {integrity: sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/utils@8.72.0": - resolution: - { - integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.72.0': + resolution: {integrity: sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: ">=4.8.4 <6.1.0" - - "@typescript-eslint/visitor-keys@8.72.0": - resolution: - { - integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@ungap/structured-clone@1.4.0": - resolution: - { - integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==, - } - - "@vitejs/plugin-react@4.7.0": - resolution: - { - integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==, - } - engines: { node: ^14.18.0 || >=16.0.0 } + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.72.0': + resolution: {integrity: sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - "@volar/kit@2.4.30": - resolution: - { - integrity: sha512-VocLtumfXnqnPQEWc9Yx7uC1uOh9gyjVNlrn2+6Zx/z9aM6yTLhPK8lSqM9Ilu81XRa48I00lcYJWLi2BLxx8Q==, - } + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + '@volar/kit@2.4.30': + resolution: {integrity: sha512-VocLtumfXnqnPQEWc9Yx7uC1uOh9gyjVNlrn2+6Zx/z9aM6yTLhPK8lSqM9Ilu81XRa48I00lcYJWLi2BLxx8Q==} peerDependencies: typescript: <7 - "@volar/language-core@2.4.30": - resolution: - { - integrity: sha512-73fjwEV8wat6aOZ6U/y/hqtE30ocrIWECg67b0txGczOMNw+MffLmvJ5fnFfN9drVUEYZzLdaNo6VAy6G6avMw==, - } - - "@volar/language-server@2.4.30": - resolution: - { - integrity: sha512-tcP9fzzUBbsLIFHhgnRU4ncQjMn9G9Vax0ffkQmdCrXVEYqRroUepmyC5AVgiju5bCiLD6a55qen5iOTQHkb3g==, - } + '@volar/language-core@2.4.30': + resolution: {integrity: sha512-73fjwEV8wat6aOZ6U/y/hqtE30ocrIWECg67b0txGczOMNw+MffLmvJ5fnFfN9drVUEYZzLdaNo6VAy6G6avMw==} + + '@volar/language-server@2.4.30': + resolution: {integrity: sha512-tcP9fzzUBbsLIFHhgnRU4ncQjMn9G9Vax0ffkQmdCrXVEYqRroUepmyC5AVgiju5bCiLD6a55qen5iOTQHkb3g==} peerDependencies: typescript: <7 peerDependenciesMeta: typescript: optional: true - "@volar/language-service@2.4.30": - resolution: - { - integrity: sha512-w/xDsASiqC71ggCOm7sTDXDNk/l72XEptodeRC68tgZ0e5QM4hM3zAqBGC7mBctvhrpYwKfMG+t9xcUo2hEFtA==, - } + '@volar/language-service@2.4.30': + resolution: {integrity: sha512-w/xDsASiqC71ggCOm7sTDXDNk/l72XEptodeRC68tgZ0e5QM4hM3zAqBGC7mBctvhrpYwKfMG+t9xcUo2hEFtA==} peerDependencies: typescript: <7 peerDependenciesMeta: typescript: optional: true - "@volar/source-map@2.4.30": - resolution: - { - integrity: sha512-zym1ob/KwDDyN+0jGVtxwbxJSRRnUvv5HspCtz3yEfVVdCG4/dzxJktMGPAgemqRuVs81O0o/y0q3qOUErR8kg==, - } - - "@volar/typescript@2.4.30": - resolution: - { - integrity: sha512-/DgA5tgYFPfrLFuSjIMXMGHk1LQv2+P8eny6+BhGNzFJELWIW7UZlc56ZLIRFhLsPpMfIFxM8/HvOPBggrjTZg==, - } + '@volar/source-map@2.4.30': + resolution: {integrity: sha512-zym1ob/KwDDyN+0jGVtxwbxJSRRnUvv5HspCtz3yEfVVdCG4/dzxJktMGPAgemqRuVs81O0o/y0q3qOUErR8kg==} + + '@volar/typescript@2.4.30': + resolution: {integrity: sha512-/DgA5tgYFPfrLFuSjIMXMGHk1LQv2+P8eny6+BhGNzFJELWIW7UZlc56ZLIRFhLsPpMfIFxM8/HvOPBggrjTZg==} peerDependencies: typescript: <7 peerDependenciesMeta: typescript: optional: true - "@vscode/emmet-helper@2.11.0": - resolution: - { - integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==, - } + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} - "@vscode/l10n@0.0.18": - resolution: - { - integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==, - } + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.19.0: - resolution: - { - integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} + engines: {node: '>=0.4.0'} hasBin: true ajv-draft-04@1.0.0: - resolution: - { - integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==, - } + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: ajv: ^8.5.0 peerDependenciesMeta: @@ -1930,925 +1297,544 @@ packages: optional: true ajv-i18n@4.2.0: - resolution: - { - integrity: sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==, - } + resolution: {integrity: sha512-v/ei2UkCEeuKNXh8RToiFsUclmU+G57LO1Oo22OagNMENIw+Yb8eMwvHu7Vn9fmkjJyv6XclhJ8TbuigSglPkg==} peerDependencies: ajv: ^8.0.0-beta.0 ajv@6.15.0: - resolution: - { - integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, - } + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.20.0: - resolution: - { - integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==, - } + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-align@3.0.1: - resolution: - { - integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==, - } + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} ansi-regex@5.0.1: - resolution: - { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} ansi-regex@6.4.0: - resolution: - { - integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} ansi-styles@6.2.3: - resolution: - { - integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} anymatch@3.1.3: - resolution: - { - integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} argparse@2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} aria-query@5.3.2: - resolution: - { - integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} array-buffer-byte-length@1.0.2: - resolution: - { - integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} array-includes@3.2.0: - resolution: - { - integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==} + engines: {node: '>= 0.4'} array-iterate@2.0.1: - resolution: - { - integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==, - } + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} array.prototype.findlast@1.2.5: - resolution: - { - integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: - resolution: - { - integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} array.prototype.flatmap@1.3.3: - resolution: - { - integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} array.prototype.tosorted@1.1.4: - resolution: - { - integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} arraybuffer.prototype.slice@1.0.4: - resolution: - { - integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} astro@5.18.2: - resolution: - { - integrity: sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ==, - } - engines: { node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: ">=9.6.5", pnpm: ">=7.1.0" } + resolution: {integrity: sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} hasBin: true async-function@1.0.0: - resolution: - { - integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} available-typed-arrays@1.0.7: - resolution: - { - integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} axobject-query@4.1.0: - resolution: - { - integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} bail@2.0.2: - resolution: - { - integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, - } + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} balanced-match@4.0.4: - resolution: - { - integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} base-64@1.0.0: - resolution: - { - integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==, - } + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} baseline-browser-mapping@2.11.31: - resolution: - { - integrity: sha512-b0k7wXlWjpuL/Uxb/pYlpjY0ej5BjAmurtPUk3eRGuloI4yjK32m8TiKUQ81l1ptcLjKS/KH2+uqIolZti1x4Q==, - } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-b0k7wXlWjpuL/Uxb/pYlpjY0ej5BjAmurtPUk3eRGuloI4yjK32m8TiKUQ81l1ptcLjKS/KH2+uqIolZti1x4Q==} + engines: {node: '>=6.0.0'} hasBin: true boolbase@1.0.0: - resolution: - { - integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, - } + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} boxen@8.0.1: - resolution: - { - integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} brace-expansion@1.1.21: - resolution: - { - integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==, - } + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} brace-expansion@5.0.12: - resolution: - { - integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==, - } - engines: { node: 20 || >=22 } + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} browserslist@4.29.4: - resolution: - { - integrity: sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + resolution: {integrity: sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.9: - resolution: - { - integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { - integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} camelcase@8.0.0: - resolution: - { - integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} caniuse-lite@1.0.30001819: - resolution: - { - integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==, - } + resolution: {integrity: sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==} ccount@2.0.1: - resolution: - { - integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, - } + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} chalk@5.6.2: - resolution: - { - integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, - } - engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} character-entities-html4@2.1.0: - resolution: - { - integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, - } + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} character-entities-legacy@3.0.0: - resolution: - { - integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, - } + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} character-entities@2.0.2: - resolution: - { - integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, - } + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} chokidar@5.0.0: - resolution: - { - integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==, - } - engines: { node: ">= 20.19.0" } + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} ci-info@4.4.0: - resolution: - { - integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} cli-boxes@3.0.0: - resolution: - { - integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} cliui@9.0.1: - resolution: - { - integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==, - } - engines: { node: ">=20" } + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} clsx@2.1.1: - resolution: - { - integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: ">=7.0.0" } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} comma-separated-tokens@2.0.3: - resolution: - { - integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, - } + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} commander@11.1.0: - resolution: - { - integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} common-ancestor-path@1.0.1: - resolution: - { - integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==, - } + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, - } + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@1.2.3: - resolution: - { - integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==, - } + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} cookie@1.1.1: - resolution: - { - integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} crossws@0.3.5: - resolution: - { - integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==, - } + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} css-select@6.0.0: - resolution: - { - integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==, - } + resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} css-tree@2.2.1: - resolution: - { - integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==, - } - engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} css-tree@3.2.1: - resolution: - { - integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==, - } - engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} css-what@7.0.0: - resolution: - { - integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==} + engines: {node: '>= 6'} cssesc@3.0.0: - resolution: - { - integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} hasBin: true csso@5.0.5: - resolution: - { - integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==, - } - engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: ">=7.0.0" } + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} csstype@3.2.3: - resolution: - { - integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, - } + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} data-view-buffer@1.0.2: - resolution: - { - integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} data-view-byte-length@1.0.2: - resolution: - { - integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: - resolution: - { - integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} debug@4.4.3: - resolution: - { - integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, - } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true decode-named-character-reference@1.3.0: - resolution: - { - integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==, - } + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} define-data-property@1.1.4: - resolution: - { - integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-properties@1.2.1: - resolution: - { - integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} defu@6.1.7: - resolution: - { - integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==, - } + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} dequal@2.0.3: - resolution: - { - integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} destr@2.0.5: - resolution: - { - integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, - } + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} deterministic-object-hash@2.0.2: - resolution: - { - integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} devalue@5.9.4: - resolution: - { - integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==, - } + resolution: {integrity: sha512-sPAT4pztbu6586/hrhOnMKS17IJrvg12mXiSPSS3W5qDeN2RGgvZ0diZCm31dBbnevfVmujNO3IM2wrS4Y2Rhg==} devlop@1.1.0: - resolution: - { - integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, - } + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} diff@8.0.4: - resolution: - { - integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==, - } - engines: { node: ">=0.3.1" } + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} dlv@1.1.3: - resolution: - { - integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==, - } + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} doctrine@2.1.0: - resolution: - { - integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dom-serializer@2.0.0: - resolution: - { - integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, - } + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} domelementtype@2.3.0: - resolution: - { - integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, - } + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} domhandler@5.0.3: - resolution: - { - integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} domutils@3.2.2: - resolution: - { - integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==, - } + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dset@3.1.4: - resolution: - { - integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} electron-to-chromium@1.5.463: - resolution: - { - integrity: sha512-nVWNU6ltFjopvOdD44J4zoBkgO0M+WZiFKKFV7EjebOH9z0KNphk6bCEjplXtJofL21ARIuuwWOCsFkBVklm6w==, - } + resolution: {integrity: sha512-nVWNU6ltFjopvOdD44J4zoBkgO0M+WZiFKKFV7EjebOH9z0KNphk6bCEjplXtJofL21ARIuuwWOCsFkBVklm6w==} emmet@2.4.11: - resolution: - { - integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==, - } + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} emoji-regex@10.6.0: - resolution: - { - integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, - } + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: - resolution: - { - integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, - } + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} enhanced-resolve@5.26.0: - resolution: - { - integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} + engines: {node: '>=10.13.0'} entities@4.5.0: - resolution: - { - integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, - } - engines: { node: ">=0.12" } + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} entities@6.0.1: - resolution: - { - integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, - } - engines: { node: ">=0.12" } + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} es-abstract-get@1.0.0: - resolution: - { - integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} es-abstract@1.24.2: - resolution: - { - integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} es-define-property@1.0.1: - resolution: - { - integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { - integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-iterator-helpers@1.4.0: - resolution: - { - integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} + engines: {node: '>= 0.4'} es-module-lexer@1.7.0: - resolution: - { - integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, - } + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} es-object-atoms@1.1.2: - resolution: - { - integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: - resolution: - { - integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} es-shim-unscopables@1.1.0: - resolution: - { - integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} es-to-primitive@1.3.4: - resolution: - { - integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} esbuild@0.25.12: - resolution: - { - integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} hasBin: true esbuild@0.27.7: - resolution: - { - integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: - resolution: - { - integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} escape-string-regexp@5.0.0: - resolution: - { - integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} eslint-plugin-react-hooks@5.2.0: - resolution: - { - integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.5: - resolution: - { - integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-scope@8.4.0: - resolution: - { - integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@4.2.1: - resolution: - { - integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@5.0.1: - resolution: - { - integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint@9.39.5: - resolution: - { - integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: - jiti: "*" + jiti: '*' peerDependenciesMeta: jiti: optional: true espree@10.4.0: - resolution: - { - integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.7.0: - resolution: - { - integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} estree-walker@2.0.2: - resolution: - { - integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, - } + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} estree-walker@3.0.3: - resolution: - { - integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, - } + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} eventemitter3@5.0.4: - resolution: - { - integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, - } + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} extend@3.0.2: - resolution: - { - integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, - } + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fast-uri@3.1.9: - resolution: - { - integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==, - } + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} fdir@6.5.0: - resolution: - { - integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2856,1494 +1842,868 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, - } - engines: { node: ">=16.0.0" } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.4.4: - resolution: - { - integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==, - } + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} flattie@1.1.1: - resolution: - { - integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} fontace@0.4.1: - resolution: - { - integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==, - } + resolution: {integrity: sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==} fontkitten@1.0.3: - resolution: - { - integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==, - } - engines: { node: ">=20" } + resolution: {integrity: sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==} + engines: {node: '>=20'} for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, - } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.2.0: - resolution: - { - integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} functions-have-names@1.2.3: - resolution: - { - integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, - } + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} generator-function@2.0.1: - resolution: - { - integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} gensync@1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} get-caller-file@2.0.5: - resolution: - { - integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, - } - engines: { node: 6.* || 8.* || >= 10.* } + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-east-asian-width@1.7.0: - resolution: - { - integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} + engines: {node: '>=18'} get-intrinsic@1.3.0: - resolution: - { - integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-proto@1.0.1: - resolution: - { - integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-symbol-description@1.1.0: - resolution: - { - integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} github-slugger@2.0.0: - resolution: - { - integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==, - } + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} globals@14.0.0: - resolution: - { - integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globals@17.13.0: - resolution: - { - integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==} + engines: {node: '>=18'} globalthis@1.0.4: - resolution: - { - integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} h3@1.15.11: - resolution: - { - integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==, - } + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} has-bigints@1.1.0: - resolution: - { - integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { - integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, - } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: - resolution: - { - integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} has-symbols@1.1.0: - resolution: - { - integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-tostringtag@1.0.2: - resolution: - { - integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} hasown@2.0.4: - resolution: - { - integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} hast-util-from-html@2.0.3: - resolution: - { - integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==, - } + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} hast-util-from-parse5@8.0.3: - resolution: - { - integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==, - } + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} hast-util-is-element@3.0.0: - resolution: - { - integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==, - } + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} hast-util-parse-selector@4.0.0: - resolution: - { - integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==, - } + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} hast-util-raw@9.1.0: - resolution: - { - integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==, - } + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} hast-util-to-html@9.0.5: - resolution: - { - integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==, - } + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-to-parse5@8.0.1: - resolution: - { - integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==, - } + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} hast-util-to-text@4.0.2: - resolution: - { - integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==, - } + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} hast-util-whitespace@3.0.0: - resolution: - { - integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, - } + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} hastscript@9.0.1: - resolution: - { - integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==, - } + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} html-escaper@3.0.3: - resolution: - { - integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==, - } + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} html-void-elements@3.0.0: - resolution: - { - integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, - } + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} http-cache-semantics@4.3.0: - resolution: - { - integrity: sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==, - } + resolution: {integrity: sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==} ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.13: - resolution: - { - integrity: sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==} + engines: {node: '>= 4'} import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} import-meta-resolve@4.2.0: - resolution: - { - integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==, - } + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: ">=0.8.19" } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} internal-slot@1.1.0: - resolution: - { - integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} iron-webcrypto@1.2.1: - resolution: - { - integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==, - } + resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} is-array-buffer@3.0.5: - resolution: - { - integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} is-async-function@2.1.1: - resolution: - { - integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} is-bigint@1.1.0: - resolution: - { - integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} is-boolean-object@1.2.2: - resolution: - { - integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} is-callable@1.2.7: - resolution: - { - integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-core-module@2.17.0: - resolution: - { - integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==} + engines: {node: '>= 0.4'} is-data-view@1.0.2: - resolution: - { - integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} is-date-object@1.1.0: - resolution: - { - integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} is-docker@3.0.0: - resolution: - { - integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true is-document.all@1.0.0: - resolution: - { - integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-finalizationregistry@1.1.1: - resolution: - { - integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} is-fullwidth-code-point@3.0.0: - resolution: - { - integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} is-generator-function@1.1.2: - resolution: - { - integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-inside-container@1.0.0: - resolution: - { - integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, - } - engines: { node: ">=14.16" } + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} hasBin: true is-map@2.0.3: - resolution: - { - integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} is-negative-zero@2.0.3: - resolution: - { - integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} is-number-object@1.1.1: - resolution: - { - integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} is-plain-obj@4.1.0: - resolution: - { - integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} is-regex@1.2.1: - resolution: - { - integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} is-set@2.0.3: - resolution: - { - integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} is-shared-array-buffer@1.0.4: - resolution: - { - integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} is-string@1.1.1: - resolution: - { - integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} is-symbol@1.1.1: - resolution: - { - integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} is-typed-array@1.1.15: - resolution: - { - integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-weakmap@2.0.2: - resolution: - { - integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} is-weakref@1.1.1: - resolution: - { - integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} is-weakset@2.0.4: - resolution: - { - integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} is-wsl@3.1.1: - resolution: - { - integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} isarray@2.0.5: - resolution: - { - integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, - } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} iterator.prototype@1.1.5: - resolution: - { - integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} jiti@2.7.0: - resolution: - { - integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==, - } + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} js-yaml@4.3.2: - resolution: - { - integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==, - } + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, - } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: - resolution: - { - integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, - } + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: - resolution: - { - integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} hasBin: true jsonc-parser@2.3.1: - resolution: - { - integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==, - } + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} jsonc-parser@3.3.1: - resolution: - { - integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, - } + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} jsx-ast-utils@3.3.5: - resolution: - { - integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, - } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} kleur@3.0.3: - resolution: - { - integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} kleur@4.1.5: - resolution: - { - integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lightningcss-android-arm64@1.32.0: - resolution: - { - integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] lightningcss-darwin-arm64@1.32.0: - resolution: - { - integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.32.0: - resolution: - { - integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.32.0: - resolution: - { - integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: - { - integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.32.0: - resolution: - { - integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: - resolution: - { - integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] lightningcss-linux-x64-gnu@1.32.0: - resolution: - { - integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] lightningcss-linux-x64-musl@1.32.0: - resolution: - { - integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: - resolution: - { - integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.32.0: - resolution: - { - integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.32.0: - resolution: - { - integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, - } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} longest-streak@3.1.0: - resolution: - { - integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, - } + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@11.5.3: - resolution: - { - integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==, - } - engines: { node: 20 || >=22 } + resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} + engines: {node: 20 || >=22} lru-cache@5.1.1: - resolution: - { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, - } + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} magic-string@0.30.21: - resolution: - { - integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, - } + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} magicast@0.5.5: - resolution: - { - integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==, - } + resolution: {integrity: sha512-UicdXN8zQ3JHlxVq+28afMXPr1z7WNY6+7EJnzTdQWkTAlMLF5fNCCKxJHBQwGaNGR11581EiQmQzx73+MvszA==} markdown-table@3.0.4: - resolution: - { - integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, - } + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} mdast-util-definitions@6.0.0: - resolution: - { - integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==, - } + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} mdast-util-find-and-replace@3.0.3: - resolution: - { - integrity: sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw==, - } + resolution: {integrity: sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw==} mdast-util-from-markdown@2.1.0: - resolution: - { - integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==, - } + resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} mdast-util-gfm-autolink-literal@2.0.1: - resolution: - { - integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, - } + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} mdast-util-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, - } + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} mdast-util-gfm-strikethrough@2.0.1: - resolution: - { - integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==, - } + resolution: {integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==} mdast-util-gfm-table@2.0.0: - resolution: - { - integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, - } + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} mdast-util-gfm-task-list-item@2.0.0: - resolution: - { - integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, - } + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} mdast-util-gfm@3.1.0: - resolution: - { - integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, - } + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} mdast-util-phrasing@4.1.0: - resolution: - { - integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, - } + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} mdast-util-to-hast@13.2.1: - resolution: - { - integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==, - } + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} mdast-util-to-markdown@2.2.0: - resolution: - { - integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==, - } + resolution: {integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==} mdast-util-to-string@4.0.0: - resolution: - { - integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, - } + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} mdn-data@2.0.28: - resolution: - { - integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==, - } + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} mdn-data@2.27.1: - resolution: - { - integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==, - } + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} micromark-core-commonmark@2.0.4: - resolution: - { - integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==, - } + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} micromark-extension-gfm-autolink-literal@2.1.0: - resolution: - { - integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, - } + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} micromark-extension-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, - } + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} micromark-extension-gfm-strikethrough@2.1.0: - resolution: - { - integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, - } + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} micromark-extension-gfm-table@2.1.2: - resolution: - { - integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==, - } + resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} micromark-extension-gfm-tagfilter@2.0.0: - resolution: - { - integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, - } + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} micromark-extension-gfm-task-list-item@2.1.0: - resolution: - { - integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, - } + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} micromark-extension-gfm@3.0.0: - resolution: - { - integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, - } + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} micromark-factory-destination@2.0.1: - resolution: - { - integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, - } + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} micromark-factory-label@2.0.1: - resolution: - { - integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, - } + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} micromark-factory-space@2.1.0: - resolution: - { - integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==, - } + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} micromark-factory-title@2.0.1: - resolution: - { - integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, - } + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} micromark-factory-whitespace@2.0.1: - resolution: - { - integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, - } + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} micromark-util-character@2.1.1: - resolution: - { - integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, - } + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} micromark-util-chunked@2.0.1: - resolution: - { - integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, - } + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} micromark-util-classify-character@2.0.1: - resolution: - { - integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, - } + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} micromark-util-combine-extensions@2.0.1: - resolution: - { - integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, - } + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} micromark-util-decode-numeric-character-reference@2.0.2: - resolution: - { - integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, - } + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} micromark-util-decode-string@2.0.1: - resolution: - { - integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, - } + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} micromark-util-edit-map@1.0.0: - resolution: - { - integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==, - } + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} micromark-util-encode@2.0.1: - resolution: - { - integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, - } + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} micromark-util-html-tag-name@2.0.1: - resolution: - { - integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, - } + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} micromark-util-normalize-identifier@2.0.1: - resolution: - { - integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, - } + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} micromark-util-resolve-all@2.0.1: - resolution: - { - integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, - } + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} micromark-util-sanitize-uri@2.0.1: - resolution: - { - integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, - } + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} micromark-util-subtokenize@2.1.0: - resolution: - { - integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, - } + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} micromark-util-symbol@2.0.1: - resolution: - { - integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, - } + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} micromark-util-types@2.0.3: - resolution: - { - integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==, - } + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} micromark@4.0.3: - resolution: - { - integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==, - } + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} minimatch@10.2.6: - resolution: - { - integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} minimatch@3.1.5: - resolution: - { - integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, - } + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} mrmime@2.0.1: - resolution: - { - integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} muggle-string@0.4.1: - resolution: - { - integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==, - } + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} nanoid@3.3.20: - resolution: - { - integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} neotraverse@0.6.18: - resolution: - { - integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==, - } - engines: { node: ">= 10" } + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} nlcst-to-string@4.0.0: - resolution: - { - integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==, - } + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} node-exports-info@1.6.2: - resolution: - { - integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} + engines: {node: '>= 0.4'} node-fetch-native@1.6.7: - resolution: - { - integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==, - } + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} node-mock-http@1.0.5: - resolution: - { - integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==, - } + resolution: {integrity: sha512-KQyt/wLjG3TAc7DOUhpqWzgd4ERxR80JOlTK5VE5R1S12IaPVN5qkj4klBce9HPG1Njuup4Sb5bljaT34lIyjw==} node-releases@2.0.60: - resolution: - { - integrity: sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==} + engines: {node: '>=18'} normalize-path@3.0.0: - resolution: - { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} nth-check@2.1.1: - resolution: - { - integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, - } + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} object-assign@4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} object-inspect@1.13.4: - resolution: - { - integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} object.assign@4.1.7: - resolution: - { - integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} object.entries@1.1.9: - resolution: - { - integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} object.fromentries@2.0.8: - resolution: - { - integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} object.values@1.2.1: - resolution: - { - integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} ofetch@1.5.1: - resolution: - { - integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==, - } + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} ohash@2.0.12: - resolution: - { - integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==, - } + resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} oniguruma-parser@0.12.2: - resolution: - { - integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==, - } + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} oniguruma-to-es@4.3.6: - resolution: - { - integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==, - } + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} own-keys@1.0.2: - resolution: - { - integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} + engines: {node: '>= 0.4'} p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-limit@6.2.0: - resolution: - { - integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} p-queue@8.1.1: - resolution: - { - integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} p-timeout@6.1.4: - resolution: - { - integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==, - } - engines: { node: ">=14.16" } + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} package-manager-detector@1.9.0: - resolution: - { - integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==, - } + resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parse-latin@7.0.0: - resolution: - { - integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==, - } + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} parse5@7.3.0: - resolution: - { - integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==, - } + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} path-browserify@1.0.1: - resolution: - { - integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, - } + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} piccolore@0.1.3: - resolution: - { - integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==, - } + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.2: - resolution: - { - integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} picomatch@4.0.7: - resolution: - { - integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + playwright-core@1.56.1: + resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.56.1: + resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==} + engines: {node: '>=18'} + hasBin: true possible-typed-array-names@1.1.0: - resolution: - { - integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} postcss@8.5.31: - resolution: - { - integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==} + engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prettier-plugin-tailwindcss@0.6.14: - resolution: - { - integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==, - } - engines: { node: ">=14.21.3" } + resolution: {integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==} + engines: {node: '>=14.21.3'} peerDependencies: - "@ianvs/prettier-plugin-sort-imports": "*" - "@prettier/plugin-hermes": "*" - "@prettier/plugin-oxc": "*" - "@prettier/plugin-pug": "*" - "@shopify/prettier-plugin-liquid": "*" - "@trivago/prettier-plugin-sort-imports": "*" - "@zackad/prettier-plugin-twig": "*" + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-hermes': '*' + '@prettier/plugin-oxc': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig': '*' prettier: ^3.0 - prettier-plugin-astro: "*" - prettier-plugin-css-order: "*" - prettier-plugin-import-sort: "*" - prettier-plugin-jsdoc: "*" - prettier-plugin-marko: "*" - prettier-plugin-multiline-arrays: "*" - prettier-plugin-organize-attributes: "*" - prettier-plugin-organize-imports: "*" - prettier-plugin-sort-imports: "*" - prettier-plugin-style-order: "*" - prettier-plugin-svelte: "*" + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' peerDependenciesMeta: - "@ianvs/prettier-plugin-sort-imports": + '@ianvs/prettier-plugin-sort-imports': optional: true - "@prettier/plugin-hermes": + '@prettier/plugin-hermes': optional: true - "@prettier/plugin-oxc": + '@prettier/plugin-oxc': optional: true - "@prettier/plugin-pug": + '@prettier/plugin-pug': optional: true - "@shopify/prettier-plugin-liquid": + '@shopify/prettier-plugin-liquid': optional: true - "@trivago/prettier-plugin-sort-imports": + '@trivago/prettier-plugin-sort-imports': optional: true - "@zackad/prettier-plugin-twig": + '@zackad/prettier-plugin-twig': optional: true prettier-plugin-astro: optional: true @@ -4369,568 +2729,346 @@ packages: optional: true prettier@3.9.10: - resolution: - { - integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} + engines: {node: '>=14'} hasBin: true prismjs@1.30.0: - resolution: - { - integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} prompts@2.4.2: - resolution: - { - integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} prop-types@15.8.1: - resolution: - { - integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, - } + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} property-information@7.2.0: - resolution: - { - integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==, - } + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} radix3@1.1.2: - resolution: - { - integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==, - } + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} react-dom@18.3.1: - resolution: - { - integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==, - } + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: react: ^18.3.1 react-is@16.13.1: - resolution: - { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, - } + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-refresh@0.17.0: - resolution: - { - integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} react@18.3.1: - resolution: - { - integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} readdirp@5.1.1: - resolution: - { - integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==, - } - engines: { node: ">= 20.19.0" } + resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} + engines: {node: '>= 20.19.0'} reflect.getprototypeof@1.0.10: - resolution: - { - integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} regex-recursion@6.0.2: - resolution: - { - integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==, - } + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} regex-utilities@2.3.0: - resolution: - { - integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==, - } + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} regex@6.1.0: - resolution: - { - integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==, - } + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} regexp.prototype.flags@1.5.4: - resolution: - { - integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} rehype-parse@9.0.1: - resolution: - { - integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==, - } + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} rehype-raw@7.0.0: - resolution: - { - integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, - } + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} rehype-stringify@10.0.1: - resolution: - { - integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==, - } + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} rehype@13.0.2: - resolution: - { - integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==, - } + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} remark-gfm@4.0.1: - resolution: - { - integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==, - } + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} remark-parse@11.0.0: - resolution: - { - integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, - } + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} remark-rehype@11.1.2: - resolution: - { - integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==, - } + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} remark-smartypants@3.0.3: - resolution: - { - integrity: sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==, - } - engines: { node: ">=16.0.0" } + resolution: {integrity: sha512-gCaK+ndZ0hYezlqFegHFCVh2CQemsi0Npdh1qVM9bxlUFknjkbP6VmojWhddOCrbK0PbbacmYLWfTULRiT1eWA==} + engines: {node: '>=16.0.0'} remark-stringify@11.0.0: - resolution: - { - integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, - } + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} request-light@0.5.8: - resolution: - { - integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==, - } + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} request-light@0.7.0: - resolution: - { - integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==, - } + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} require-from-string@2.0.2: - resolution: - { - integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve@2.0.0-next.7: - resolution: - { - integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} hasBin: true retext-latin@4.0.0: - resolution: - { - integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==, - } + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} retext-smartypants@6.2.0: - resolution: - { - integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==, - } + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} retext-stringify@4.0.0: - resolution: - { - integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==, - } + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} retext@9.0.0: - resolution: - { - integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==, - } + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} rollup@4.65.0: - resolution: - { - integrity: sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==, - } - engines: { node: ">=18.0.0", npm: ">=8.0.0" } + resolution: {integrity: sha512-QVCvQUTGDN6JRwi3jVGCY3H4aTHvi9rX0LyoBMkGUvdh/Z6O51CzcLz85BWZaTCDGhl6ySGqYZs+xI1jmPqGcw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true safe-array-concat@1.1.4: - resolution: - { - integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==, - } - engines: { node: ">=0.4" } + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} safe-push-apply@1.0.0: - resolution: - { - integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} safe-regex-test@1.1.0: - resolution: - { - integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} sax@1.6.1: - resolution: - { - integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==, - } - engines: { node: ">=11.0.0" } + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} scheduler@0.23.2: - resolution: - { - integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==, - } + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, - } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.8.5: - resolution: - { - integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} hasBin: true set-function-length@1.2.2: - resolution: - { - integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} set-function-name@2.0.2: - resolution: - { - integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} set-proto@1.0.0: - resolution: - { - integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} sharp@0.34.5: - resolution: - { - integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} shiki@3.23.0: - resolution: - { - integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==, - } + resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} side-channel-list@1.0.1: - resolution: - { - integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} side-channel-map@1.0.1: - resolution: - { - integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: - resolution: - { - integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} side-channel@1.1.1: - resolution: - { - integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} sisteransi@1.0.5: - resolution: - { - integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, - } + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} smol-toml@1.9.1: - resolution: - { - integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==, - } - engines: { node: ">= 18" } + resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} + engines: {node: '>= 18'} source-map-js@1.2.2: - resolution: - { - integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} space-separated-tokens@2.0.2: - resolution: - { - integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, - } + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} stop-iteration-iterator@1.1.0: - resolution: - { - integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} string-width@4.2.3: - resolution: - { - integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} string-width@7.2.0: - resolution: - { - integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} string-width@8.3.0: - resolution: - { - integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==, - } - engines: { node: ">=20" } + resolution: {integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==} + engines: {node: '>=20'} string.prototype.matchall@4.1.0: - resolution: - { - integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} + engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: - resolution: - { - integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, - } + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} string.prototype.trim@1.2.11: - resolution: - { - integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} string.prototype.trimend@1.0.10: - resolution: - { - integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: - resolution: - { - integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} stringify-entities@4.0.4: - resolution: - { - integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==, - } + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} strip-ansi@6.0.1: - resolution: - { - integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} strip-ansi@7.2.0: - resolution: - { - integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} strip-json-comments@3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} svgo@4.1.0: - resolution: - { - integrity: sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==} + engines: {node: '>=16'} hasBin: true tailwind-merge@2.6.1: - resolution: - { - integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==, - } + resolution: {integrity: sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==} tailwindcss@4.3.3: - resolution: - { - integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==, - } + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} tapable@2.3.3: - resolution: - { - integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} tiny-inflate@1.0.3: - resolution: - { - integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==, - } + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyexec@1.3.1: - resolution: - { - integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} tinyglobby@0.2.17: - resolution: - { - integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.6: + resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==} + engines: {node: '>=14.0.0'} trim-lines@3.0.1: - resolution: - { - integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, - } + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} trough@2.2.0: - resolution: - { - integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==, - } + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} ts-api-utils@2.5.0: - resolution: - { - integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, - } - engines: { node: ">=18.12" } + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: - typescript: ">=4.8.4" + typescript: '>=4.8.4' tsconfck@3.1.6: - resolution: - { - integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==, - } - engines: { node: ^18 || >=20 } + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} deprecated: unmaintained hasBin: true peerDependencies: @@ -4940,224 +3078,143 @@ packages: optional: true tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} type-fest@4.41.0: - resolution: - { - integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} typed-array-buffer@1.0.3: - resolution: - { - integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typed-array-byte-length@1.0.3: - resolution: - { - integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} typed-array-byte-offset@1.0.5: - resolution: - { - integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==} + engines: {node: '>= 0.4'} typed-array-length@1.0.8: - resolution: - { - integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} typesafe-path@0.2.2: - resolution: - { - integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==, - } + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} typescript-auto-import-cache@0.3.6: - resolution: - { - integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==, - } + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} typescript@5.9.3: - resolution: - { - integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, - } - engines: { node: ">=14.17" } + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} hasBin: true ufo@1.6.4: - resolution: - { - integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==, - } + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} ultrahtml@1.7.0: - resolution: - { - integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==, - } + resolution: {integrity: sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==} unbox-primitive@1.1.0: - resolution: - { - integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} uncrypto@0.1.3: - resolution: - { - integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==, - } + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} undici@8.12.3: - resolution: - { - integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==, - } - engines: { node: ">=22.19.0" } + resolution: {integrity: sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==} + engines: {node: '>=22.19.0'} unified@11.0.5: - resolution: - { - integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==, - } + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} unifont@0.7.5: - resolution: - { - integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==, - } + resolution: {integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==} unist-util-find-after@5.0.0: - resolution: - { - integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==, - } + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} unist-util-is@6.0.1: - resolution: - { - integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==, - } + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-modify-children@4.0.0: - resolution: - { - integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==, - } + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} unist-util-position@5.0.0: - resolution: - { - integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, - } + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} unist-util-remove-position@5.0.0: - resolution: - { - integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==, - } + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} unist-util-stringify-position@4.0.0: - resolution: - { - integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, - } + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} unist-util-visit-children@3.0.0: - resolution: - { - integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==, - } + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} unist-util-visit-parents@6.0.2: - resolution: - { - integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==, - } + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} unist-util-visit@5.1.0: - resolution: - { - integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, - } + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} unstorage@1.17.6: - resolution: - { - integrity: sha512-gjKtcmn/KwZuJZO9o2sc0esAQnhmTw0LviDHwevTXbJMYs0dRTp8ZEEWXHU0vTpzbN3cA0yxlTeyXssMp3zDqw==, - } + resolution: {integrity: sha512-gjKtcmn/KwZuJZO9o2sc0esAQnhmTw0LviDHwevTXbJMYs0dRTp8ZEEWXHU0vTpzbN3cA0yxlTeyXssMp3zDqw==} peerDependencies: - "@azure/app-configuration": ^1.8.0 - "@azure/cosmos": ^4.2.0 - "@azure/data-tables": ^13.3.0 - "@azure/identity": ^4.6.0 - "@azure/keyvault-secrets": ^4.9.0 - "@azure/storage-blob": ^12.26.0 - "@capacitor/preferences": ^6 || ^7 || ^8 - "@deno/kv": ">=0.9.0" - "@netlify/blobs": ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 - "@planetscale/database": ^1.19.0 - "@upstash/redis": ^1.34.3 - "@vercel/blob": ">=0.27.1" - "@vercel/functions": ^2.2.12 || ^3.0.0 - "@vercel/kv": ^1 || ^2 || ^3 + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1 || ^2 || ^3 aws4fetch: ^1.0.20 - db0: ">=0.2.1" + db0: '>=0.2.1' idb-keyval: ^6.2.1 ioredis: ^5.4.2 uploadthing: ^7.4.4 peerDependenciesMeta: - "@azure/app-configuration": + '@azure/app-configuration': optional: true - "@azure/cosmos": + '@azure/cosmos': optional: true - "@azure/data-tables": + '@azure/data-tables': optional: true - "@azure/identity": + '@azure/identity': optional: true - "@azure/keyvault-secrets": + '@azure/keyvault-secrets': optional: true - "@azure/storage-blob": + '@azure/storage-blob': optional: true - "@capacitor/preferences": + '@capacitor/preferences': optional: true - "@deno/kv": + '@deno/kv': optional: true - "@netlify/blobs": + '@netlify/blobs': optional: true - "@planetscale/database": + '@planetscale/database': optional: true - "@upstash/redis": + '@upstash/redis': optional: true - "@vercel/blob": + '@vercel/blob': optional: true - "@vercel/functions": + '@vercel/functions': optional: true - "@vercel/kv": + '@vercel/kv': optional: true aws4fetch: optional: true @@ -5171,59 +3228,46 @@ packages: optional: true update-browserslist-db@1.3.4: - resolution: - { - integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==, - } + resolution: {integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==} hasBin: true peerDependencies: - browserslist: ">= 4.21.0" + browserslist: '>= 4.21.0' uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} vfile-location@5.0.3: - resolution: - { - integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==, - } + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} vfile-message@4.0.3: - resolution: - { - integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==, - } + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@6.0.3: - resolution: - { - integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==, - } + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true vite@6.4.5: - resolution: - { - integrity: sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==, - } - engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + resolution: {integrity: sha512-4enwjFfi4IwtEh0w+jPzpLVOFQiPHmmzPxcHXQOyEur47o6u0jE573DmGpjA+7irXoMLYRZD5ZaFUEneS3abPg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: ">=1.21.0" - less: "*" + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: - "@types/node": + '@types/node': optional: true jiti: optional: true @@ -5247,406 +3291,296 @@ packages: optional: true vitefu@1.1.3: - resolution: - { - integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==, - } + resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: vite: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + volar-service-css@0.0.71: - resolution: - { - integrity: sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==, - } + resolution: {integrity: sha512-wRRFt9BpjMKCazcgOh67MSjUjiWUCAh99DyYSDIOTuxaRjEtDC7PpB0k1Y1wbJIW/pVtMUSVbpPo3UGSm0Byxw==} peerDependencies: - "@volar/language-service": ~2.4.0 + '@volar/language-service': ~2.4.0 peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true volar-service-emmet@0.0.71: - resolution: - { - integrity: sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==, - } + resolution: {integrity: sha512-zqjzt6bN95e3CUstBm0PBFAJnrfz0ZAARka87fart46/gNCLLuP3Vujy8V/J8HEziTFLnfkgIASLFYPUhonJcA==} peerDependencies: - "@volar/language-service": ~2.4.0 + '@volar/language-service': ~2.4.0 peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true volar-service-html@0.0.71: - resolution: - { - integrity: sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==, - } + resolution: {integrity: sha512-e8tHPhgQ7ooLfudAEIku+kgd9pWkq3SSz8RbnQDI1+Eb8wbenkLGHqoirLqz5ORLV6wIMr2Iv08RWBG5eOcgpw==} peerDependencies: - "@volar/language-service": ~2.4.0 + '@volar/language-service': ~2.4.0 peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true volar-service-prettier@0.0.71: - resolution: - { - integrity: sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==, - } + resolution: {integrity: sha512-Rz7JVH3qD108UCdmIEiZvOBNljMt2nLFdbN8AXcDfn7xD9F5I2aCIsDVqBbXw21PsnxG0b7MfwtNF+zPS/NKUg==} peerDependencies: - "@volar/language-service": ~2.4.0 + '@volar/language-service': ~2.4.0 prettier: ^2.2 || ^3.0 peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true prettier: optional: true volar-service-typescript-twoslash-queries@0.0.71: - resolution: - { - integrity: sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==, - } + resolution: {integrity: sha512-9K2k72s4n7rV9s4bX0MyjbX9iBribvKZbBJKuEmTCZfeWJXs6Yh7bGpY4eoc7UufAjvpheBqwyZCOIPBvxCv0A==} peerDependencies: - "@volar/language-service": ~2.4.0 - typescript: "*" + '@volar/language-service': ~2.4.0 + typescript: '*' peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true typescript: optional: true volar-service-typescript@0.0.71: - resolution: - { - integrity: sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==, - } + resolution: {integrity: sha512-yTtM/BVT6hoyEYnDtaCyAtNhdNeS/mhTTABlBOdw3NNiRBUin3IznFJpgfjer4c6RYopiPjjQjc9VFhxVl1mLw==} peerDependencies: - "@volar/language-service": ~2.4.0 - typescript: "*" + '@volar/language-service': ~2.4.0 + typescript: '*' peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true typescript: optional: true volar-service-yaml@0.0.71: - resolution: - { - integrity: sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==, - } + resolution: {integrity: sha512-qYGWGuVpUTnZGu5P/CR4KLK4aIR8RrcVnmfZ2eRcj9q/I8VZCoC5yy9FtEvfNvnDp4MU17yhdJcvpQPIqhJS2Q==} peerDependencies: - "@volar/language-service": ~2.4.0 + '@volar/language-service': ~2.4.0 peerDependenciesMeta: - "@volar/language-service": + '@volar/language-service': optional: true vscode-css-languageservice@6.3.10: - resolution: - { - integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==, - } + resolution: {integrity: sha512-eq5N9Er3fC4vA9zd9EFhyBG90wtCCuXgRSpAndaOgXMh1Wgep5lBgRIeDgjZBW9pa+332yC9+49cZMW8jcL3MA==} vscode-html-languageservice@5.6.2: - resolution: - { - integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==, - } + resolution: {integrity: sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg==} vscode-json-languageservice@4.1.8: - resolution: - { - integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==, - } - engines: { npm: ">=7.0.0" } + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} vscode-jsonrpc@10.0.0: - resolution: - { - integrity: sha512-USSfxg0vDIpTQqlQBGxF3mBARyoS8cU7LHLpbYNh6dOJt32dlskxPWB0Irrh5TllTmyf7oNLAvqDqzklbwwnpA==, - } - engines: { node: ">=14.17.0" } + resolution: {integrity: sha512-USSfxg0vDIpTQqlQBGxF3mBARyoS8cU7LHLpbYNh6dOJt32dlskxPWB0Irrh5TllTmyf7oNLAvqDqzklbwwnpA==} + engines: {node: '>=14.17.0'} vscode-jsonrpc@8.2.0: - resolution: - { - integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==, - } - engines: { node: ">=14.0.0" } + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} vscode-languageserver-protocol@3.17.5: - resolution: - { - integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==, - } + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} vscode-languageserver-protocol@3.18.5: - resolution: - { - integrity: sha512-FW4fBMzZ99N8xeob4py0l050wgCoRgTpEq5Xcsb9lM1bc29UKGR/wSV+4knaqFxssFHajy7OzNiKP/m3U1ZEuA==, - } + resolution: {integrity: sha512-FW4fBMzZ99N8xeob4py0l050wgCoRgTpEq5Xcsb9lM1bc29UKGR/wSV+4knaqFxssFHajy7OzNiKP/m3U1ZEuA==} vscode-languageserver-textdocument@1.0.16: - resolution: - { - integrity: sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==, - } + resolution: {integrity: sha512-qeNEuOi9a3qkqRf7Gfez2WL2VwlBTLCNekHBGO9kxPVUp9XuY15yfTJ9jwvOwKI0I2MhUUfZq95NSp+LK1CLQg==} vscode-languageserver-types@3.17.5: - resolution: - { - integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==, - } + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} vscode-languageserver-types@3.18.5: - resolution: - { - integrity: sha512-kPd6+JQNz3HMJvjKlqUxERSlZHAntl/aGpsk7ImwIh0ewDKDcjgAEyVSYciPiOJD3ftbK9IrfiqUeTZLSbCizw==, - } + resolution: {integrity: sha512-kPd6+JQNz3HMJvjKlqUxERSlZHAntl/aGpsk7ImwIh0ewDKDcjgAEyVSYciPiOJD3ftbK9IrfiqUeTZLSbCizw==} vscode-languageserver@9.0.1: - resolution: - { - integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==, - } + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} hasBin: true vscode-nls@5.2.0: - resolution: - { - integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==, - } + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} vscode-uri@3.2.0: - resolution: - { - integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==, - } + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} web-namespaces@2.0.1: - resolution: - { - integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==, - } + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} which-boxed-primitive@1.1.1: - resolution: - { - integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} which-builtin-type@1.2.1: - resolution: - { - integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} which-collection@1.0.2: - resolution: - { - integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} which-pm-runs@1.1.0: - resolution: - { - integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} which-typed-array@1.1.24: - resolution: - { - integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==} + engines: {node: '>= 0.4'} which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} hasBin: true widest-line@5.0.0: - resolution: - { - integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} wrap-ansi@9.0.2: - resolution: - { - integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} xxhash-wasm@1.1.0: - resolution: - { - integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==, - } + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} y18n@5.0.8: - resolution: - { - integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} yallist@3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, - } + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yaml-language-server@1.23.0: - resolution: - { - integrity: sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==, - } + resolution: {integrity: sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==} hasBin: true yaml@2.8.3: - resolution: - { - integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==, - } - engines: { node: ">= 14.6" } + resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + engines: {node: '>= 14.6'} hasBin: true yaml@2.9.1: - resolution: - { - integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==, - } - engines: { node: ">= 14.6" } + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} hasBin: true yargs-parser@21.1.1: - resolution: - { - integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} yargs-parser@22.0.0: - resolution: - { - integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==, - } - engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yargs@18.2.0: - resolution: - { - integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==, - } - engines: { node: ^20.19.0 || ^22.12.0 || >=23 } + resolution: {integrity: sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} yocto-queue@1.2.2: - resolution: - { - integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==, - } - engines: { node: ">=12.20" } + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} yocto-spinner@0.2.3: - resolution: - { - integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==, - } - engines: { node: ">=18.19" } + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} yoctocolors@2.2.0: - resolution: - { - integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} zod-to-json-schema@3.25.2: - resolution: - { - integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==, - } + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: zod: ^3.25.28 || ^4 zod-to-ts@1.2.0: - resolution: - { - integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==, - } + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} peerDependencies: typescript: ^4.9.4 || ^5.0.2 zod: ^3 zod@3.25.76: - resolution: - { - integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, - } + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} zwitch@2.0.4: - resolution: - { - integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, - } + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - "@astrojs/astro2tsx@0.1.3(@emnapi/runtime@1.11.3)": + + '@astrojs/astro2tsx@0.1.3(@emnapi/runtime@1.11.3)': dependencies: - "@emnapi/core": 1.11.3 - "@napi-rs/wasm-runtime": 1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) + '@emnapi/core': 1.11.3 + '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3) transitivePeerDependencies: - - "@emnapi/runtime" + - '@emnapi/runtime' - "@astrojs/check@0.9.12(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3)": + '@astrojs/check@0.9.12(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3)': dependencies: - "@astrojs/language-server": 2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3) + '@astrojs/language-server': 2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3) chokidar: 5.0.0 kleur: 4.1.5 typescript: 5.9.3 yargs: 18.2.0 transitivePeerDependencies: - - "@emnapi/runtime" + - '@emnapi/runtime' - prettier - prettier-plugin-astro - "@astrojs/compiler@2.13.1": {} + '@astrojs/compiler@2.13.1': {} - "@astrojs/internal-helpers@0.7.6": {} + '@astrojs/internal-helpers@0.7.6': {} - "@astrojs/language-server@2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3)": + '@astrojs/language-server@2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@5.9.3)': dependencies: - "@astrojs/astro2tsx": 0.1.3(@emnapi/runtime@1.11.3) - "@astrojs/yaml2ts": 0.2.4 - "@volar/kit": 2.4.30(typescript@5.9.3) - "@volar/language-core": 2.4.30 - "@volar/language-server": 2.4.30(typescript@5.9.3) - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@astrojs/astro2tsx': 0.1.3(@emnapi/runtime@1.11.3) + '@astrojs/yaml2ts': 0.2.4 + '@volar/kit': 2.4.30(typescript@5.9.3) + '@volar/language-core': 2.4.30 + '@volar/language-server': 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) muggle-string: 0.4.1 tinyglobby: 0.2.17 volar-service-css: 0.0.71(@volar/language-service@2.4.30(typescript@5.9.3)) @@ -5661,13 +3595,13 @@ snapshots: optionalDependencies: prettier: 3.9.10 transitivePeerDependencies: - - "@emnapi/runtime" + - '@emnapi/runtime' - typescript - "@astrojs/markdown-remark@6.3.11(supports-color@7.2.0)": + '@astrojs/markdown-remark@6.3.11(supports-color@7.2.0)': dependencies: - "@astrojs/internal-helpers": 0.7.6 - "@astrojs/prism": 3.3.0 + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/prism': 3.3.0 github-slugger: 2.0.0 hast-util-from-html: 2.0.3 hast-util-to-text: 4.0.2 @@ -5690,21 +3624,21 @@ snapshots: transitivePeerDependencies: - supports-color - "@astrojs/prism@3.3.0": + '@astrojs/prism@3.3.0': dependencies: prismjs: 1.30.0 - "@astrojs/react@4.4.2(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(jiti@2.7.0)(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)(yaml@2.9.1)": + '@astrojs/react@4.4.2(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(jiti@2.7.0)(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)(yaml@2.9.1)': dependencies: - "@types/react": 18.3.31 - "@types/react-dom": 18.3.7(@types/react@18.3.31) - "@vitejs/plugin-react": 4.7.0(supports-color@7.2.0)(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1)) + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + '@vitejs/plugin-react': 4.7.0(supports-color@7.2.0)(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) ultrahtml: 1.7.0 vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) transitivePeerDependencies: - - "@types/node" + - '@types/node' - jiti - less - lightningcss @@ -5717,7 +3651,7 @@ snapshots: - tsx - yaml - "@astrojs/telemetry@3.3.0(supports-color@7.2.0)": + '@astrojs/telemetry@3.3.0(supports-color@7.2.0)': dependencies: ci-info: 4.4.0 debug: 4.4.3(supports-color@7.2.0) @@ -5729,30 +3663,30 @@ snapshots: transitivePeerDependencies: - supports-color - "@astrojs/yaml2ts@0.2.4": + '@astrojs/yaml2ts@0.2.4': dependencies: yaml: 2.9.1 - "@babel/code-frame@7.29.7": + '@babel/code-frame@7.29.7': dependencies: - "@babel/helper-validator-identifier": 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - "@babel/compat-data@7.29.7": {} + '@babel/compat-data@7.29.7': {} - "@babel/core@7.29.7(supports-color@7.2.0)": + '@babel/core@7.29.7(supports-color@7.2.0)': dependencies: - "@babel/code-frame": 7.29.7 - "@babel/generator": 7.29.8 - "@babel/helper-compilation-targets": 7.29.7 - "@babel/helper-module-transforms": 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) - "@babel/helpers": 7.29.10 - "@babel/parser": 7.29.9 - "@babel/template": 7.29.7 - "@babel/traverse": 7.29.10(supports-color@7.2.0) - "@babel/types": 7.29.8 - "@jridgewell/remapping": 2.3.5 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/helpers': 7.29.10 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.10(supports-color@7.2.0) + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@7.2.0) gensync: 1.0.0-beta.2 @@ -5761,310 +3695,310 @@ snapshots: transitivePeerDependencies: - supports-color - "@babel/generator@7.29.8": + '@babel/generator@7.29.8': dependencies: - "@babel/parser": 7.29.9 - "@babel/types": 7.29.8 - "@jridgewell/gen-mapping": 0.3.13 - "@jridgewell/trace-mapping": 0.3.31 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - "@babel/helper-compilation-targets@7.29.7": + '@babel/helper-compilation-targets@7.29.7': dependencies: - "@babel/compat-data": 7.29.7 - "@babel/helper-validator-option": 7.29.7 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.29.4 lru-cache: 5.1.1 semver: 6.3.1 - "@babel/helper-globals@7.29.7": {} + '@babel/helper-globals@7.29.7': {} - "@babel/helper-module-imports@7.29.7(supports-color@7.2.0)": + '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': dependencies: - "@babel/traverse": 7.29.10(supports-color@7.2.0) - "@babel/types": 7.29.8 + '@babel/traverse': 7.29.10(supports-color@7.2.0) + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - "@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)": + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - "@babel/core": 7.29.7(supports-color@7.2.0) - "@babel/helper-module-imports": 7.29.7(supports-color@7.2.0) - "@babel/helper-validator-identifier": 7.29.7 - "@babel/traverse": 7.29.10(supports-color@7.2.0) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.10(supports-color@7.2.0) transitivePeerDependencies: - supports-color - "@babel/helper-plugin-utils@7.29.7": {} + '@babel/helper-plugin-utils@7.29.7': {} - "@babel/helper-string-parser@7.29.7": {} + '@babel/helper-string-parser@7.29.7': {} - "@babel/helper-validator-identifier@7.29.7": {} + '@babel/helper-validator-identifier@7.29.7': {} - "@babel/helper-validator-option@7.29.7": {} + '@babel/helper-validator-option@7.29.7': {} - "@babel/helpers@7.29.10": + '@babel/helpers@7.29.10': dependencies: - "@babel/template": 7.29.7 - "@babel/types": 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 - "@babel/parser@7.29.9": + '@babel/parser@7.29.9': dependencies: - "@babel/types": 7.29.8 + '@babel/types': 7.29.8 - "@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))": + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - "@babel/core": 7.29.7(supports-color@7.2.0) - "@babel/helper-plugin-utils": 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-plugin-utils': 7.29.7 - "@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))": + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - "@babel/core": 7.29.7(supports-color@7.2.0) - "@babel/helper-plugin-utils": 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-plugin-utils': 7.29.7 - "@babel/template@7.29.7": + '@babel/template@7.29.7': dependencies: - "@babel/code-frame": 7.29.7 - "@babel/parser": 7.29.9 - "@babel/types": 7.29.8 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - "@babel/traverse@7.29.10(supports-color@7.2.0)": + '@babel/traverse@7.29.10(supports-color@7.2.0)': dependencies: - "@babel/code-frame": 7.29.7 - "@babel/generator": 7.29.8 - "@babel/helper-globals": 7.29.7 - "@babel/parser": 7.29.9 - "@babel/template": 7.29.7 - "@babel/types": 7.29.8 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - "@babel/types@7.29.8": + '@babel/types@7.29.8': dependencies: - "@babel/helper-string-parser": 7.29.7 - "@babel/helper-validator-identifier": 7.29.7 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 - "@capsizecss/unpack@4.0.1": + '@capsizecss/unpack@4.0.1': dependencies: fontkitten: 1.0.3 - "@emmetio/abbreviation@2.3.3": + '@emmetio/abbreviation@2.3.3': dependencies: - "@emmetio/scanner": 1.0.4 + '@emmetio/scanner': 1.0.4 - "@emmetio/css-abbreviation@2.1.8": + '@emmetio/css-abbreviation@2.1.8': dependencies: - "@emmetio/scanner": 1.0.4 + '@emmetio/scanner': 1.0.4 - "@emmetio/css-parser@0.4.1": + '@emmetio/css-parser@0.4.1': dependencies: - "@emmetio/stream-reader": 2.2.0 - "@emmetio/stream-reader-utils": 0.1.0 + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 - "@emmetio/html-matcher@1.3.0": + '@emmetio/html-matcher@1.3.0': dependencies: - "@emmetio/scanner": 1.0.4 + '@emmetio/scanner': 1.0.4 - "@emmetio/scanner@1.0.4": {} + '@emmetio/scanner@1.0.4': {} - "@emmetio/stream-reader-utils@0.1.0": {} + '@emmetio/stream-reader-utils@0.1.0': {} - "@emmetio/stream-reader@2.2.0": {} + '@emmetio/stream-reader@2.2.0': {} - "@emnapi/core@1.11.3": + '@emnapi/core@1.11.3': dependencies: - "@emnapi/wasi-threads": 1.2.3 + '@emnapi/wasi-threads': 1.2.3 tslib: 2.8.1 - "@emnapi/runtime@1.11.3": + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 - "@emnapi/wasi-threads@1.2.3": + '@emnapi/wasi-threads@1.2.3': dependencies: tslib: 2.8.1 - "@esbuild/aix-ppc64@0.25.12": + '@esbuild/aix-ppc64@0.25.12': optional: true - "@esbuild/aix-ppc64@0.27.7": + '@esbuild/aix-ppc64@0.27.7': optional: true - "@esbuild/android-arm64@0.25.12": + '@esbuild/android-arm64@0.25.12': optional: true - "@esbuild/android-arm64@0.27.7": + '@esbuild/android-arm64@0.27.7': optional: true - "@esbuild/android-arm@0.25.12": + '@esbuild/android-arm@0.25.12': optional: true - "@esbuild/android-arm@0.27.7": + '@esbuild/android-arm@0.27.7': optional: true - "@esbuild/android-x64@0.25.12": + '@esbuild/android-x64@0.25.12': optional: true - "@esbuild/android-x64@0.27.7": + '@esbuild/android-x64@0.27.7': optional: true - "@esbuild/darwin-arm64@0.25.12": + '@esbuild/darwin-arm64@0.25.12': optional: true - "@esbuild/darwin-arm64@0.27.7": + '@esbuild/darwin-arm64@0.27.7': optional: true - "@esbuild/darwin-x64@0.25.12": + '@esbuild/darwin-x64@0.25.12': optional: true - "@esbuild/darwin-x64@0.27.7": + '@esbuild/darwin-x64@0.27.7': optional: true - "@esbuild/freebsd-arm64@0.25.12": + '@esbuild/freebsd-arm64@0.25.12': optional: true - "@esbuild/freebsd-arm64@0.27.7": + '@esbuild/freebsd-arm64@0.27.7': optional: true - "@esbuild/freebsd-x64@0.25.12": + '@esbuild/freebsd-x64@0.25.12': optional: true - "@esbuild/freebsd-x64@0.27.7": + '@esbuild/freebsd-x64@0.27.7': optional: true - "@esbuild/linux-arm64@0.25.12": + '@esbuild/linux-arm64@0.25.12': optional: true - "@esbuild/linux-arm64@0.27.7": + '@esbuild/linux-arm64@0.27.7': optional: true - "@esbuild/linux-arm@0.25.12": + '@esbuild/linux-arm@0.25.12': optional: true - "@esbuild/linux-arm@0.27.7": + '@esbuild/linux-arm@0.27.7': optional: true - "@esbuild/linux-ia32@0.25.12": + '@esbuild/linux-ia32@0.25.12': optional: true - "@esbuild/linux-ia32@0.27.7": + '@esbuild/linux-ia32@0.27.7': optional: true - "@esbuild/linux-loong64@0.25.12": + '@esbuild/linux-loong64@0.25.12': optional: true - "@esbuild/linux-loong64@0.27.7": + '@esbuild/linux-loong64@0.27.7': optional: true - "@esbuild/linux-mips64el@0.25.12": + '@esbuild/linux-mips64el@0.25.12': optional: true - "@esbuild/linux-mips64el@0.27.7": + '@esbuild/linux-mips64el@0.27.7': optional: true - "@esbuild/linux-ppc64@0.25.12": + '@esbuild/linux-ppc64@0.25.12': optional: true - "@esbuild/linux-ppc64@0.27.7": + '@esbuild/linux-ppc64@0.27.7': optional: true - "@esbuild/linux-riscv64@0.25.12": + '@esbuild/linux-riscv64@0.25.12': optional: true - "@esbuild/linux-riscv64@0.27.7": + '@esbuild/linux-riscv64@0.27.7': optional: true - "@esbuild/linux-s390x@0.25.12": + '@esbuild/linux-s390x@0.25.12': optional: true - "@esbuild/linux-s390x@0.27.7": + '@esbuild/linux-s390x@0.27.7': optional: true - "@esbuild/linux-x64@0.25.12": + '@esbuild/linux-x64@0.25.12': optional: true - "@esbuild/linux-x64@0.27.7": + '@esbuild/linux-x64@0.27.7': optional: true - "@esbuild/netbsd-arm64@0.25.12": + '@esbuild/netbsd-arm64@0.25.12': optional: true - "@esbuild/netbsd-arm64@0.27.7": + '@esbuild/netbsd-arm64@0.27.7': optional: true - "@esbuild/netbsd-x64@0.25.12": + '@esbuild/netbsd-x64@0.25.12': optional: true - "@esbuild/netbsd-x64@0.27.7": + '@esbuild/netbsd-x64@0.27.7': optional: true - "@esbuild/openbsd-arm64@0.25.12": + '@esbuild/openbsd-arm64@0.25.12': optional: true - "@esbuild/openbsd-arm64@0.27.7": + '@esbuild/openbsd-arm64@0.27.7': optional: true - "@esbuild/openbsd-x64@0.25.12": + '@esbuild/openbsd-x64@0.25.12': optional: true - "@esbuild/openbsd-x64@0.27.7": + '@esbuild/openbsd-x64@0.27.7': optional: true - "@esbuild/openharmony-arm64@0.25.12": + '@esbuild/openharmony-arm64@0.25.12': optional: true - "@esbuild/openharmony-arm64@0.27.7": + '@esbuild/openharmony-arm64@0.27.7': optional: true - "@esbuild/sunos-x64@0.25.12": + '@esbuild/sunos-x64@0.25.12': optional: true - "@esbuild/sunos-x64@0.27.7": + '@esbuild/sunos-x64@0.27.7': optional: true - "@esbuild/win32-arm64@0.25.12": + '@esbuild/win32-arm64@0.25.12': optional: true - "@esbuild/win32-arm64@0.27.7": + '@esbuild/win32-arm64@0.27.7': optional: true - "@esbuild/win32-ia32@0.25.12": + '@esbuild/win32-ia32@0.25.12': optional: true - "@esbuild/win32-ia32@0.27.7": + '@esbuild/win32-ia32@0.27.7': optional: true - "@esbuild/win32-x64@0.25.12": + '@esbuild/win32-x64@0.25.12': optional: true - "@esbuild/win32-x64@0.27.7": + '@esbuild/win32-x64@0.27.7': optional: true - "@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))": + '@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))': dependencies: eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 - "@eslint-community/regexpp@4.12.2": {} + '@eslint-community/regexpp@4.12.2': {} - "@eslint/config-array@0.21.2(supports-color@7.2.0)": + '@eslint/config-array@0.21.2(supports-color@7.2.0)': dependencies: - "@eslint/object-schema": 2.1.7 + '@eslint/object-schema': 2.1.7 debug: 4.4.3(supports-color@7.2.0) minimatch: 3.1.5 transitivePeerDependencies: - supports-color - "@eslint/config-helpers@0.4.2": + '@eslint/config-helpers@0.4.2': dependencies: - "@eslint/core": 0.17.0 + '@eslint/core': 0.17.0 - "@eslint/core@0.17.0": + '@eslint/core@0.17.0': dependencies: - "@types/json-schema": 7.0.15 + '@types/json-schema': 7.0.15 - "@eslint/eslintrc@3.3.7(supports-color@7.2.0)": + '@eslint/eslintrc@3.3.7(supports-color@7.2.0)': dependencies: ajv: 6.15.0 debug: 4.4.3(supports-color@7.2.0) @@ -6078,281 +4012,285 @@ snapshots: transitivePeerDependencies: - supports-color - "@eslint/js@9.39.5": {} + '@eslint/js@9.39.5': {} - "@eslint/object-schema@2.1.7": {} + '@eslint/object-schema@2.1.7': {} - "@eslint/plugin-kit@0.4.1": + '@eslint/plugin-kit@0.4.1': dependencies: - "@eslint/core": 0.17.0 + '@eslint/core': 0.17.0 levn: 0.4.1 - "@fontsource-variable/inter@5.3.0": {} + '@fontsource-variable/inter@5.3.0': {} - "@humanfs/core@0.19.2": + '@humanfs/core@0.19.2': dependencies: - "@humanfs/types": 0.15.0 + '@humanfs/types': 0.15.0 - "@humanfs/node@0.16.8": + '@humanfs/node@0.16.8': dependencies: - "@humanfs/core": 0.19.2 - "@humanfs/types": 0.15.0 - "@humanwhocodes/retry": 0.4.3 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 - "@humanfs/types@0.15.0": {} + '@humanfs/types@0.15.0': {} - "@humanwhocodes/module-importer@1.0.1": {} + '@humanwhocodes/module-importer@1.0.1': {} - "@humanwhocodes/retry@0.4.3": {} + '@humanwhocodes/retry@0.4.3': {} - "@img/colour@1.1.0": + '@img/colour@1.1.0': optional: true - "@img/sharp-darwin-arm64@0.34.5": + '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: - "@img/sharp-libvips-darwin-arm64": 1.2.4 + '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true - "@img/sharp-darwin-x64@0.34.5": + '@img/sharp-darwin-x64@0.34.5': optionalDependencies: - "@img/sharp-libvips-darwin-x64": 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true - "@img/sharp-libvips-darwin-arm64@1.2.4": + '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true - "@img/sharp-libvips-darwin-x64@1.2.4": + '@img/sharp-libvips-darwin-x64@1.2.4': optional: true - "@img/sharp-libvips-linux-arm64@1.2.4": + '@img/sharp-libvips-linux-arm64@1.2.4': optional: true - "@img/sharp-libvips-linux-arm@1.2.4": + '@img/sharp-libvips-linux-arm@1.2.4': optional: true - "@img/sharp-libvips-linux-ppc64@1.2.4": + '@img/sharp-libvips-linux-ppc64@1.2.4': optional: true - "@img/sharp-libvips-linux-riscv64@1.2.4": + '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true - "@img/sharp-libvips-linux-s390x@1.2.4": + '@img/sharp-libvips-linux-s390x@1.2.4': optional: true - "@img/sharp-libvips-linux-x64@1.2.4": + '@img/sharp-libvips-linux-x64@1.2.4': optional: true - "@img/sharp-libvips-linuxmusl-arm64@1.2.4": + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true - "@img/sharp-libvips-linuxmusl-x64@1.2.4": + '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true - "@img/sharp-linux-arm64@0.34.5": + '@img/sharp-linux-arm64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-arm64": 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true - "@img/sharp-linux-arm@0.34.5": + '@img/sharp-linux-arm@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-arm": 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 optional: true - "@img/sharp-linux-ppc64@0.34.5": + '@img/sharp-linux-ppc64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-ppc64": 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 optional: true - "@img/sharp-linux-riscv64@0.34.5": + '@img/sharp-linux-riscv64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-riscv64": 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true - "@img/sharp-linux-s390x@0.34.5": + '@img/sharp-linux-s390x@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-s390x": 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true - "@img/sharp-linux-x64@0.34.5": + '@img/sharp-linux-x64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linux-x64": 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 optional: true - "@img/sharp-linuxmusl-arm64@0.34.5": + '@img/sharp-linuxmusl-arm64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true - "@img/sharp-linuxmusl-x64@0.34.5": + '@img/sharp-linuxmusl-x64@0.34.5': optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64": 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true - "@img/sharp-wasm32@0.34.5": + '@img/sharp-wasm32@0.34.5': dependencies: - "@emnapi/runtime": 1.11.3 + '@emnapi/runtime': 1.11.3 optional: true - "@img/sharp-win32-arm64@0.34.5": + '@img/sharp-win32-arm64@0.34.5': optional: true - "@img/sharp-win32-ia32@0.34.5": + '@img/sharp-win32-ia32@0.34.5': optional: true - "@img/sharp-win32-x64@0.34.5": + '@img/sharp-win32-x64@0.34.5': optional: true - "@jridgewell/gen-mapping@0.3.13": + '@jridgewell/gen-mapping@0.3.13': dependencies: - "@jridgewell/sourcemap-codec": 1.6.0 - "@jridgewell/trace-mapping": 0.3.31 + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 - "@jridgewell/remapping@2.3.5": + '@jridgewell/remapping@2.3.5': dependencies: - "@jridgewell/gen-mapping": 0.3.13 - "@jridgewell/trace-mapping": 0.3.31 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - "@jridgewell/resolve-uri@3.1.2": {} + '@jridgewell/resolve-uri@3.1.2': {} - "@jridgewell/sourcemap-codec@1.6.0": {} + '@jridgewell/sourcemap-codec@1.6.0': {} - "@jridgewell/trace-mapping@0.3.31": + '@jridgewell/trace-mapping@0.3.31': dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.6.0 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 - "@napi-rs/lzma-linux-x64-gnu@1.5.1": + '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true - "@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)": + '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.11.3)(@emnapi/runtime@1.11.3)': dependencies: - "@emnapi/core": 1.11.3 - "@emnapi/runtime": 1.11.3 - "@tybys/wasm-util": 0.10.4 + '@emnapi/core': 1.11.3 + '@emnapi/runtime': 1.11.3 + '@tybys/wasm-util': 0.10.4 - "@oslojs/encoding@1.1.0": {} + '@oslojs/encoding@1.1.0': {} + + '@playwright/test@1.56.1': + dependencies: + playwright: 1.56.1 - "@rolldown/pluginutils@1.0.0-beta.27": {} + '@rolldown/pluginutils@1.0.0-beta.27': {} - "@rollup/pluginutils@5.5.1(rollup@4.65.0)": + '@rollup/pluginutils@5.5.1(rollup@4.65.0)': dependencies: - "@types/estree": 1.0.9 + '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.7 optionalDependencies: rollup: 4.65.0 - "@rollup/rollup-android-arm-eabi@4.65.0": + '@rollup/rollup-android-arm-eabi@4.65.0': optional: true - "@rollup/rollup-android-arm64@4.65.0": + '@rollup/rollup-android-arm64@4.65.0': optional: true - "@rollup/rollup-darwin-arm64@4.65.0": + '@rollup/rollup-darwin-arm64@4.65.0': optional: true - "@rollup/rollup-darwin-x64@4.65.0": + '@rollup/rollup-darwin-x64@4.65.0': optional: true - "@rollup/rollup-freebsd-arm64@4.65.0": + '@rollup/rollup-freebsd-arm64@4.65.0': optional: true - "@rollup/rollup-freebsd-x64@4.65.0": + '@rollup/rollup-freebsd-x64@4.65.0': optional: true - "@rollup/rollup-linux-arm-gnueabihf@4.65.0": + '@rollup/rollup-linux-arm-gnueabihf@4.65.0': optional: true - "@rollup/rollup-linux-arm-musleabihf@4.65.0": + '@rollup/rollup-linux-arm-musleabihf@4.65.0': optional: true - "@rollup/rollup-linux-arm64-gnu@4.65.0": + '@rollup/rollup-linux-arm64-gnu@4.65.0': optional: true - "@rollup/rollup-linux-arm64-musl@4.65.0": + '@rollup/rollup-linux-arm64-musl@4.65.0': optional: true - "@rollup/rollup-linux-loong64-gnu@4.65.0": + '@rollup/rollup-linux-loong64-gnu@4.65.0': optional: true - "@rollup/rollup-linux-loong64-musl@4.65.0": + '@rollup/rollup-linux-loong64-musl@4.65.0': optional: true - "@rollup/rollup-linux-ppc64-gnu@4.65.0": + '@rollup/rollup-linux-ppc64-gnu@4.65.0': optional: true - "@rollup/rollup-linux-ppc64-musl@4.65.0": + '@rollup/rollup-linux-ppc64-musl@4.65.0': optional: true - "@rollup/rollup-linux-riscv64-gnu@4.65.0": + '@rollup/rollup-linux-riscv64-gnu@4.65.0': optional: true - "@rollup/rollup-linux-riscv64-musl@4.65.0": + '@rollup/rollup-linux-riscv64-musl@4.65.0': optional: true - "@rollup/rollup-linux-s390x-gnu@4.65.0": + '@rollup/rollup-linux-s390x-gnu@4.65.0': optional: true - "@rollup/rollup-linux-x64-gnu@4.65.0": + '@rollup/rollup-linux-x64-gnu@4.65.0': optional: true - "@rollup/rollup-linux-x64-musl@4.65.0": + '@rollup/rollup-linux-x64-musl@4.65.0': optional: true - "@rollup/rollup-openbsd-x64@4.65.0": + '@rollup/rollup-openbsd-x64@4.65.0': optional: true - "@rollup/rollup-openharmony-arm64@4.65.0": + '@rollup/rollup-openharmony-arm64@4.65.0': optional: true - "@rollup/rollup-win32-arm64-msvc@4.65.0": + '@rollup/rollup-win32-arm64-msvc@4.65.0': optional: true - "@rollup/rollup-win32-ia32-msvc@4.65.0": + '@rollup/rollup-win32-ia32-msvc@4.65.0': optional: true - "@rollup/rollup-win32-x64-gnu@4.65.0": + '@rollup/rollup-win32-x64-gnu@4.65.0': optional: true - "@rollup/rollup-win32-x64-msvc@4.65.0": + '@rollup/rollup-win32-x64-msvc@4.65.0': optional: true - "@shikijs/core@3.23.0": + '@shikijs/core@3.23.0': dependencies: - "@shikijs/types": 3.23.0 - "@shikijs/vscode-textmate": 10.1.0 - "@types/hast": 3.0.5 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.1.0 + '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 - "@shikijs/engine-javascript@3.23.0": + '@shikijs/engine-javascript@3.23.0': dependencies: - "@shikijs/types": 3.23.0 - "@shikijs/vscode-textmate": 10.1.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.1.0 oniguruma-to-es: 4.3.6 - "@shikijs/engine-oniguruma@3.23.0": + '@shikijs/engine-oniguruma@3.23.0': dependencies: - "@shikijs/types": 3.23.0 - "@shikijs/vscode-textmate": 10.1.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.1.0 - "@shikijs/langs@3.23.0": + '@shikijs/langs@3.23.0': dependencies: - "@shikijs/types": 3.23.0 + '@shikijs/types': 3.23.0 - "@shikijs/themes@3.23.0": + '@shikijs/themes@3.23.0': dependencies: - "@shikijs/types": 3.23.0 + '@shikijs/types': 3.23.0 - "@shikijs/types@3.23.0": + '@shikijs/types@3.23.0': dependencies: - "@shikijs/vscode-textmate": 10.1.0 - "@types/hast": 3.0.5 + '@shikijs/vscode-textmate': 10.1.0 + '@types/hast': 3.0.5 - "@shikijs/vscode-textmate@10.1.0": {} + '@shikijs/vscode-textmate@10.1.0': {} - "@tailwindcss/node@4.3.3": + '@tailwindcss/node@4.3.3': dependencies: - "@jridgewell/remapping": 2.3.5 + '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.26.0 jiti: 2.7.0 lightningcss: 1.32.0 @@ -6360,132 +4298,139 @@ snapshots: source-map-js: 1.2.2 tailwindcss: 4.3.3 - "@tailwindcss/oxide-android-arm64@4.3.3": + '@tailwindcss/oxide-android-arm64@4.3.3': optional: true - "@tailwindcss/oxide-darwin-arm64@4.3.3": + '@tailwindcss/oxide-darwin-arm64@4.3.3': optional: true - "@tailwindcss/oxide-darwin-x64@4.3.3": + '@tailwindcss/oxide-darwin-x64@4.3.3': optional: true - "@tailwindcss/oxide-freebsd-x64@4.3.3": + '@tailwindcss/oxide-freebsd-x64@4.3.3': optional: true - "@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3": + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': optional: true - "@tailwindcss/oxide-linux-arm64-gnu@4.3.3": + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': optional: true - "@tailwindcss/oxide-linux-arm64-musl@4.3.3": + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': optional: true - "@tailwindcss/oxide-linux-x64-gnu@4.3.3": + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': optional: true - "@tailwindcss/oxide-linux-x64-musl@4.3.3": + '@tailwindcss/oxide-linux-x64-musl@4.3.3': optional: true - "@tailwindcss/oxide-wasm32-wasi@4.3.3": + '@tailwindcss/oxide-wasm32-wasi@4.3.3': optional: true - "@tailwindcss/oxide-win32-arm64-msvc@4.3.3": + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': optional: true - "@tailwindcss/oxide-win32-x64-msvc@4.3.3": + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': optional: true - "@tailwindcss/oxide@4.3.3": + '@tailwindcss/oxide@4.3.3': optionalDependencies: - "@tailwindcss/oxide-android-arm64": 4.3.3 - "@tailwindcss/oxide-darwin-arm64": 4.3.3 - "@tailwindcss/oxide-darwin-x64": 4.3.3 - "@tailwindcss/oxide-freebsd-x64": 4.3.3 - "@tailwindcss/oxide-linux-arm-gnueabihf": 4.3.3 - "@tailwindcss/oxide-linux-arm64-gnu": 4.3.3 - "@tailwindcss/oxide-linux-arm64-musl": 4.3.3 - "@tailwindcss/oxide-linux-x64-gnu": 4.3.3 - "@tailwindcss/oxide-linux-x64-musl": 4.3.3 - "@tailwindcss/oxide-wasm32-wasi": 4.3.3 - "@tailwindcss/oxide-win32-arm64-msvc": 4.3.3 - "@tailwindcss/oxide-win32-x64-msvc": 4.3.3 - - "@tailwindcss/vite@4.3.3(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1))": - dependencies: - "@tailwindcss/node": 4.3.3 - "@tailwindcss/oxide": 4.3.3 + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/vite@4.3.3(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1))': + dependencies: + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) - "@tybys/wasm-util@0.10.4": + '@tybys/wasm-util@0.10.4': dependencies: tslib: 2.8.1 - "@types/babel__core@7.20.5": + '@types/babel__core@7.20.5': dependencies: - "@babel/parser": 7.29.9 - "@babel/types": 7.29.8 - "@types/babel__generator": 7.27.0 - "@types/babel__template": 7.4.4 - "@types/babel__traverse": 7.28.0 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 - "@types/babel__generator@7.27.0": + '@types/babel__generator@7.27.0': dependencies: - "@babel/types": 7.29.8 + '@babel/types': 7.29.8 - "@types/babel__template@7.4.4": + '@types/babel__template@7.4.4': dependencies: - "@babel/parser": 7.29.9 - "@babel/types": 7.29.8 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 - "@types/babel__traverse@7.28.0": + '@types/babel__traverse@7.28.0': dependencies: - "@babel/types": 7.29.8 + '@babel/types': 7.29.8 - "@types/debug@4.1.13": + '@types/chai@5.2.3': dependencies: - "@types/ms": 2.1.0 + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - "@types/estree@1.0.9": {} + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} - "@types/hast@3.0.5": + '@types/estree@1.0.9': {} + + '@types/hast@3.0.5': dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 - "@types/json-schema@7.0.15": {} + '@types/json-schema@7.0.15': {} - "@types/mdast@4.0.4": + '@types/mdast@4.0.4': dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 - "@types/ms@2.1.0": {} + '@types/ms@2.1.0': {} - "@types/nlcst@2.0.3": + '@types/nlcst@2.0.3': dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 - "@types/prop-types@15.7.15": {} + '@types/prop-types@15.7.15': {} - "@types/react-dom@18.3.7(@types/react@18.3.31)": + '@types/react-dom@18.3.7(@types/react@18.3.31)': dependencies: - "@types/react": 18.3.31 + '@types/react': 18.3.31 - "@types/react@18.3.31": + '@types/react@18.3.31': dependencies: - "@types/prop-types": 15.7.15 + '@types/prop-types': 15.7.15 csstype: 3.2.3 - "@types/unist@3.0.3": {} + '@types/unist@3.0.3': {} - "@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/eslint-plugin@8.72.0(@typescript-eslint/parser@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@eslint-community/regexpp": 4.12.2 - "@typescript-eslint/parser": 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/scope-manager": 8.72.0 - "@typescript-eslint/type-utils": 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/utils": 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/visitor-keys": 8.72.0 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/type-utils': 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.0 eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) ignore: 7.0.13 natural-compare: 1.4.0 @@ -6494,41 +4439,41 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/parser@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@typescript-eslint/scope-manager": 8.72.0 - "@typescript-eslint/types": 8.72.0 - "@typescript-eslint/typescript-estree": 8.72.0(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/visitor-keys": 8.72.0 + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.72.0 debug: 4.4.3(supports-color@7.2.0) eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/project-service@8.72.0(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/project-service@8.72.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@typescript-eslint/tsconfig-utils": 8.72.0(typescript@5.9.3) - "@typescript-eslint/types": 8.72.0 + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 debug: 4.4.3(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/scope-manager@8.72.0": + '@typescript-eslint/scope-manager@8.72.0': dependencies: - "@typescript-eslint/types": 8.72.0 - "@typescript-eslint/visitor-keys": 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 - "@typescript-eslint/tsconfig-utils@8.72.0(typescript@5.9.3)": + '@typescript-eslint/tsconfig-utils@8.72.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - "@typescript-eslint/type-utils@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/type-utils@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@typescript-eslint/types": 8.72.0 - "@typescript-eslint/typescript-estree": 8.72.0(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/utils": 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) debug: 4.4.3(supports-color@7.2.0) eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6536,14 +4481,14 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/types@8.72.0": {} + '@typescript-eslint/types@8.72.0': {} - "@typescript-eslint/typescript-estree@8.72.0(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/typescript-estree@8.72.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@typescript-eslint/project-service": 8.72.0(supports-color@7.2.0)(typescript@5.9.3) - "@typescript-eslint/tsconfig-utils": 8.72.0(typescript@5.9.3) - "@typescript-eslint/types": 8.72.0 - "@typescript-eslint/visitor-keys": 8.72.0 + '@typescript-eslint/project-service': 8.72.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.72.0(typescript@5.9.3) + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/visitor-keys': 8.72.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 @@ -6553,54 +4498,100 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)": + '@typescript-eslint/utils@8.72.0(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - "@eslint-community/eslint-utils": 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) - "@typescript-eslint/scope-manager": 8.72.0 - "@typescript-eslint/types": 8.72.0 - "@typescript-eslint/typescript-estree": 8.72.0(supports-color@7.2.0)(typescript@5.9.3) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.72.0 + '@typescript-eslint/types': 8.72.0 + '@typescript-eslint/typescript-estree': 8.72.0(supports-color@7.2.0)(typescript@5.9.3) eslint: 9.39.5(jiti@2.7.0)(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/visitor-keys@8.72.0": + '@typescript-eslint/visitor-keys@8.72.0': dependencies: - "@typescript-eslint/types": 8.72.0 + '@typescript-eslint/types': 8.72.0 eslint-visitor-keys: 5.0.1 - "@ungap/structured-clone@1.4.0": {} + '@ungap/structured-clone@1.4.0': {} - "@vitejs/plugin-react@4.7.0(supports-color@7.2.0)(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1))": + '@vitejs/plugin-react@4.7.0(supports-color@7.2.0)(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1))': dependencies: - "@babel/core": 7.29.7(supports-color@7.2.0) - "@babel/plugin-transform-react-jsx-self": 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - "@babel/plugin-transform-react-jsx-source": 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) - "@rolldown/pluginutils": 1.0.0-beta.27 - "@types/babel__core": 7.20.5 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 react-refresh: 0.17.0 vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) transitivePeerDependencies: - supports-color - "@volar/kit@2.4.30(typescript@5.9.3)": + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) + + '@vitest/pretty-format@3.2.4': dependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) - "@volar/typescript": 2.4.30(typescript@5.9.3) + tinyrainbow: 2.0.0 + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.6 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + '@volar/kit@2.4.30(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.30(typescript@5.9.3) + '@volar/typescript': 2.4.30(typescript@5.9.3) typesafe-path: 0.2.2 typescript: 5.9.3 vscode-languageserver-textdocument: 1.0.16 vscode-uri: 3.2.0 - "@volar/language-core@2.4.30": + '@volar/language-core@2.4.30': dependencies: - "@volar/source-map": 2.4.30 + '@volar/source-map': 2.4.30 - "@volar/language-server@2.4.30(typescript@5.9.3)": + '@volar/language-server@2.4.30(typescript@5.9.3)': dependencies: - "@volar/language-core": 2.4.30 - "@volar/language-service": 2.4.30(typescript@5.9.3) - "@volar/typescript": 2.4.30(typescript@5.9.3) + '@volar/language-core': 2.4.30 + '@volar/language-service': 2.4.30(typescript@5.9.3) + '@volar/typescript': 2.4.30(typescript@5.9.3) path-browserify: 1.0.1 request-light: 0.7.0 vscode-languageserver: 9.0.1 @@ -6610,27 +4601,27 @@ snapshots: optionalDependencies: typescript: 5.9.3 - "@volar/language-service@2.4.30(typescript@5.9.3)": + '@volar/language-service@2.4.30(typescript@5.9.3)': dependencies: - "@volar/language-core": 2.4.30 + '@volar/language-core': 2.4.30 vscode-languageserver-protocol: 3.18.5 vscode-languageserver-textdocument: 1.0.16 vscode-uri: 3.2.0 optionalDependencies: typescript: 5.9.3 - "@volar/source-map@2.4.30": {} + '@volar/source-map@2.4.30': {} - "@volar/typescript@2.4.30(typescript@5.9.3)": + '@volar/typescript@2.4.30(typescript@5.9.3)': dependencies: - "@volar/language-core": 2.4.30 - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-core': 2.4.30 + '@volar/language-service': 2.4.30(typescript@5.9.3) path-browserify: 1.0.1 vscode-uri: 3.2.0 optionalDependencies: typescript: 5.9.3 - "@vscode/emmet-helper@2.11.0": + '@vscode/emmet-helper@2.11.0': dependencies: emmet: 2.4.11 jsonc-parser: 2.3.1 @@ -6638,7 +4629,7 @@ snapshots: vscode-languageserver-types: 3.18.5 vscode-uri: 3.2.0 - "@vscode/l10n@0.0.18": {} + '@vscode/l10n@0.0.18': {} acorn-jsx@5.3.2(acorn@8.19.0): dependencies: @@ -6750,15 +4741,17 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + astro@5.18.2(jiti@2.7.0)(lightningcss@1.32.0)(rollup@4.65.0)(supports-color@7.2.0)(typescript@5.9.3)(yaml@2.9.1): dependencies: - "@astrojs/compiler": 2.13.1 - "@astrojs/internal-helpers": 0.7.6 - "@astrojs/markdown-remark": 6.3.11(supports-color@7.2.0) - "@astrojs/telemetry": 3.3.0(supports-color@7.2.0) - "@capsizecss/unpack": 4.0.1 - "@oslojs/encoding": 1.1.0 - "@rollup/pluginutils": 5.5.1(rollup@4.65.0) + '@astrojs/compiler': 2.13.1 + '@astrojs/internal-helpers': 0.7.6 + '@astrojs/markdown-remark': 6.3.11(supports-color@7.2.0) + '@astrojs/telemetry': 3.3.0(supports-color@7.2.0) + '@capsizecss/unpack': 4.0.1 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.5.1(rollup@4.65.0) acorn: 8.19.0 aria-query: 5.3.2 axobject-query: 4.1.0 @@ -6818,21 +4811,21 @@ snapshots: optionalDependencies: sharp: 0.34.5 transitivePeerDependencies: - - "@azure/app-configuration" - - "@azure/cosmos" - - "@azure/data-tables" - - "@azure/identity" - - "@azure/keyvault-secrets" - - "@azure/storage-blob" - - "@capacitor/preferences" - - "@deno/kv" - - "@netlify/blobs" - - "@planetscale/database" - - "@types/node" - - "@upstash/redis" - - "@vercel/blob" - - "@vercel/functions" - - "@vercel/kv" + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' - aws4fetch - db0 - idb-keyval @@ -6900,6 +4893,8 @@ snapshots: node-releases: 2.0.60 update-browserslist-db: 1.3.4(browserslist@4.29.4) + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6925,6 +4920,14 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6938,6 +4941,8 @@ snapshots: character-entities@2.0.2: {} + check-error@2.1.3: {} + chokidar@5.0.0: dependencies: readdirp: 5.1.1 @@ -7040,6 +5045,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} define-data-property@1.1.4: @@ -7110,8 +5117,8 @@ snapshots: emmet@2.4.11: dependencies: - "@emmetio/abbreviation": 2.3.3 - "@emmetio/css-abbreviation": 2.1.8 + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 emoji-regex@10.6.0: {} @@ -7241,61 +5248,61 @@ snapshots: esbuild@0.25.12: optionalDependencies: - "@esbuild/aix-ppc64": 0.25.12 - "@esbuild/android-arm": 0.25.12 - "@esbuild/android-arm64": 0.25.12 - "@esbuild/android-x64": 0.25.12 - "@esbuild/darwin-arm64": 0.25.12 - "@esbuild/darwin-x64": 0.25.12 - "@esbuild/freebsd-arm64": 0.25.12 - "@esbuild/freebsd-x64": 0.25.12 - "@esbuild/linux-arm": 0.25.12 - "@esbuild/linux-arm64": 0.25.12 - "@esbuild/linux-ia32": 0.25.12 - "@esbuild/linux-loong64": 0.25.12 - "@esbuild/linux-mips64el": 0.25.12 - "@esbuild/linux-ppc64": 0.25.12 - "@esbuild/linux-riscv64": 0.25.12 - "@esbuild/linux-s390x": 0.25.12 - "@esbuild/linux-x64": 0.25.12 - "@esbuild/netbsd-arm64": 0.25.12 - "@esbuild/netbsd-x64": 0.25.12 - "@esbuild/openbsd-arm64": 0.25.12 - "@esbuild/openbsd-x64": 0.25.12 - "@esbuild/openharmony-arm64": 0.25.12 - "@esbuild/sunos-x64": 0.25.12 - "@esbuild/win32-arm64": 0.25.12 - "@esbuild/win32-ia32": 0.25.12 - "@esbuild/win32-x64": 0.25.12 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 esbuild@0.27.7: optionalDependencies: - "@esbuild/aix-ppc64": 0.27.7 - "@esbuild/android-arm": 0.27.7 - "@esbuild/android-arm64": 0.27.7 - "@esbuild/android-x64": 0.27.7 - "@esbuild/darwin-arm64": 0.27.7 - "@esbuild/darwin-x64": 0.27.7 - "@esbuild/freebsd-arm64": 0.27.7 - "@esbuild/freebsd-x64": 0.27.7 - "@esbuild/linux-arm": 0.27.7 - "@esbuild/linux-arm64": 0.27.7 - "@esbuild/linux-ia32": 0.27.7 - "@esbuild/linux-loong64": 0.27.7 - "@esbuild/linux-mips64el": 0.27.7 - "@esbuild/linux-ppc64": 0.27.7 - "@esbuild/linux-riscv64": 0.27.7 - "@esbuild/linux-s390x": 0.27.7 - "@esbuild/linux-x64": 0.27.7 - "@esbuild/netbsd-arm64": 0.27.7 - "@esbuild/netbsd-x64": 0.27.7 - "@esbuild/openbsd-arm64": 0.27.7 - "@esbuild/openbsd-x64": 0.27.7 - "@esbuild/openharmony-arm64": 0.27.7 - "@esbuild/sunos-x64": 0.27.7 - "@esbuild/win32-arm64": 0.27.7 - "@esbuild/win32-ia32": 0.27.7 - "@esbuild/win32-x64": 0.27.7 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 escalade@3.2.0: {} @@ -7342,18 +5349,18 @@ snapshots: eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0): dependencies: - "@eslint-community/eslint-utils": 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) - "@eslint-community/regexpp": 4.12.2 - "@eslint/config-array": 0.21.2(supports-color@7.2.0) - "@eslint/config-helpers": 0.4.2 - "@eslint/core": 0.17.0 - "@eslint/eslintrc": 3.3.7(supports-color@7.2.0) - "@eslint/js": 9.39.5 - "@eslint/plugin-kit": 0.4.1 - "@humanfs/node": 0.16.8 - "@humanwhocodes/module-importer": 1.0.1 - "@humanwhocodes/retry": 0.4.3 - "@types/estree": 1.0.9 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0)(supports-color@7.2.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2(supports-color@7.2.0) + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.7(supports-color@7.2.0) + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -7401,12 +5408,14 @@ snapshots: estree-walker@3.0.3: dependencies: - "@types/estree": 1.0.9 + '@types/estree': 1.0.9 esutils@2.0.3: {} eventemitter3@5.0.4: {} + expect-type@1.4.0: {} + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -7451,6 +5460,9 @@ snapshots: dependencies: is-callable: 1.2.7 + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -7557,7 +5569,7 @@ snapshots: hast-util-from-html@2.0.3: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 devlop: 1.1.0 hast-util-from-parse5: 8.0.3 parse5: 7.3.0 @@ -7566,8 +5578,8 @@ snapshots: hast-util-from-parse5@8.0.3: dependencies: - "@types/hast": 3.0.5 - "@types/unist": 3.0.3 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 devlop: 1.1.0 hastscript: 9.0.1 property-information: 7.2.0 @@ -7577,17 +5589,17 @@ snapshots: hast-util-is-element@3.0.0: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hast-util-parse-selector@4.0.0: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hast-util-raw@9.1.0: dependencies: - "@types/hast": 3.0.5 - "@types/unist": 3.0.3 - "@ungap/structured-clone": 1.4.0 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.4.0 hast-util-from-parse5: 8.0.3 hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 @@ -7601,8 +5613,8 @@ snapshots: hast-util-to-html@9.0.5: dependencies: - "@types/hast": 3.0.5 - "@types/unist": 3.0.3 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 ccount: 2.0.1 comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 @@ -7615,7 +5627,7 @@ snapshots: hast-util-to-parse5@8.0.1: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 comma-separated-tokens: 2.0.3 devlop: 1.1.0 property-information: 7.2.0 @@ -7625,18 +5637,18 @@ snapshots: hast-util-to-text@4.0.2: dependencies: - "@types/hast": 3.0.5 - "@types/unist": 3.0.3 + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 hast-util-is-element: 3.0.0 unist-util-find-after: 5.0.0 hast-util-whitespace@3.0.0: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hastscript@9.0.1: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 7.2.0 @@ -7810,6 +5822,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.3.2: dependencies: argparse: 2.0.1 @@ -7911,6 +5925,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lru-cache@11.5.3: {} lru-cache@5.1.1: @@ -7919,12 +5935,12 @@ snapshots: magic-string@0.30.21: dependencies: - "@jridgewell/sourcemap-codec": 1.6.0 + '@jridgewell/sourcemap-codec': 1.6.0 magicast@0.5.5: dependencies: - "@babel/parser": 7.29.9 - "@babel/types": 7.29.8 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 source-map-js: 1.2.2 markdown-table@3.0.4: {} @@ -7933,21 +5949,21 @@ snapshots: mdast-util-definitions@6.0.0: dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 unist-util-visit: 5.1.0 mdast-util-find-and-replace@3.0.3: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 mdast-util-from-markdown@2.1.0(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 @@ -7963,7 +5979,7 @@ snapshots: mdast-util-gfm-autolink-literal@2.0.1: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 mdast-util-find-and-replace: 3.0.3 @@ -7971,7 +5987,7 @@ snapshots: mdast-util-gfm-footnote@2.1.0(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.1.0(supports-color@7.2.0) mdast-util-to-markdown: 2.2.0 @@ -7981,7 +5997,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.1(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.1.0(supports-color@7.2.0) mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: @@ -7989,7 +6005,7 @@ snapshots: mdast-util-gfm-table@2.0.0(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 mdast-util-from-markdown: 2.1.0(supports-color@7.2.0) @@ -7999,7 +6015,7 @@ snapshots: mdast-util-gfm-task-list-item@2.0.0(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.1.0(supports-color@7.2.0) mdast-util-to-markdown: 2.2.0 @@ -8020,14 +6036,14 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 unist-util-is: 6.0.1 mdast-util-to-hast@13.2.1: dependencies: - "@types/hast": 3.0.5 - "@types/mdast": 4.0.4 - "@ungap/structured-clone": 1.4.0 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -8037,8 +6053,8 @@ snapshots: mdast-util-to-markdown@2.2.0: dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 @@ -8051,7 +6067,7 @@ snapshots: mdast-util-to-string@4.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdn-data@2.0.28: {} @@ -8233,7 +6249,7 @@ snapshots: micromark@4.0.3(supports-color@7.2.0): dependencies: - "@types/debug": 4.1.13 + '@types/debug': 4.1.13 debug: 4.4.3(supports-color@7.2.0) decode-named-character-reference: 1.3.0 devlop: 1.1.0 @@ -8276,7 +6292,7 @@ snapshots: nlcst-to-string@4.0.0: dependencies: - "@types/nlcst": 2.0.3 + '@types/nlcst': 2.0.3 node-exports-info@1.6.2: dependencies: @@ -8392,8 +6408,8 @@ snapshots: parse-latin@7.0.0: dependencies: - "@types/nlcst": 2.0.3 - "@types/unist": 3.0.3 + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 nlcst-to-string: 4.0.0 unist-util-modify-children: 4.0.0 unist-util-visit-children: 3.0.0 @@ -8411,6 +6427,10 @@ snapshots: path-parse@1.0.7: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + piccolore@0.1.3: {} picocolors@1.1.1: {} @@ -8419,6 +6439,14 @@ snapshots: picomatch@4.0.7: {} + playwright-core@1.56.1: {} + + playwright@1.56.1: + dependencies: + playwright-core: 1.56.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss@8.5.31: @@ -8502,32 +6530,32 @@ snapshots: rehype-parse@9.0.1: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hast-util-from-html: 2.0.3 unified: 11.0.5 rehype-raw@7.0.0: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hast-util-raw: 9.1.0 vfile: 6.0.3 rehype-stringify@10.0.1: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 hast-util-to-html: 9.0.5 unified: 11.0.5 rehype@13.0.2: dependencies: - "@types/hast": 3.0.5 + '@types/hast': 3.0.5 rehype-parse: 9.0.1 rehype-stringify: 10.0.1 unified: 11.0.5 remark-gfm@4.0.1(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-gfm: 3.1.0(supports-color@7.2.0) micromark-extension-gfm: 3.0.0 remark-parse: 11.0.0(supports-color@7.2.0) @@ -8538,7 +6566,7 @@ snapshots: remark-parse@11.0.0(supports-color@7.2.0): dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.1.0(supports-color@7.2.0) micromark-util-types: 2.0.3 unified: 11.0.5 @@ -8547,8 +6575,8 @@ snapshots: remark-rehype@11.1.2: dependencies: - "@types/hast": 3.0.5 - "@types/mdast": 4.0.4 + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.1 unified: 11.0.5 vfile: 6.0.3 @@ -8562,7 +6590,7 @@ snapshots: remark-stringify@11.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-to-markdown: 2.2.0 unified: 11.0.5 @@ -8585,59 +6613,59 @@ snapshots: retext-latin@4.0.0: dependencies: - "@types/nlcst": 2.0.3 + '@types/nlcst': 2.0.3 parse-latin: 7.0.0 unified: 11.0.5 retext-smartypants@6.2.0: dependencies: - "@types/nlcst": 2.0.3 + '@types/nlcst': 2.0.3 nlcst-to-string: 4.0.0 unist-util-visit: 5.1.0 retext-stringify@4.0.0: dependencies: - "@types/nlcst": 2.0.3 + '@types/nlcst': 2.0.3 nlcst-to-string: 4.0.0 unified: 11.0.5 retext@9.0.0: dependencies: - "@types/nlcst": 2.0.3 + '@types/nlcst': 2.0.3 retext-latin: 4.0.0 retext-stringify: 4.0.0 unified: 11.0.5 rollup@4.65.0: dependencies: - "@types/estree": 1.0.9 + '@types/estree': 1.0.9 optionalDependencies: - "@napi-rs/lzma-linux-x64-gnu": 1.5.1 - "@rollup/rollup-android-arm-eabi": 4.65.0 - "@rollup/rollup-android-arm64": 4.65.0 - "@rollup/rollup-darwin-arm64": 4.65.0 - "@rollup/rollup-darwin-x64": 4.65.0 - "@rollup/rollup-freebsd-arm64": 4.65.0 - "@rollup/rollup-freebsd-x64": 4.65.0 - "@rollup/rollup-linux-arm-gnueabihf": 4.65.0 - "@rollup/rollup-linux-arm-musleabihf": 4.65.0 - "@rollup/rollup-linux-arm64-gnu": 4.65.0 - "@rollup/rollup-linux-arm64-musl": 4.65.0 - "@rollup/rollup-linux-loong64-gnu": 4.65.0 - "@rollup/rollup-linux-loong64-musl": 4.65.0 - "@rollup/rollup-linux-ppc64-gnu": 4.65.0 - "@rollup/rollup-linux-ppc64-musl": 4.65.0 - "@rollup/rollup-linux-riscv64-gnu": 4.65.0 - "@rollup/rollup-linux-riscv64-musl": 4.65.0 - "@rollup/rollup-linux-s390x-gnu": 4.65.0 - "@rollup/rollup-linux-x64-gnu": 4.65.0 - "@rollup/rollup-linux-x64-musl": 4.65.0 - "@rollup/rollup-openbsd-x64": 4.65.0 - "@rollup/rollup-openharmony-arm64": 4.65.0 - "@rollup/rollup-win32-arm64-msvc": 4.65.0 - "@rollup/rollup-win32-ia32-msvc": 4.65.0 - "@rollup/rollup-win32-x64-gnu": 4.65.0 - "@rollup/rollup-win32-x64-msvc": 4.65.0 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.65.0 + '@rollup/rollup-android-arm64': 4.65.0 + '@rollup/rollup-darwin-arm64': 4.65.0 + '@rollup/rollup-darwin-x64': 4.65.0 + '@rollup/rollup-freebsd-arm64': 4.65.0 + '@rollup/rollup-freebsd-x64': 4.65.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.65.0 + '@rollup/rollup-linux-arm-musleabihf': 4.65.0 + '@rollup/rollup-linux-arm64-gnu': 4.65.0 + '@rollup/rollup-linux-arm64-musl': 4.65.0 + '@rollup/rollup-linux-loong64-gnu': 4.65.0 + '@rollup/rollup-linux-loong64-musl': 4.65.0 + '@rollup/rollup-linux-ppc64-gnu': 4.65.0 + '@rollup/rollup-linux-ppc64-musl': 4.65.0 + '@rollup/rollup-linux-riscv64-gnu': 4.65.0 + '@rollup/rollup-linux-riscv64-musl': 4.65.0 + '@rollup/rollup-linux-s390x-gnu': 4.65.0 + '@rollup/rollup-linux-x64-gnu': 4.65.0 + '@rollup/rollup-linux-x64-musl': 4.65.0 + '@rollup/rollup-openbsd-x64': 4.65.0 + '@rollup/rollup-openharmony-arm64': 4.65.0 + '@rollup/rollup-win32-arm64-msvc': 4.65.0 + '@rollup/rollup-win32-ia32-msvc': 4.65.0 + '@rollup/rollup-win32-x64-gnu': 4.65.0 + '@rollup/rollup-win32-x64-msvc': 4.65.0 fsevents: 2.3.3 safe-array-concat@1.1.4: @@ -8693,34 +6721,34 @@ snapshots: sharp@0.34.5: dependencies: - "@img/colour": 1.1.0 + '@img/colour': 1.1.0 detect-libc: 2.1.2 semver: 7.8.5 optionalDependencies: - "@img/sharp-darwin-arm64": 0.34.5 - "@img/sharp-darwin-x64": 0.34.5 - "@img/sharp-libvips-darwin-arm64": 1.2.4 - "@img/sharp-libvips-darwin-x64": 1.2.4 - "@img/sharp-libvips-linux-arm": 1.2.4 - "@img/sharp-libvips-linux-arm64": 1.2.4 - "@img/sharp-libvips-linux-ppc64": 1.2.4 - "@img/sharp-libvips-linux-riscv64": 1.2.4 - "@img/sharp-libvips-linux-s390x": 1.2.4 - "@img/sharp-libvips-linux-x64": 1.2.4 - "@img/sharp-libvips-linuxmusl-arm64": 1.2.4 - "@img/sharp-libvips-linuxmusl-x64": 1.2.4 - "@img/sharp-linux-arm": 0.34.5 - "@img/sharp-linux-arm64": 0.34.5 - "@img/sharp-linux-ppc64": 0.34.5 - "@img/sharp-linux-riscv64": 0.34.5 - "@img/sharp-linux-s390x": 0.34.5 - "@img/sharp-linux-x64": 0.34.5 - "@img/sharp-linuxmusl-arm64": 0.34.5 - "@img/sharp-linuxmusl-x64": 0.34.5 - "@img/sharp-wasm32": 0.34.5 - "@img/sharp-win32-arm64": 0.34.5 - "@img/sharp-win32-ia32": 0.34.5 - "@img/sharp-win32-x64": 0.34.5 + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 optional: true shebang-command@2.0.0: @@ -8731,14 +6759,14 @@ snapshots: shiki@3.23.0: dependencies: - "@shikijs/core": 3.23.0 - "@shikijs/engine-javascript": 3.23.0 - "@shikijs/engine-oniguruma": 3.23.0 - "@shikijs/langs": 3.23.0 - "@shikijs/themes": 3.23.0 - "@shikijs/types": 3.23.0 - "@shikijs/vscode-textmate": 10.1.0 - "@types/hast": 3.0.5 + '@shikijs/core': 3.23.0 + '@shikijs/engine-javascript': 3.23.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 + '@shikijs/vscode-textmate': 10.1.0 + '@types/hast': 3.0.5 side-channel-list@1.0.1: dependencies: @@ -8768,6 +6796,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + sisteransi@1.0.5: {} smol-toml@1.9.1: {} @@ -8776,6 +6806,10 @@ snapshots: space-separated-tokens@2.0.2: {} + stackback@0.0.2: {} + + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -8858,6 +6892,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8882,6 +6920,10 @@ snapshots: tiny-inflate@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -8889,6 +6931,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.6: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -8966,7 +7014,7 @@ snapshots: unified@11.0.5: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 bail: 2.0.2 devlop: 1.1.0 extend: 3.0.2 @@ -8982,43 +7030,43 @@ snapshots: unist-util-find-after@5.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-is: 6.0.1 unist-util-is@6.0.1: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-modify-children@4.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 array-iterate: 2.0.1 unist-util-position@5.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-remove-position@5.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-visit: 5.1.0 unist-util-stringify-position@4.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-visit-children@3.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-visit-parents@6.0.2: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-is: 6.0.1 unist-util-visit@5.1.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 @@ -9045,19 +7093,40 @@ snapshots: vfile-location@5.0.3: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 vfile: 6.0.3 vfile-message@4.0.3: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 vfile@6.0.3: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite-node@3.2.4(jiti@2.7.0)(lightningcss@1.32.0)(supports-color@7.2.0)(yaml@2.9.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@7.2.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1): dependencies: esbuild: 0.25.12 @@ -9076,22 +7145,63 @@ snapshots: optionalDependencies: vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) + vitest@3.2.4(@types/debug@4.1.13)(jiti@2.7.0)(lightningcss@1.32.0)(supports-color@7.2.0)(yaml@2.9.1): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3(supports-color@7.2.0) + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.4.5(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.1) + vite-node: 3.2.4(jiti@2.7.0)(lightningcss@1.32.0)(supports-color@7.2.0)(yaml@2.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.13 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + volar-service-css@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3)): dependencies: vscode-css-languageservice: 6.3.10 vscode-languageserver-textdocument: 1.0.16 vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) volar-service-emmet@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3)): dependencies: - "@emmetio/css-parser": 0.4.1 - "@emmetio/html-matcher": 1.3.0 - "@vscode/emmet-helper": 2.11.0 + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) volar-service-html@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3)): dependencies: @@ -9099,20 +7209,20 @@ snapshots: vscode-languageserver-textdocument: 1.0.16 vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) volar-service-prettier@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3))(prettier@3.9.10): dependencies: vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) prettier: 3.9.10 volar-service-typescript-twoslash-queries@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3))(typescript@5.9.3): dependencies: vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) typescript: 5.9.3 volar-service-typescript@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3))(typescript@5.9.3): @@ -9124,7 +7234,7 @@ snapshots: vscode-nls: 5.2.0 vscode-uri: 3.2.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) typescript: 5.9.3 volar-service-yaml@0.0.71(@volar/language-service@2.4.30(typescript@5.9.3)): @@ -9132,18 +7242,18 @@ snapshots: vscode-uri: 3.2.0 yaml-language-server: 1.23.0 optionalDependencies: - "@volar/language-service": 2.4.30(typescript@5.9.3) + '@volar/language-service': 2.4.30(typescript@5.9.3) vscode-css-languageservice@6.3.10: dependencies: - "@vscode/l10n": 0.0.18 + '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.16 vscode-languageserver-types: 3.17.5 vscode-uri: 3.2.0 vscode-html-languageservice@5.6.2: dependencies: - "@vscode/l10n": 0.0.18 + '@vscode/l10n': 0.0.18 vscode-languageserver-textdocument: 1.0.16 vscode-languageserver-types: 3.18.5 vscode-uri: 3.2.0 @@ -9233,6 +7343,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + widest-line@5.0.0: dependencies: string-width: 7.2.0 @@ -9253,7 +7368,7 @@ snapshots: yaml-language-server@1.23.0: dependencies: - "@vscode/l10n": 0.0.18 + '@vscode/l10n': 0.0.18 ajv: 8.20.0 ajv-draft-04: 1.0.0(ajv@8.20.0) ajv-i18n: 4.2.0(ajv@8.20.0) diff --git a/web/src/components/AsmEditor.tsx b/web/src/components/AsmEditor.tsx new file mode 100644 index 0000000..d814185 --- /dev/null +++ b/web/src/components/AsmEditor.tsx @@ -0,0 +1,59 @@ +import { useRef } from "react"; +import type { AsmError } from "../lib/assembler"; + +interface Props { + value: string; + onChange: (v: string) => void; + errors: AsmError[]; + /** 1-based source line of the instruction executing now, if any */ + activeLine?: number; +} + +const LINE_HEIGHT = "1.5rem"; + +export function AsmEditor({ value, onChange, errors, activeLine }: Props) { + const gutter = useRef(null); + const lines = value.split("\n").length; + const errorLines = new Map(errors.map((e) => [e.line, e.message])); + + return ( +
+
+ {Array.from({ length: lines }, (_, i) => { + const n = i + 1; + const isErr = errorLines.has(n); + const isActive = n === activeLine; + return ( +
+ {n} +
+ ); + })} +
+