Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 42 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,29 @@ env:

permissions:
contents: read
pages: write
id-token: write

jobs:
# ═════════════════════════════════════════════════════════════════════════
# 课题功能测试:所有14个模块的单元测试 + 集成测试
# ═════════════════════════════════════════════════════════════════════════
test:
runs-on: self-hosted
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'self-hosted' }}
timeout-minutes: 20
steps:
- name: Checkout pull request on isolated runner
if: github.event_name == 'pull_request'
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false

- name: Set up Python for pull request
if: github.event_name == 'pull_request'
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Sync from local mirror (skip unstable GitHub checkout)
if: github.event_name != 'pull_request'
run: |
echo "=== CI Sync ==="
retry() {
Expand All @@ -46,12 +57,6 @@ jobs:
cd "$WORKSPACE"
echo "Fetching origin main..."
retry "git fetch origin main" git fetch origin main || exit 1
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
PR_NUMBER="${GITHUB_REF#refs/pull/}"
PR_NUMBER="${PR_NUMBER%/merge}"
echo "Fetching PR #${PR_NUMBER} merge ref..."
retry "git fetch PR #${PR_NUMBER}" git fetch origin "refs/pull/${PR_NUMBER}/merge" || exit 1
fi
echo "Checking out $GITHUB_SHA..."
if git -c advice.detachedHead=false checkout -f "$GITHUB_SHA"; then
echo "::notice::Checkout successful: $(git log -1 --format='%h %ai %s')"
Expand All @@ -64,6 +69,7 @@ jobs:
run: |
python3.12 -m pip install --upgrade pip
python3.12 -m pip install -e ".[all]"
python3.12 -m pip install "pytest>=7,<10"

- name: Run all topic tests
run: |
Expand All @@ -72,6 +78,14 @@ jobs:
--junit-xml=benchmark_reports/test_results.xml \
--ignore=tests/test_simulator.py

- name: Run PR #35 constant-merge regressions
run: |
python3.12 -m pytest \
tests/test_const_merge.py \
tests/test_backend.py::TestConstMergeIntegration \
tests/test_simulator.py::TestStubProfiledMachine \
-v --tb=short

- name: Generate test visualization page
if: github.ref == 'refs/heads/main'
run: |
Expand All @@ -81,7 +95,7 @@ jobs:

- name: Upload test reports
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: test-reports
path: |
Expand All @@ -93,10 +107,23 @@ jobs:
# 模型性能测试:ONNX模型管线 + DSL用例 + CNN RISC-V编译
# ═════════════════════════════════════════════════════════════════════════
benchmark:
runs-on: self-hosted
runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'self-hosted' }}
timeout-minutes: 30
steps:
- name: Checkout pull request on isolated runner
if: github.event_name == 'pull_request'
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false

- name: Set up Python for pull request
if: github.event_name == 'pull_request'
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"

- name: Sync from local mirror (skip unstable GitHub checkout)
if: github.event_name != 'pull_request'
run: |
echo "=== CI Sync ==="
retry() {
Expand All @@ -120,12 +147,6 @@ jobs:
cd "$WORKSPACE"
echo "Fetching origin main..."
retry "git fetch origin main" git fetch origin main || exit 1
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
PR_NUMBER="${GITHUB_REF#refs/pull/}"
PR_NUMBER="${PR_NUMBER%/merge}"
echo "Fetching PR #${PR_NUMBER} merge ref..."
retry "git fetch PR #${PR_NUMBER}" git fetch origin "refs/pull/${PR_NUMBER}/merge" || exit 1
fi
echo "Checking out $GITHUB_SHA..."
if git -c advice.detachedHead=false checkout -f "$GITHUB_SHA"; then
echo "::notice::Checkout successful: $(git log -1 --format='%h %ai %s')"
Expand All @@ -138,7 +159,7 @@ jobs:
run: |
python3.12 -m pip install --upgrade pip
python3.12 -m pip install -e ".[all]"
python3.12 -m pip install markdown
python3.12 -m pip install markdown "pytest>=7,<10"

# ── 3.1 ONNX 模型管线基准测试 ──────────────────────────────────────
- name: ONNX model pipeline benchmarks
Expand Down Expand Up @@ -208,7 +229,7 @@ jobs:

# ── 上报 ──────────────────────────────────────────────────────────
- name: Upload benchmark reports
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: benchmark-reports
path: benchmark_reports/
Expand Down Expand Up @@ -253,7 +274,7 @@ jobs:
# ── 上传 Pages 产物 ──────────────────────────────────────────────
- name: Upload Pages artifact
if: github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: benchmark_reports/

Expand Down Expand Up @@ -284,4 +305,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
143 changes: 104 additions & 39 deletions benchmarks/bench_const_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,91 @@
from __future__ import annotations

import argparse
import os
import statistics
import sys
import time
from typing import Optional

from scratchv.backend.const_merge import merge_constants
BENCH_DIR = os.path.dirname(__file__)
PROJ_DIR = os.path.dirname(BENCH_DIR)
sys.path.insert(0, PROJ_DIR)

from scratchv.backend._asm_parser import parse_asm
from scratchv.backend.const_merge import merge_constants_detailed

def _gen_synthetic_asm(num_instrs: int, seed: int = 42,
lui_ratio: float = 0.3) -> str:
"""Generate synthetic assembly with lui+addi patterns.

def _gen_synthetic_asm(
num_instructions: int,
seed: int = 42,
pair_density: float = 0.3,
redundant_lui_density: float = 0.1,
) -> str:
"""Generate controlled synthetic assembly covering both optimization rules.

Parameters
----------
num_instrs:
num_instructions:
Target number of instructions.
seed:
Random seed for reproducibility.
lui_ratio:
pair_density:
Fraction of instructions that form lui+addi pairs.
redundant_lui_density:
Fraction of generated groups that contain a redundant LUI pattern.
"""
import random
random.seed(seed)

if num_instructions < 0:
raise ValueError("num_instructions must be non-negative")
if not 0.0 <= pair_density <= 1.0:
raise ValueError("pair_density must be between 0 and 1")
if not 0.0 <= redundant_lui_density <= 1.0:
raise ValueError("redundant_lui_density must be between 0 and 1")
if pair_density + redundant_lui_density > 1.0:
raise ValueError(
"pair_density + redundant_lui_density must not exceed 1",
)

rng = random.Random(seed)

lines = [".text", "synthetic_func:"]
i = 0
while i < num_instrs:
use_lui = random.random() < lui_ratio

if use_lui and i + 1 < num_instrs:
while i < num_instructions:
choice = rng.random()

if choice < redundant_lui_density and i + 2 < num_instructions:
regs = ["t0", "t1", "t2", "s0", "s1", "a0", "a1"]
reg = rng.choice(regs)
imm_hi = rng.choice([0x10000, 0x20000, 0x12345])
lines.append(f" lui {reg}, {hex(imm_hi)}")
lines.append(" add a4, a5, a6")
lines.append(f" lui {reg}, {hex(imm_hi)}")
i += 3
elif (
choice < redundant_lui_density + pair_density
and i + 1 < num_instructions
):
regs = ["t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3"]
r = random.choice(regs)
imm_hi = random.choice([0x10000, 0x20000, 0x12345, 0xABCDE, 0xFFFFF])
imm_lo = random.choice([0x000, 0x100, 0x678, 0xFFF, 0x800])
r = rng.choice(regs)
imm_hi = rng.choice([0x10000, 0x20000, 0x12345, 0xABCDE, 0xFFFFF])
imm_lo = rng.choice([0x000, 0x100, 0x678, 0xFFF, 0x800])
lines.append(f" lui {r}, {hex(imm_hi)}")
lines.append(f" addi {r}, {r}, {hex(imm_lo)}")
i += 2
else:
op = random.choice(["add", "sub", "lw", "sw", "mv", "mul", "xor",
"li", "addi", "beq", "j", "ret"])
op = rng.choice(["add", "sub", "lw", "sw", "mv", "mul", "xor",
"li", "addi", "beq", "j", "ret"])
regs = ["t0", "t1", "t2", "t3", "t4", "s0", "s1",
"a0", "a1", "a2", "a3", "sp", "ra"]
r1 = random.choice(regs)
r2 = random.choice(regs)
r3 = random.choice(regs)
r1 = rng.choice(regs)
r2 = rng.choice(regs)
r3 = rng.choice(regs)
if op == "li":
lines.append(f" {op} {r1}, {random.randint(0, 4096)}")
lines.append(f" {op} {r1}, {rng.randint(0, 4096)}")
elif op == "addi":
lines.append(f" {op} {r1}, {r2}, {random.randint(-2048, 2047)}")
lines.append(f" {op} {r1}, {r2}, {rng.randint(-2048, 2047)}")
elif op in ("lw", "sw"):
lines.append(f" {op} {r1}, {random.randint(0, 16)}(sp)")
lines.append(f" {op} {r1}, {rng.randint(0, 16)}(sp)")
elif op in ("beq", "bne", "blt", "bge"):
lines.append(f" {op} {r1}, {r2}, label_{i}")
elif op == "j":
Expand All @@ -78,24 +113,39 @@ def _gen_synthetic_asm(num_instrs: int, seed: int = 42,

def bench_merge(asm_text: str, repeats: int = 50) -> dict:
"""Benchmark the constant merge optimizer."""
if repeats < 1:
raise ValueError("repeats must be at least 1")
times = []
results = []

for _ in range(repeats):
t0 = time.perf_counter()
result, changes = merge_constants(asm_text)
result, stats = merge_constants_detailed(asm_text)
t1 = time.perf_counter()
times.append(t1 - t0)
results.append((result, changes))

changes_list = [r[1] for r in results]
input_lines = asm_text.count("\n")
output_lines = results[0][0].count("\n") if results else 0
results.append((result, stats))

changes_list = [r[1].total_changes for r in results]
first_stats = results[0][1]
parsed_input = parse_asm(asm_text)
parsed_output = parse_asm(results[0][0])
input_instructions = sum(
line.opcode is not None and not line.is_directive
for line in parsed_input
)
output_instructions = sum(
line.opcode is not None and not line.is_directive
for line in parsed_output
)

return {
"input_lines": input_lines,
"output_lines": output_lines,
"line_reduction": input_lines - output_lines,
"benchmark_type": "synthetic",
"input_instructions": input_instructions,
"output_instructions": output_instructions,
"instruction_reduction": input_instructions - output_instructions,
"candidate_pairs": first_stats.candidate_pairs,
"merged_pairs": first_stats.merged_pairs,
"redundant_lui_removed": first_stats.redundant_lui_removed,
"changes_mean": statistics.mean(changes_list),
"changes_stdev": statistics.stdev(changes_list) if len(changes_list) > 1 else 0,
"repeats": repeats,
Expand All @@ -111,35 +161,50 @@ def main():
parser = argparse.ArgumentParser(description="Constant Merge Benchmark")
parser.add_argument("--repeats", type=int, default=50,
help="Number of repeat measurements")
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--pair-density", type=float, default=0.3)
parser.add_argument("--redundant-lui-density", type=float, default=0.1)
args = parser.parse_args()

sizes = [100, 500, 1000, 2000, 5000]
print("=" * 80)
print("RISC-V Constant Load Merge Optimizer Benchmark")
print("benchmark_type=synthetic")
print("=" * 80)

print(f"\n{'Size':>8} {'Mean(ms)':>10} {'Stdev(ms)':>10} "
f"{'Changes':>8} {'InpLines':>10} {'OutLines':>10} {'Reduc':>8}")
f"{'Pairs':>8} {'RedLUI':>8} {'InpInst':>10} {'OutInst':>10}")
print("-" * 80)

for size in sizes:
asm = _gen_synthetic_asm(size, lui_ratio=0.3)
asm = _gen_synthetic_asm(
size,
seed=args.seed,
pair_density=args.pair_density,
redundant_lui_density=args.redundant_lui_density,
)
stats = bench_merge(asm, repeats=args.repeats)
print(f"{size:>8} {stats['mean_s'] * 1000:>10.3f} "
f"{stats['stdev_s'] * 1000:>10.3f} "
f"{stats['changes_mean']:>8.1f} "
f"{stats['input_lines']:>10} {stats['output_lines']:>10} "
f"{stats['line_reduction']:>8}")
f"{stats['merged_pairs']:>8} "
f"{stats['redundant_lui_removed']:>8} "
f"{stats['input_instructions']:>10} "
f"{stats['output_instructions']:>10}")

# Test different lui densities
print(f"\nLUI Density Impact (2000 instructions):")
print("-" * 60)
for ratio in [0.0, 0.1, 0.3, 0.5]:
asm = _gen_synthetic_asm(2000, lui_ratio=ratio)
asm = _gen_synthetic_asm(
2000,
seed=args.seed,
pair_density=ratio,
redundant_lui_density=args.redundant_lui_density,
)
stats = bench_merge(asm, repeats=args.repeats)
print(f" ratio={ratio:.1f} {stats['mean_s'] * 1000:.3f} ms "
f"changes: {stats['changes_mean']:.1f} "
f"reduction: {stats['line_reduction']}")
f"reduction: {stats['instruction_reduction']}")


if __name__ == "__main__":
Expand Down
Loading
Loading