diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..15ef70c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,91 @@ +name: LIRA CI + +on: + push: + branches: [master] + pull_request: + +jobs: + python-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - run: pip install ruamel.yaml pytest pytest-cov + - name: Unit tests + run: pytest python/tests/unit/ -v + - name: Unit coverage + run: pytest --cov=python/lira python/tests/unit/ --cov-report=term + - name: Integration tests + run: pytest python/tests/integration/ -v + - name: Diff integration output vs reference + run: diff python/tests/integration/integration.yaml tests/integration/reference.yaml + + ruby-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - run: pip install ruamel.yaml + - run: gem install minitest simplecov + - name: Unit tests + run: | + ruby -I ruby -I ruby/lib ruby/tests/unit/test_ir_ser_txt.rb + ruby -I ruby -I ruby/lib ruby/tests/unit/test_arch_ser_yaml.rb + ruby -I ruby -I ruby/lib ruby/tests/unit/test_ir_builder.rb + - name: Unit coverage + run: | + ruby -r simplecov -I ruby -I ruby/lib -e 'SimpleCov.start; Dir["ruby/tests/unit/*.rb"].each { |f| require_relative f }' + - name: Integration tests + run: ruby -I ruby -I ruby/lib ruby/tests/integration/test_integration.rb + - name: Diff integration output vs reference + run: diff ruby/tests/integration/integration.yaml tests/integration/reference.yaml + + rust-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - run: pip install ruamel.yaml + - name: Integration tests + run: cd rust && cargo test -p lira-tests --test integration + - name: Diff integration output vs reference + run: diff rust/tests/integration/integration.yaml tests/integration/reference.yaml + + cross-language: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + - uses: dtolnay/rust-toolchain@stable + - run: pip install ruamel.yaml pytest + - run: gem install minitest + + - name: Python writes native YAML + run: pytest tests/cross_language/test_cross.py -k "write" -v + - name: Ruby writes native YAML + run: ruby -I ruby -I ruby/lib tests/cross_language/test_cross.rb --name test_ruby_write_and_self_read + - name: Rust writes native YAML + run: cd rust && cargo test -p lira-tests --test cross_language test_rust_write_and_self_read + + - name: Python reads Ruby and Rust + run: pytest tests/cross_language/test_cross.py -k "reads" -v + - name: Ruby reads Python and Rust + run: ruby -I ruby -I ruby/lib tests/cross_language/test_cross.rb --name '/test_ruby_reads/' + - name: Rust reads Python and Ruby + run: cd rust && cargo test -p lira-tests --test cross_language test_rust_reads diff --git a/.gitignore b/.gitignore index 3624352..57f9e89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ __pycache__ target + +*coverage + +build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c4259a --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# LIRA + +LIRA is a framework that provides a data-flow Intermediate Representation. + +**Libraries:** +- [python/lira](python/lira) — Python 3 library +- [ruby/lira](ruby/lira) — Ruby library +- [rust/lira](rust/lira) — Rust library + +## Install + +- Python (pip): +```bash +pip install -e python/ +``` + +- Ruby (gem) +```bash +gem build ruby/lira.gemspec && gem install lira-*.gem +# or via Gemfile: +# gem "lira", path: "ruby" +``` +- Rust (Cargo) +```bash +cargo add --path rust/lira +``` + +## Examples + +- [Python 3 examples](python/examples) +- [Ruby examples](ruby/examples) +- [Rust examples](rust/examples) diff --git a/docs/ir_ops.md b/docs/ir_ops.md new file mode 100644 index 0000000..50ad8c8 --- /dev/null +++ b/docs/ir_ops.md @@ -0,0 +1,113 @@ +# Basic operations + +All basic LIRA operations are inherited from the standard operation from the lira/arch module. + +Below is an implementation of a standard operation in [python](../python/lira/arch.py): + +```python3 +@dataclass +class Operation(Component): + inputs: list[int] + outputs: list[int] + semantic_base: Optional[str] = None + semantic_func: Optional[str] = None + semantic_func_128: Optional[str] = None + semantic_table: Optional[str] = None +``` + +# Types + +In general, base operations work on bit-vector (BV) values. + +Note: `BV<1>` is used instead of `Bool`. + +# Name Conventions + +Operations are grouped by operand signature. +The `name` field follows the specific pattern. + +## Unary + +`BV -> BV` - `_` + +| Operation | Description | +|-----------|-------------| +| `not` | bitwise NOT | +| `neg` | two's complement negation | +| `popcnt` | population count (number of set bits) | +| `ctz` | count trailing zeros | +| `clz` | count leading zeros | +| `reverse` | reverse bit order | + +## Binary + +`BV BV -> BV` – `_` + +| Operation | Description | +|-----------|-------------| +| `add` | addition | +| `sub` | subtraction | +| `mul` | multiplication | +| `and` | bitwise AND | +| `orr` | bitwise OR | +| `xor` | bitwise XOR | +| `lsl` | logical shift left | +| `lsr` | logical shift right | +| `asr` | arithmetic shift right | +| `rem_u` | unsigned remainder (`rem(a,0)=a`) | +| `rem_s` | signed remainder (`rem(a,0)=a`) | +| `ror` | rotate right | +| `rol` | rotate left | + +## Comparison + +`BV BV -> BV<1>` - `_` + +| Operation | Description | +|-----------|-------------| +| `eq` | equality |x +| `ne` | not equal | +| `slt` | signed less than | +| `sle` | signed less or equal | +| `sgt` | signed greater than | +| `sge` | signed greater or equal | +| `ult` | unsigned less than | +| `ule` | unsigned less or equal | +| `ugt` | unsigned greater than | +| `uge` | unsigned greater or equal | +| `add_overflow` | signed addition overflow | +| `sub_overflow` | signed subtraction overflow | + +## Ternary + +`BV BV BV -> BV` - `_` + +| Operation | Description | +|-----------|-------------| +| `div_u` | unsigned division: `div(a,b,c) = b != 0 ? a/b : c` | +| `div_s` | signed division: `div(a,b,c) = b != 0 ? a/b : c` | + +## Select + +`BV<1> BV BV -> BV` - `_` + +| Operation | Description | +|-----------|-------------| +| `select` | multiplexer: `select(cond, true, false)` | + +## Cast + +`BV -> BV` - `__to_` + +| Operation | Description | +|-----------|-------------| +| `extract_low` | extract low bits (`out` <= `in`) | +| `extend_sign` | sign-extend (`out` > `in`) | +| `extend_zero` | zero-extend (`out` > `in`) | + +## Derivatives + +Some compound operations are derived from the standard set and need semantic definitions in the LIRA architecture: + +- `extract(v, start, width)` = `extract_low(lsr(v, start), width)` +- `concat(low, high)` = `orr(extend_zero(low, high_bits + low_bits), lsl(extend_zero(high, high_bits + low_bits), low_bits))` diff --git a/docs/ops_std.md b/docs/ops_std.md deleted file mode 100644 index df4248a..0000000 --- a/docs/ops_std.md +++ /dev/null @@ -1,61 +0,0 @@ -# Set of standard operations - -WIP - -Note: in lira `BV1` is used instead of `Bool` - -Note: mentioned `op_name` will be in the `Operation.semantic_base` - -## Signatures - -List of ops, grouped by signature - -`BVn -> BVn`: `_` -- `not` -- `neg` -- `popcnt` -- `ctz`, `clz` -- `reverse` (bits) - -`BVn BVn -> BVn`: `_` -- `add`, `sub` -- `and`, `orr`, `xor` -- `mul` -- `rem_u`, `rem_s`: `rem(a,0)=a` -- `lsl`, `lsr`, `asr`: on overflow: `shift(x,y+1)=shift_1(shift(x,y))` -- `ror`, `rol` (?) - -`BVn BVn -> BV1`: `_` -- `eq`, `ne` -- `(u/s)(l/g)(t/e)` (`slt`, etc) -- `(add/sub)_(u/s)_overflow` - - maybe also for `mul`? - - maybe add saturating operations to std? - -`BVn BVn BVn -> BVn`: `_` -- `div_u`, `div_s`: `div(a,b,c) = b != 0 ? a/b : c` - - motivation for ternary: total definition - no UB - -`BV1 BVn BVn -> BVn`: `_` -- `ite`/`select` - -`BVin -> BVout`: `__` (order?) -- `extract_low` -- `extend_zero`, `extend_sign` - -`BVin BVin -> BVout`: `__` (order?) -- `extract`: defined as: `extract(v,s) = extract_low(lsr(v,s))` - -`BVa BVb -> BVa+b`: `__` (naming?) -- `concat`: defined as: `concat(x,y)=orr(extend_zero(y),lsl(extend_zero(x),b))` - -## Derivability - -Some standard operations have to have semantic in lira arch - -`extract`, `concat`, `clz`, predicates - -## Encoding - -Encoding will likely be using same approach: `a |= b << c`. -Maybe it's better to make this operation standard one. diff --git a/python/__init__.py b/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/example/integration_test.py b/python/example/integration_test.py deleted file mode 100644 index 7af0d19..0000000 --- a/python/example/integration_test.py +++ /dev/null @@ -1,169 +0,0 @@ -assert __name__ == '__main__' -import sys -from pathlib import Path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from lira.arch_ser_txt import * -from lira.ir_ser_txt import * -from lira.ir import * -from lira.arch import * - - -assert len(sys.argv) == 2 -arch_dir = Path(sys.argv[1]) - -rf = RegisterFile('X', [], Shape(32, None), [f'x{i}' for i in range(32)]) -ld32 = EnvironmentFunction('ld32', ['mem.read'], [32], [32]) -st32 = EnvironmentFunction('st32', ['mem.write'], [32, 32], []) -pc_read = EnvironmentFunction('pc_read', ['pc.read'], [], [32]) -pc_write = EnvironmentFunction('pc_write', ['pc.write'], [32], []) - -ops = [] -for name, inputs, output, base in [ - ('add_32', [32, 32], 32, 'add'), - ('lsl_32', [32, 32], 32, 'lsl'), - ('lsr_32', [32, 32], 32, 'lsr'), - ('asr_32', [32, 32], 32, 'asr'), - ('slt_32', [32, 32], 1, 'slt'), - ('extract_low_5_32', [32], 5, 'extract_low'), -]: - ops.append(Operation(name, [], inputs, [output], base)) - -def sem(code: list[str]): - return StatementSeq([deserialize_statement(stmt) for stmt in code]) - -snippets = [] -def add_snippet(name: str, code: list[str]): - snippets.append(Snippet(name, sem(code))) - -add_snippet('op_extend_sign_inner_32', [ - '1 32 input = input 0', - '1 32 width = input 1', - '1 32 c32 = const 32', - '1 32 delta = sub_32 c32 width', - '1 32 temp = lsl_32 input delta', - '1 32 r = asr_32 temp delta', - '1 = output r', -]) -ops.append(Operation('extend_sign_inner_32', [], [32, 32], [32], None, 'op_extend_sign_inner_32')) - -add_snippet('op_extract_inner_32', [ - '1 32 input = input 0', - '1 32 lsb = input 1', - '1 32 width = input 2', - '1 32 new_lsb = input 3', - '1 32 c32 = const 32', - '1 32 t1 = sub_32 c32 lsb', - '1 32 shift_l = sub_32 t1 width', - '1 32 temp = lsl_32 input shift_l', # input << (32 - lsb - width) - '1 32 shift_r = sub_32 c32 width', - '1 32 temp2 = lsr_32 temp shift_r', # (input >> lsb) & ((1 << width) - 1) - '1 = output r', -]) -# eii(input, lsb, width, new_lsb) extracts width bits from input at offset lsb -ops.append(Operation('extract_inner_32', [], [32, 32, 32], [32], None, 'op_extract_inner_32')) -add_snippet('op_orr_shifted_32', [ - '1 32 data = input 0', - '1 32 lsb = input 1', - '1 32 value = input 2', - '1 32 insert = lsl_32 value lsb', - '1 32 r = orr_32 data insert', - '1 = output r', -]) -ops.append(Operation('orr_shifted_32', [], [32, 32, 32], [32], None, 'op_orr_shifted_32')) - - -def add_snippet_extract(name: str, input: int, lsb: int, output: int): - add_snippet(name, [ - f'1 {input} enc = input 0', - f'1 {input} shift = const {lsb}', - f'1 {input} shifted = op lsr_{input} enc shift', - f'1 {output} r = op extract_low_{output}_{input} shifted', - '1 = output r', - ]) - -for i, lsb in [(1, 15), (2, 20)]: - add_snippet_extract(f'decode_b_rs{i}', 32, lsb, 5) -# Yes, IR designed for (vector) instruction analysis doesn't look great -# in the context of instruction encoding/decoding, that's true. -add_snippet('decode_b_imm', [ - '1 32 enc = input 0', - '1 32 c1 = const 1', - '1 32 c4 = const 4', - '1 32 c5 = const 5', - '1 32 c6 = const 6', - '1 32 c7 = const 7', - '1 32 c8 = const 8', - '1 32 c11 = const 11', - '1 32 c12 = const 12', - '1 32 c13 = const 13', - '1 32 c25 = const 25', - '1 32 c31 = const 31', - '1 32 t1 = op extract_inner_32 enc c31 c1', - '1 32 t2 = op extract_inner_32 enc c25 c6', - '1 32 t3 = op extract_inner_32 enc c8 c4', - '1 32 t4 = op extract_inner_32 enc c7 c1', - '1 32 t5 = const 0', - '1 32 t6 = op orr_shifted_32 t5 t1 c12', - '1 32 t7 = op orr_shifted_32 t5 t1 c11', - '1 32 t8 = op orr_shifted_32 t5 t1 c5', - '1 32 t9 = op orr_shifted_32 t5 t1 c1', - '1 32 imm_sext = op extend_sign_inner_32 t9 c13', - '1 = output imm_sext', -]) -add_snippet('encode_b', [ - '1 5 rs1 = input 0', - '1 5 rs2 = input 2', - '1 32 imm = input 2', - '1 32 base = dyn_const enc_base', - '1 32 c15 = const 15', - '1 32 c20 = const 20', - '1 32 t1 = op orr_shifted base rs1 c15', - '1 32 t2 = op orr_shifted t1 rs2 c20', - '1 32 r = todo todo t2 imm', # that's a pain to write by hand.. - '1 = output r', -]) - -def enc_b(funct3: int, opcode: int): - return InstructionEncoding(32, (funct3 << 12) + opcode, - ['decode_b_rs1', 'decode_b_rs2', 'decode_b_imm'], 'encode_b', '', '' - ) - -instrs = [] -instrs.append(Instruction('blt', ['kind.branch.cond'], - [5, 5, 32], ['x1', 'x2', 'offset'], enc_b(0b100, 0b1100011), sem([ - '1 5 x1 = input 0', - '1 5 x2 = input 1', - '1 5 offset = input 2', - '1 32 v1 = read X x1', - '1 32 v2 = read X x2', - '1 1 cond = op slt_32 v1 v2', - '1 32 base = env pc_read', - '1 32 dest = op add_32 base offset', - '1 = cond_env pc_write cond dest', - ]) -)) - -arch = Arch( - name='test_arch', - attributes=['attr.1', 'attr.2'], - register_files=[rf], - system_registers=[], - environment_functions=[ld32, st32], - tables_int=[], - operations=ops, - snippets=snippets, - instructions=instrs, -) - -write_arch(arch, arch_dir) -arch2 = read_arch(arch_dir) - -assert arch.register_files == arch2.register_files -assert arch.system_registers == arch2.system_registers -assert arch.environment_functions == arch2.environment_functions -assert arch.tables_int == arch2.tables_int -assert arch.operations == arch2.operations -assert arch.snippets == arch2.snippets -assert arch.instructions == arch2.instructions -assert arch == arch2 diff --git a/python/examples/README.md b/python/examples/README.md new file mode 100644 index 0000000..d38f487 --- /dev/null +++ b/python/examples/README.md @@ -0,0 +1,16 @@ +# LIRA Python Usage Example + +* RISC-V-like test architecture (register file, environment functions, operations, snippets, and a `blt` instruction) +* Serializes it to YAML, deserializes it back +* Asserts round-trip equality. + +## Run + +```bash +python3 example.py --output /path/to/output.yaml +``` + +## Requirements +```bash +pip install ruamel.yaml +``` diff --git a/python/examples/example.py b/python/examples/example.py new file mode 100644 index 0000000..7d0d6b0 --- /dev/null +++ b/python/examples/example.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +import sys +from pathlib import Path + +from python.lira.ir import Shape +from python.lira.arch import Register, RegisterFile, EnvironmentFunction, InstructionEncoding, Operation +from python.lira.ir_builder import ArchBuilder, SnippetBuilder, InstructionBuilder + +from python.lira import arch_ser_yaml + +def build_test_arch() -> ArchBuilder: + registers = [Register(f"x{i}") for i in range(32)] + rf = RegisterFile("X", [], Shape(32, None), registers) + + ld32 = EnvironmentFunction("ld32", ["mem.read"], [32], [32]) + st32 = EnvironmentFunction("st32", ["mem.write"], [32, 32], []) + pc_read = EnvironmentFunction("pc_read", ["pc.read"], [], [32]) + pc_write = EnvironmentFunction("pc_write", ["pc.write"], [32], []) + + # ------------------------------------------------------------------ + # op_extend_sign_inner_32 + # ------------------------------------------------------------------ + snip = SnippetBuilder("op_extend_sign_inner_32") + input_val = snip.input(0, 32) + width_val = snip.input(1, 32) + c32 = snip.const(32) + delta = snip.sub(c32, width_val) + temp = snip.lsl(input_val, delta) + r = snip.asr(temp, delta) + snip.output(r, 0) + extend_sign_inner = snip.build() + + op_extend_sign = Operation( + "extend_sign_inner_32", + [], + [32, 32], + [32], + semantic_base=None, + semantic_func="op_extend_sign_inner_32", + ) + + # ------------------------------------------------------------------ + # op_extract_inner_32 + # ------------------------------------------------------------------ + snip = SnippetBuilder("op_extract_inner_32") + inp = snip.input(0, 32) + lsb = snip.input(1, 32) + width = snip.input(2, 32) + c32 = snip.const(32) + t1 = snip.sub(c32, lsb) + shift_l = snip.sub(t1, width) + temp = snip.lsl(inp, shift_l) + shift_r = snip.sub(c32, width) + temp2 = snip.lsr(temp, shift_r) + snip.output(temp2, 0) + extract_inner_snip = snip.build() + op_extract_inner = Operation( + "extract_inner_32", + [], + [32, 32, 32], + [32], + semantic_base=None, + semantic_func="op_extract_inner_32", + ) + + # ------------------------------------------------------------------ + # op_orr_shifted_32 + # ------------------------------------------------------------------ + snip = SnippetBuilder("op_orr_shifted_32") + data = snip.input(0, 32) + lsb = snip.input(1, 32) + value = snip.input(2, 32) + insert = snip.lsl(value, lsb) + r = snip.orr(data, insert) + snip.output(r, 0) + orr_shifted_snip = snip.build() + op_orr_shifted = Operation( + "orr_shifted_32", + [], + [32, 32, 32], + [32], + semantic_base=None, + semantic_func="op_orr_shifted_32", + ) + + # ------------------------------------------------------------------ + # decode_b_rs1, decode_b_rs2 + # ------------------------------------------------------------------ + def make_decode_extract(name: str, shift: int): + snip = SnippetBuilder(name) + enc = snip.input(0, 32) + shift_const = snip.const(shift) + shifted = snip.lsr(enc, shift_const) + r = snip.extract_low(shifted, 5) + snip.output(r, 0) + return snip.build() + + decode_rs1 = make_decode_extract("decode_b_rs1", 15) + decode_rs2 = make_decode_extract("decode_b_rs2", 20) + + # ------------------------------------------------------------------ + # decode_b_imm + # ------------------------------------------------------------------ + snip = SnippetBuilder("decode_b_imm") + enc = snip.input(0, 32) + c1 = snip.const(1) + c4 = snip.const(4) + c5 = snip.const(5) + c6 = snip.const(6) + c7 = snip.const(7) + c8 = snip.const(8) + c11 = snip.const(11) + c12 = snip.const(12) + c13 = snip.const(13) + c25 = snip.const(25) + c31 = snip.const(31) + + t1 = snip.op(op_extract_inner, [enc, c31, c1]) + t2 = snip.op(op_extract_inner, [enc, c25, c6]) + t3 = snip.op(op_extract_inner, [enc, c8, c4]) + t4 = snip.op(op_extract_inner, [enc, c7, c1]) + t5 = snip.const(0) + t6 = snip.op(op_orr_shifted, [t5, t1, c12]) + t7 = snip.op(op_orr_shifted, [t5, t1, c11]) + t8 = snip.op(op_orr_shifted, [t5, t1, c5]) + t9 = snip.op(op_orr_shifted, [t5, t1, c1]) + imm_sext = snip.op(op_extend_sign, [t9, c13]) + snip.output(imm_sext, 0) + decode_imm = snip.build() + + # ------------------------------------------------------------------ + # encode_b + # ------------------------------------------------------------------ + snip = SnippetBuilder("encode_b") + rs1 = snip.input(0, 5) + rs2 = snip.input(1, 5) + imm = snip.input(2, 32) + base = snip.dyn_const("enc_base", 32) + c15 = snip.const(15) + c20 = snip.const(20) + t1 = snip.op(op_orr_shifted, [base, rs1, c15]) + t2 = snip.op(op_orr_shifted, [t1, rs2, c20]) + r = snip.orr(t2, imm) + snip.output(r, 0) + encode_b_snip = snip.build() + + # ------------------------------------------------------------------ + # blt + # ------------------------------------------------------------------ + enc_blt = InstructionEncoding( + 32, + (0b100 << 12) + 0b1100011, + 0, + ["decode_b_rs1", "decode_b_rs2", "decode_b_imm"], + "encode_b", + "", + "", + ) + + instr_builder = InstructionBuilder( + "blt", [5, 5, 32], ["x1", "x2", "offset"], enc_blt + ) + x1 = instr_builder.add_input_operand(0, 5) + x2 = instr_builder.add_input_operand(1, 5) + offset = instr_builder.add_input_operand(2, 32) + v1 = instr_builder.read(rf, x1) + v2 = instr_builder.read(rf, x2) + cond = instr_builder.slt(v1, v2) + base = instr_builder.env(pc_read, [])[0] + dest = instr_builder.add(base, offset) + instr_builder.cond_env(pc_write, cond, [dest], []) + blt_instr = instr_builder.build() + + arch_builder = ( + ArchBuilder("test_arch", ["attr.1", "attr.2"]) + .add_register_file(rf) + .add_env_func(ld32) + .add_env_func(st32) + .add_env_func(pc_read) + .add_env_func(pc_write) + .add_operation(op_extend_sign) + .add_operation(op_extract_inner) + .add_operation(op_orr_shifted) + .add_snippet(extend_sign_inner) + .add_snippet(extract_inner_snip) + .add_snippet(orr_shifted_snip) + .add_snippet(decode_rs1) + .add_snippet(decode_rs2) + .add_snippet(decode_imm) + .add_snippet(encode_b_snip) + .add_instruction(blt_instr) + ) + return arch_builder + + +def main(): + parser = argparse.ArgumentParser( + description="Test LIRA YAML serialization" + ) + parser.add_argument( + "--output", + type=str, + default="lira.yaml", + help="Output YAML file path (default: lira.yaml)" + ) + args = parser.parse_args() + + output_path = Path(args.output) + arch = build_test_arch().build() + + raw_path = output_path.with_suffix('.raw.yaml') + arch_ser_yaml.write_arch(arch, raw_path) + + canonicalize = Path(__file__).parent.parent.parent / 'tools' / 'yaml_canonicalize.py' + subprocess.run([sys.executable, str(canonicalize), str(raw_path), str(output_path)], check=True) + raw_path.unlink() + + arch2 = arch_ser_yaml.read_arch(output_path) + + assert arch == arch2, "Integration test failed" + print("Integration test passed") + + +if __name__ == "__main__": + main() diff --git a/python/lira/__init__.py b/python/lira/__init__.py index e69de29..ea7988d 100644 --- a/python/lira/__init__.py +++ b/python/lira/__init__.py @@ -0,0 +1,10 @@ +from .ir import Shape, Statement, StatementSeq +from .ir_ser_txt import serialize_statement_seq, deserialize_statement_seq +from .ir_builder import SeqBuilder, SnippetBuilder, InstructionBuilder, ArchBuilder, Value +from .arch import ( + Arch, Register, RegisterFile, EnvironmentFunction, Operation, + Instruction, InstructionEncoding, Snippet, + SystemRegister, SystemRegisterField, TableInt, +) +from .arch_ser_yaml import write_arch, read_arch +from .ir_ops import BaseOp diff --git a/python/lira/arch.py b/python/lira/arch.py index 63be010..a36db61 100644 --- a/python/lira/arch.py +++ b/python/lira/arch.py @@ -1,7 +1,7 @@ from typing import Optional from dataclasses import dataclass, field -from lira.ir import * +from .ir import * @dataclass class Component: @@ -24,13 +24,32 @@ class Operation(Component): semantic_func_128: Optional[str] = None # Snippet semantic_table: Optional[str] = None # TableInt + def __eq__(self, other): + if not isinstance(other, Operation): + return NotImplemented + return (self.name, self.attributes, self.inputs, self.outputs, + self.semantic_base, self.semantic_func, + self.semantic_func_128, self.semantic_table) == \ + (other.name, other.attributes, other.inputs, other.outputs, + other.semantic_base, other.semantic_func, + other.semantic_func_128, other.semantic_table) + + +@dataclass +class Register(Component): + def __init__(self, name, attributes: list[str] = []): + super().__init__(name=name, attributes=attributes) + @dataclass class RegisterFile(Component): reg_size: Shape - reg_names: list[str] + regs: list[Register] + + def reg_names(self) -> list[str]: + return [r.name for r in self.regs] def regs_num(self) -> int: - return len(self.reg_names) + return len(self.regs) @dataclass class EnvironmentFunction(Component): @@ -55,6 +74,7 @@ class InstructionEncoding: encoded_size: int # Used to reuse same encode/constraint_decode for multiple instructions const_encoding_part: int + const_mask: int # `[encoding_size -> operand_size]` decode: list[str] # Snippet # `[operand_size] -> encoding_size` diff --git a/python/lira/arch_ser_txt.py b/python/lira/arch_ser_txt.py deleted file mode 100644 index 60067dc..0000000 --- a/python/lira/arch_ser_txt.py +++ /dev/null @@ -1,130 +0,0 @@ -from lira.ir import * -from lira.arch import * -from lira.ir_ser_txt import * - -from dataclasses import is_dataclass, fields, asdict -from typing import Any, Type, get_origin, get_args -from pathlib import Path -import json -import os -import shutil - -def to_serializable(obj: Any) -> Any: - '''Convert a dataclass or nested structure into JSON‑serializable dicts/lists.''' - if is_dataclass(obj): - return {f.name: to_serializable(getattr(obj, f.name)) for f in fields(obj)} - if isinstance(obj, list): - return [to_serializable(item) for item in obj] - return obj - -def from_serializable(cls: Type, data: Any) -> Any: - '''Reconstruct a dataclass, list of dataclasses, or primitive from serialized data.''' - origin = get_origin(cls) - if is_dataclass(cls): - types = {f.name: f.type for f in fields(cls)} - return cls(**{name: from_serializable(types[name], value) for name, value in data.items()}) - if origin is list: - return [from_serializable(get_args(cls)[0], item) for item in data] - return data - -def read_arch(folder_path: Path) -> Arch: - def load_json(suffix: str): - with open(folder_path / suffix, 'r') as f: - return json.load(f) - - arch_info = load_json('arch.json') - name = arch_info['name'] - attributes = arch_info['attributes'] - - def load_json_as(cls, suffix: str): - return from_serializable(cls, load_json(suffix)) - - register_files = load_json_as(list[RegisterFile], 'register_files.json') - system_registers = load_json_as(list[SystemRegister], 'system_registers.json') - environment_functions = load_json_as(list[EnvironmentFunction], 'environment_functions.json') - tables_int = load_json_as(list[TableInt], 'tables_int.json') - - index = load_json('index.json') - op_names = index['operations'] - snippet_names = index['snippets'] - instr_names = index['instructions'] - - def load_lira(suffix: str): - with open(folder_path / suffix) as f: - return deserialize_statement_seq(f.read()) - - def load_operation(name: str): - return load_json_as(Operation, f'operations/{name}.json') - def load_snippet(name: str): - return Snippet(name, load_lira(f'snippets/{name}.lira')) - def load_instruction(name: str): - instr = load_json(f'instructions/{name}.json') - instr['semantic'] = {'stmts':[]} - instr = from_serializable(Instruction, instr) - instr.semantic = load_lira(f'instructions/{name}.lira') - return instr - - operations = [load_operation(name) for name in op_names] - snippets = [load_snippet(name) for name in snippet_names] - instructions = [load_instruction(name) for name in instr_names] - - return Arch( - name=name, - attributes=attributes, - register_files=register_files, - system_registers=system_registers, - environment_functions=environment_functions, - tables_int=tables_int, - operations=operations, - snippets=snippets, - instructions=instructions, - ) - -def write_arch(arch: Arch, folder_path: Path) -> None: - '''Write an entire architecture to a folder, overwriting any existing content.''' - if folder_path.exists(): - shutil.rmtree(folder_path) - folder_path.mkdir(parents=True) - - def write_json(data, suffix: str) -> None: - with open(folder_path / suffix, 'w') as f: - json.dump(data, f) - - write_json({'name': arch.name, 'attributes': arch.attributes}, 'arch.json') - - def write_component_list(objs, suffix: str) -> None: - write_json([to_serializable(obj) for obj in objs], suffix) - - write_component_list(arch.register_files, 'register_files.json') - write_component_list(arch.system_registers, 'system_registers.json') - write_component_list(arch.environment_functions, 'environment_functions.json') - write_component_list(arch.tables_int, 'tables_int.json') - - index = { - 'operations': [op.name for op in arch.operations], - 'snippets': [s.name for s in arch.snippets], - 'instructions': [i.name for i in arch.instructions], - } - write_json(index, 'index.json') - - ops_dir = folder_path / 'operations' - ops_dir.mkdir() - for op in arch.operations: - with open(ops_dir / f'{op.name}.json', 'w') as f: - json.dump(to_serializable(op), f, indent=2) - - snippets_dir = folder_path / 'snippets' - snippets_dir.mkdir() - for snip in arch.snippets: - with open(snippets_dir / f'{snip.name}.lira', 'w') as f: - f.write(serialize_statement_seq(snip.seq)) - - instr_dir = folder_path / 'instructions' - instr_dir.mkdir() - for instr in arch.instructions: - instr_dict = to_serializable(instr) - instr_dict.pop('semantic', None) - with open(instr_dir / f'{instr.name}.json', 'w') as f: - json.dump(instr_dict, f, indent=2) - with open(instr_dir / f'{instr.name}.lira', 'w') as f: - f.write(serialize_statement_seq(instr.semantic)) diff --git a/python/lira/arch_ser_yaml.py b/python/lira/arch_ser_yaml.py new file mode 100644 index 0000000..6151ed7 --- /dev/null +++ b/python/lira/arch_ser_yaml.py @@ -0,0 +1,57 @@ +from pathlib import Path +from typing import Any, Type, get_origin, get_args +from dataclasses import is_dataclass, fields + +from ruamel.yaml import YAML +from ruamel.yaml.scalarstring import LiteralScalarString + +from .arch import * +from .ir import StatementSeq +from .ir_ser_txt import serialize_statement_seq, deserialize_statement_seq + +def to_serializable(obj: Any) -> Any: + if isinstance(obj, StatementSeq): + return LiteralScalarString(serialize_statement_seq(obj)) + if is_dataclass(obj): + d = {} + for f in fields(obj): + val = getattr(obj, f.name) + d[f.name] = to_serializable(val) + return d + if isinstance(obj, list): + return [to_serializable(item) for item in obj] + return obj + + +def from_serializable(cls: Type, data: Any) -> Any: + origin = get_origin(cls) + if is_dataclass(cls): + types = {f.name: f.type for f in fields(cls)} + kwargs = {} + for name, value in data.items(): + if types[name] is StatementSeq and isinstance(value, str): + kwargs[name] = deserialize_statement_seq(value) + else: + kwargs[name] = from_serializable(types[name], value) + return cls(**kwargs) + if origin is list: + item_cls = get_args(cls)[0] + return [from_serializable(item_cls, item) for item in data] + return data + + +def write_arch(arch: Arch, filepath: Path) -> None: + yaml = YAML() + yaml.default_flow_style = False + yaml.indent(mapping=2, sequence=4, offset=2) + + data = to_serializable(arch) + with open(filepath, 'w', encoding='utf-8') as f: + yaml.dump(data, f) + + +def read_arch(filepath: Path) -> Arch: + yaml = YAML() + with open(filepath, 'r', encoding='utf-8') as f: + data = yaml.load(f) + return from_serializable(Arch, data) diff --git a/python/lira/arch_utils.py b/python/lira/arch_utils.py index bb13235..61d89b0 100644 --- a/python/lira/arch_utils.py +++ b/python/lira/arch_utils.py @@ -1,4 +1,4 @@ -from lira.arch import * +from .arch import * from typing import Dict diff --git a/python/lira/ir.py b/python/lira/ir.py index de1dd04..1f82e62 100644 --- a/python/lira/ir.py +++ b/python/lira/ir.py @@ -24,4 +24,4 @@ def input(self, id: int, ss: 'StatementSeq') -> 'Statement': @dataclass class StatementSeq: - stmts: [Statement] + stmts: list[Statement] diff --git a/python/lira/ir_builder.py b/python/lira/ir_builder.py new file mode 100644 index 0000000..81e05da --- /dev/null +++ b/python/lira/ir_builder.py @@ -0,0 +1,616 @@ +# lira/builder.py +from typing import List, Optional, Dict, Tuple +from .ir import * +from .arch import * +from .ir_ops import * + + +class Value: + def __init__(self, name: str, width: int = 32): + self.name = name + self.width = width + self.shape = Shape(1, None) + + def __str__(self) -> str: + return self.name + + def __repr__(self) -> str: + return f"Value({self.name}, {self.width})" + + +class SeqBuilder: + def __init__(self): + self.stmts: List[Statement] = [] + self._temp_counter = 0 + + def _new_temp(self, width: int = 32) -> Value: + self._temp_counter += 1 + return Value(f"_t{self._temp_counter}", width) + + def _emit_op(self, op: Operation, inputs: List[str], out_bits: int) -> Value: + out = self._new_temp(out_bits) + self.add_op(op, inputs, [out.name]) + return out + + def check_width_match(self, a: Value, b: Value): + if a.width != b.width: + raise TypeError(f"width mismatch: {a.width} != {b.width}") + + # ------------------------------------------------------------------ + # NOTE: Building python/lira/ir_ops.py objects + # ------------------------------------------------------------------ + def add(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Add(a.width), [a.name, b.name], a.width) + + def sub(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Sub(a.width), [a.name, b.name], a.width) + + def mul(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Mul(a.width), [a.name, b.name], a.width) + + def and_(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(And(a.width), [a.name, b.name], a.width) + + def orr(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Orr(a.width), [a.name, b.name], a.width) + + def xor(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Xor(a.width), [a.name, b.name], a.width) + + def lsl(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Lsl(a.width), [a.name, b.name], a.width) + + def lsr(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Lsr(a.width), [a.name, b.name], a.width) + + def asr(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Asr(a.width), [a.name, b.name], a.width) + + def slt(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Slt(a.width), [a.name, b.name], 1) + + def sle(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Sle(a.width), [a.name, b.name], 1) + + def sgt(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Sgt(a.width), [a.name, b.name], 1) + + def sge(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Sge(a.width), [a.name, b.name], 1) + + def ult(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Ult(a.width), [a.name, b.name], 1) + + def ule(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Ule(a.width), [a.name, b.name], 1) + + def ugt(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Ugt(a.width), [a.name, b.name], 1) + + def uge(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Uge(a.width), [a.name, b.name], 1) + + def eq(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Eq(a.width), [a.name, b.name], 1) + + def ne(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Ne(a.width), [a.name, b.name], 1) + + def rem_u(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(RemU(a.width), [a.name, b.name], a.width) + + def rem_s(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(RemS(a.width), [a.name, b.name], a.width) + + def ror(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Ror(a.width), [a.name, b.name], a.width) + + def rol(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(Rol(a.width), [a.name, b.name], a.width) + + def add_overflow(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(AddOverflow(a.width), [a.name, b.name], 1) + + def sub_overflow(self, a: Value, b: Value) -> Value: + self.check_width_match(a, b) + return self._emit_op(SubOverflow(a.width), [a.name, b.name], 1) + + def not_(self, a: Value) -> Value: + return self._emit_op(Not(a.width), [a.name], a.width) + + def neg(self, a: Value) -> Value: + return self._emit_op(Neg(a.width), [a.name], a.width) + + def popcnt(self, a: Value) -> Value: + return self._emit_op(Popcnt(a.width), [a.name], a.width) + + def ctz(self, a: Value) -> Value: + return self._emit_op(Ctz(a.width), [a.name], a.width) + + def clz(self, a: Value) -> Value: + return self._emit_op(Clz(a.width), [a.name], a.width) + + def reverse(self, a: Value) -> Value: + return self._emit_op(Reverse(a.width), [a.name], a.width) + + def extend_sign(self, a: Value, to_width: int) -> Value: + if a.width >= to_width: + raise ValueError( + f"extend_sign: input width {a.width} >= output width {to_width}" + ) + return self._emit_op(ExtendSign(a.width, to_width), [a.name], to_width) + + def extend_zero(self, a: Value, to_width: int) -> Value: + if a.width >= to_width: + raise ValueError( + f"extend_zero: input width {a.width} >= output width {to_width}" + ) + return self._emit_op(ExtendZero(a.width, to_width), [a.name], to_width) + + def extract_low(self, a: Value, out_width: int) -> Value: + if out_width > a.width: + raise ValueError( + f"extract_low: output width {out_width} > input width {a.width}" + ) + return self._emit_op(ExtractLow(a.width, out_width), [a.name], out_width) + + def div_u(self, a: Value, b: Value, default: Value) -> Value: + self.check_width_match(a, b) + if a.width != default.width: + raise TypeError("div_u: default width mismatch") + return self._emit_op(DivU(a.width), [a.name, b.name, default.name], a.width) + + def div_s(self, a: Value, b: Value, default: Value) -> Value: + self.check_width_match(a, b) + if a.width != default.width: + raise TypeError("div_s: default width mismatch") + return self._emit_op(DivS(a.width), [a.name, b.name, default.name], a.width) + + def select(self, cond: Value, true_val: Value, false_val: Value) -> Value: + if cond.width != 1: + raise TypeError("select: condition must be 1-bit") + if true_val.width != false_val.width: + raise TypeError("select: true and false branch widths mismatch") + return self._emit_op( + Select(true_val.width), + [cond.name, true_val.name, false_val.name], + true_val.width, + ) + + # ------------------------------------------------------------------ + # NOTE: Building python/lira/ir_std.py objects + # ------------------------------------------------------------------ + def read( + self, rf: RegisterFile, rsi: Value, shape: Shape = Shape(1, None) + ) -> Value: + width = rf.reg_size.lanes_base + out = self._new_temp(width) + stmt = Statement(shape, [out.name], [width], "read", rf.name, [rsi.name]) + self.stmts.append(stmt) + return out + + def write( + self, rf: RegisterFile, rsi: Value, value: Value, shape: Shape = Shape(1, None) + ): + stmt = Statement(shape, [], [], "write", rf.name, [rsi.name, value.name]) + self.stmts.append(stmt) + + def const(self, value: int, width: int = 32) -> Value: + out = self._new_temp(width) + stmt = Statement(Shape(1, None), [out.name], [width], "const", str(value), []) + self.stmts.append(stmt) + return out + + def dyn_const(self, name: str, width: int = 32) -> Value: + out = self._new_temp(width) + stmt = Statement(Shape(1, None), [out.name], [width], "dyn_const", name, []) + self.stmts.append(stmt) + return out + + def env(self, env_func: EnvironmentFunction, inputs: List[Value]) -> List[Value]: + outputs = [self._new_temp(w) for w in env_func.outputs] + stmt = Statement( + Shape(1, None), + [o.name for o in outputs], + env_func.outputs, + "env", + env_func.name, + [v.name for v in inputs], + ) + self.stmts.append(stmt) + return outputs + + def cond_env( + self, + env_func: EnvironmentFunction, + cond: Value, + inputs: List[Value], + on_false: List[Value], + ) -> List[Value]: + outputs = [self._new_temp(w) for w in env_func.outputs] + all_inputs = [cond.name] + [v.name for v in inputs] + [v.name for v in on_false] + stmt = Statement( + Shape(1, None), + [o.name for o in outputs], + env_func.outputs, + "cond_env", + env_func.name, + all_inputs, + ) + self.stmts.append(stmt) + return outputs + + def input(self, idx: int, width: int = 32) -> Value: + out = self._new_temp(width) + stmt = Statement(Shape(1, None), [out.name], [width], "input", str(idx), []) + self.stmts.append(stmt) + return out + + def output(self, value: Value, idx: int): + stmt = Statement(Shape(1, None), [], [], "output", str(idx), [value.name]) + self.stmts.append(stmt) + + # ------------------------------------------------------------------ + # NOTE: Others + # ------------------------------------------------------------------ + def add_op( + self, + op: Operation, + inputs: List[str], + outputs: List[str], + shape: Shape = Shape(1, None), + ): + out_types = op.outputs + stmt = Statement(shape, outputs, out_types, "op", op.name, inputs) + self.stmts.append(stmt) + + def op(self, operation: Operation, inputs: List[Value]) -> Value: + if len(operation.outputs) != 1: + raise ValueError( + f"Operation {operation.name} has {len(operation.outputs)} outputs, use op_multi" + ) + return self._emit_op(operation, [v.name for v in inputs], operation.outputs[0]) + + def op_multi(self, operation: Operation, inputs: List[Value]) -> List[Value]: + outputs = [self._new_temp(w) for w in operation.outputs] + self.add_op(operation, [v.name for v in inputs], [o.name for o in outputs]) + return outputs + + def build(self) -> StatementSeq: + return StatementSeq(self.stmts) + + def __iadd__(self, other: "SeqBuilder") -> "SeqBuilder": + self.stmts.extend(other.stmts) + self._temp_counter = max(self._temp_counter, other._temp_counter) + return self + + +class BaseBuilder: + def __init__(self): + self.seq = SeqBuilder() + self._op_cache: Dict[str, Operation] = {} + + def _cache_op(self, op_class, *args, **kwargs) -> Operation: + op = op_class(*args, **kwargs) + if op.name not in self._op_cache: + self._op_cache[op.name] = op + return self._op_cache[op.name] + + @property + def operations_map(self) -> Dict[str, Operation]: + return {op.name: op for op in self._op_cache.values()} + + # ------------------------------------------------------------------ + # NOTE: Building python/lira/ir_ops.py objects + # ------------------------------------------------------------------ + def add(self, a: Value, b: Value) -> Value: + self._cache_op(Add, a.width) + return self.seq.add(a, b) + + def sub(self, a: Value, b: Value) -> Value: + self._cache_op(Sub, a.width) + return self.seq.sub(a, b) + + def mul(self, a: Value, b: Value) -> Value: + self._cache_op(Mul, a.width) + return self.seq.mul(a, b) + + def and_(self, a: Value, b: Value) -> Value: + self._cache_op(And, a.width) + return self.seq.and_(a, b) + + def orr(self, a: Value, b: Value) -> Value: + self._cache_op(Orr, a.width) + return self.seq.orr(a, b) + + def xor(self, a: Value, b: Value) -> Value: + self._cache_op(Xor, a.width) + return self.seq.xor(a, b) + + def lsl(self, a: Value, b: Value) -> Value: + self._cache_op(Lsl, a.width) + return self.seq.lsl(a, b) + + def lsr(self, a: Value, b: Value) -> Value: + self._cache_op(Lsr, a.width) + return self.seq.lsr(a, b) + + def asr(self, a: Value, b: Value) -> Value: + self._cache_op(Asr, a.width) + return self.seq.asr(a, b) + + def slt(self, a: Value, b: Value) -> Value: + self._cache_op(Slt, a.width) + return self.seq.slt(a, b) + + def sle(self, a: Value, b: Value) -> Value: + self._cache_op(Sle, a.width) + return self.seq.sle(a, b) + + def sgt(self, a: Value, b: Value) -> Value: + self._cache_op(Sgt, a.width) + return self.seq.sgt(a, b) + + def sge(self, a: Value, b: Value) -> Value: + self._cache_op(Sge, a.width) + return self.seq.sge(a, b) + + def ult(self, a: Value, b: Value) -> Value: + self._cache_op(Ult, a.width) + return self.seq.ult(a, b) + + def ule(self, a: Value, b: Value) -> Value: + self._cache_op(Ule, a.width) + return self.seq.ule(a, b) + + def ugt(self, a: Value, b: Value) -> Value: + self._cache_op(Ugt, a.width) + return self.seq.ugt(a, b) + + def uge(self, a: Value, b: Value) -> Value: + self._cache_op(Uge, a.width) + return self.seq.uge(a, b) + + def eq(self, a: Value, b: Value) -> Value: + self._cache_op(Eq, a.width) + return self.seq.eq(a, b) + + def ne(self, a: Value, b: Value) -> Value: + self._cache_op(Ne, a.width) + return self.seq.ne(a, b) + + def rem_u(self, a: Value, b: Value) -> Value: + self._cache_op(RemU, a.width) + return self.seq.rem_u(a, b) + + def rem_s(self, a: Value, b: Value) -> Value: + self._cache_op(RemS, a.width) + return self.seq.rem_s(a, b) + + def ror(self, a: Value, b: Value) -> Value: + self._cache_op(Ror, a.width) + return self.seq.ror(a, b) + + def rol(self, a: Value, b: Value) -> Value: + self._cache_op(Rol, a.width) + return self.seq.rol(a, b) + + def add_overflow(self, a: Value, b: Value) -> Value: + self._cache_op(AddOverflow, a.width) + return self.seq.add_overflow(a, b) + + def sub_overflow(self, a: Value, b: Value) -> Value: + self._cache_op(SubOverflow, a.width) + return self.seq.sub_overflow(a, b) + + def not_(self, a: Value) -> Value: + self._cache_op(Not, a.width) + return self.seq.not_(a) + + def neg(self, a: Value) -> Value: + self._cache_op(Neg, a.width) + return self.seq.neg(a) + + def popcnt(self, a: Value) -> Value: + self._cache_op(Popcnt, a.width) + return self.seq.popcnt(a) + + def ctz(self, a: Value) -> Value: + self._cache_op(Ctz, a.width) + return self.seq.ctz(a) + + def clz(self, a: Value) -> Value: + self._cache_op(Clz, a.width) + return self.seq.clz(a) + + def reverse(self, a: Value) -> Value: + self._cache_op(Reverse, a.width) + return self.seq.reverse(a) + + def extend_sign(self, a: Value, to_width: int) -> Value: + self._cache_op(ExtendSign, a.width, to_width) + return self.seq.extend_sign(a, to_width) + + def extend_zero(self, a: Value, to_width: int) -> Value: + self._cache_op(ExtendZero, a.width, to_width) + return self.seq.extend_zero(a, to_width) + + def extract_low(self, a: Value, out_width: int) -> Value: + self._cache_op(ExtractLow, a.width, out_width) + return self.seq.extract_low(a, out_width) + + def div_u(self, a: Value, b: Value, default: Value) -> Value: + self._cache_op(DivU, a.width) + return self.seq.div_u(a, b, default) + + def div_s(self, a: Value, b: Value, default: Value) -> Value: + self._cache_op(DivS, a.width) + return self.seq.div_s(a, b, default) + + def select(self, cond: Value, true_val: Value, false_val: Value) -> Value: + self._cache_op(Select, true_val.width) + return self.seq.select(cond, true_val, false_val) + + # ------------------------------------------------------------------ + # NOTE: Building python/lira/ir_std.py objects + # ------------------------------------------------------------------ + def read( + self, rf: RegisterFile, rsi: Value, shape: Shape = Shape(1, None) + ) -> Value: + return self.seq.read(rf, rsi, shape) + + def write( + self, rf: RegisterFile, rsi: Value, value: Value, shape: Shape = Shape(1, None) + ): + self.seq.write(rf, rsi, value, shape) + + def const(self, value: int, width: int = 32) -> Value: + return self.seq.const(value, width) + + def dyn_const(self, name: str, width: int = 32) -> Value: + return self.seq.dyn_const(name, width) + + def env(self, env_func: EnvironmentFunction, inputs: List[Value]) -> List[Value]: + return self.seq.env(env_func, inputs) + + def cond_env( + self, + env_func: EnvironmentFunction, + cond: Value, + inputs: List[Value], + on_false: List[Value], + ) -> List[Value]: + return self.seq.cond_env(env_func, cond, inputs, on_false) + + def input(self, idx: int, width: int = 32) -> Value: + return self.seq.input(idx, width) + + def output(self, value: Value, idx: int): + self.seq.output(value, idx) + + # ------------------------------------------------------------------ + # NOTE: Others + # ------------------------------------------------------------------ + def op(self, operation: Operation, inputs: List[Value]) -> Value: + self._op_cache[operation.name] = operation + return self.seq.op(operation, inputs) + + def op_multi(self, operation: Operation, inputs: List[Value]) -> List[Value]: + return self.seq.op_multi(operation, inputs) + + +class SnippetBuilder(BaseBuilder): + def __init__(self, name: str): + super().__init__() + self.name = name + + def build(self) -> Snippet: + return Snippet(self.name, self.seq.build()) + + +class InstructionBuilder(BaseBuilder): + def __init__( + self, + name: str, + operand_sizes: List[int], + operand_names: List[str], + encoding: InstructionEncoding, + ): + super().__init__() + self.name = name + self.operand_sizes = operand_sizes + self.operand_names = operand_names + self.encoding = encoding + + def add_input_operand(self, idx: int, width: Optional[int] = None) -> Value: + if width is None: + width = self.operand_sizes[idx] if idx < len(self.operand_sizes) else 32 + return self.input(idx, width) + + def build(self) -> Instruction: + return Instruction( + name=self.name, + attributes=[], + operand_sizes=self.operand_sizes, + operand_names=self.operand_names, + encoding=self.encoding, + semantic=self.seq.build(), + ) + + +class ArchBuilder: + def __init__(self, name: str, attributes: List[str] = None): + self.name = name + self.attributes = attributes or [] + self.register_files: List[RegisterFile] = [] + self.system_registers: List[SystemRegister] = [] + self.environment_functions: List[EnvironmentFunction] = [] + self.tables_int: List[TableInt] = [] + self.operations: List[Operation] = [] + self.snippets: List[Snippet] = [] + self.instructions: List[Instruction] = [] + + def add_register_file(self, rf: RegisterFile): + self.register_files.append(rf) + return self + + def add_system_register(self, sr: SystemRegister): + self.system_registers.append(sr) + return self + + def add_env_func(self, env: EnvironmentFunction): + self.environment_functions.append(env) + return self + + def add_table_int(self, table: TableInt): + self.tables_int.append(table) + return self + + def add_operation(self, op: Operation): + self.operations.append(op) + return self + + def add_snippet(self, snippet: Snippet): + self.snippets.append(snippet) + return self + + def add_instruction(self, instr: Instruction): + self.instructions.append(instr) + return self + + def build(self) -> Arch: + return Arch( + name=self.name, + attributes=self.attributes, + register_files=self.register_files, + system_registers=self.system_registers, + environment_functions=self.environment_functions, + tables_int=self.tables_int, + operations=self.operations, + snippets=self.snippets, + instructions=self.instructions, + ) diff --git a/python/lira/ir_ops.py b/python/lira/ir_ops.py new file mode 100644 index 0000000..6d7fbfe --- /dev/null +++ b/python/lira/ir_ops.py @@ -0,0 +1,403 @@ +from .arch import Operation + + +class TypeCheckError(Exception): + pass + + +class BaseOp: + NOT = "not" + NEG = "neg" + ADD = "add" + SUB = "sub" + MUL = "mul" + AND = "and" + ORR = "orr" + XOR = "xor" + LSL = "lsl" + LSR = "lsr" + ASR = "asr" + EQ = "eq" + NE = "ne" + SLT = "slt" + SLE = "sle" + SGT = "sgt" + SGE = "sge" + ULT = "ult" + ULE = "ule" + UGT = "ugt" + UGE = "uge" + EXTEND_SIGN = "extend_sign" + EXTEND_ZERO = "extend_zero" + EXTRACT_LOW = "extract_low" + SELECT = "select" + POPCNT = "popcnt" + CTZ = "ctz" + CLZ = "clz" + REVERSE = "reverse" + DIV_U = "div_u" + DIV_S = "div_s" + REM_U = "rem_u" + REM_S = "rem_s" + ROR = "ror" + ROL = "rol" + ADD_OVERFLOW = "add_overflow" + SUB_OVERFLOW = "sub_overflow" + + +def check_bits(value: int, name: str): + if not isinstance(value, int) or value <= 0: + raise TypeCheckError(f"{name} must be positive integer, got {value}") + + +class UnaryOp(Operation): + def __init__(self, out_bits: int, semantic_base: str, name: str = ""): + if not name: + name = f"{semantic_base}_{out_bits}" + super().__init__( + name=name, + attributes=[], + inputs=[out_bits], + outputs=[out_bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + check_bits(self.inputs[0], "input width") + check_bits(self.outputs[0], "output width") + if self.inputs[0] != self.outputs[0]: + raise TypeCheckError( + f"UnaryOp: input {self.inputs[0]} != output {self.outputs[0]}" + ) + + +class BinaryOp(Operation): + def __init__(self, bits: int, semantic_base: str, name: str = ""): + if not name: + name = f"{semantic_base}_{bits}" + super().__init__( + name=name, + attributes=[], + inputs=[bits, bits], + outputs=[bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + for i, inp in enumerate(self.inputs): + check_bits(inp, f"input[{i}]") + check_bits(self.outputs[0], "output") + if not (self.inputs[0] == self.inputs[1] == self.outputs[0]): + raise TypeCheckError( + f"BinaryOp: inputs {self.inputs} != output {self.outputs[0]}" + ) + + +class CmpOp(Operation): + def __init__( + self, bits: int, semantic_base: str, out_bits: int = 1, name: str = "" + ): + if not name: + name = f"{semantic_base}_{bits}" + super().__init__( + name=name, + attributes=[], + inputs=[bits, bits], + outputs=[out_bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + check_bits(self.inputs[0], "input[0]") + check_bits(self.inputs[1], "input[1]") + check_bits(self.outputs[0], "output") + if self.inputs[0] != self.inputs[1]: + raise TypeCheckError( + f"CmpOp: input widths differ {self.inputs[0]} != {self.inputs[1]}" + ) + + +class TernaryOp(Operation): + def __init__(self, bits: int, semantic_base: str, name: str = ""): + if not name: + name = f"{semantic_base}_{bits}" + super().__init__( + name=name, + attributes=[], + inputs=[bits, bits, bits], + outputs=[bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + for i, inp in enumerate(self.inputs): + check_bits(inp, f"input[{i}]") + check_bits(self.outputs[0], "output") + if not (self.inputs[0] == self.inputs[1] == self.inputs[2] == self.outputs[0]): + raise TypeCheckError( + f"TernaryOp: mismatched widths {self.inputs} -> {self.outputs[0]}" + ) + + +class ExtendOp(Operation): + def __init__(self, in_bits: int, out_bits: int, semantic_base: str, name: str = ""): + if not name: + name = f"{semantic_base}_{in_bits}_to_{out_bits}" + super().__init__( + name=name, + attributes=[], + inputs=[in_bits], + outputs=[out_bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + check_bits(self.inputs[0], "input") + check_bits(self.outputs[0], "output") + if self.inputs[0] >= self.outputs[0]: + raise TypeCheckError( + f"ExtendOp: input {self.inputs[0]} >= output {self.outputs[0]}" + ) + + +class ExtractLowOp(Operation): + def __init__(self, in_bits: int, out_bits: int, semantic_base: str, name: str = ""): + if not name: + name = f"{semantic_base}_{in_bits}_to_{out_bits}" + super().__init__( + name=name, + attributes=[], + inputs=[in_bits], + outputs=[out_bits], + semantic_base=semantic_base, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + check_bits(self.inputs[0], "input") + check_bits(self.outputs[0], "output") + if self.outputs[0] > self.inputs[0]: + raise TypeCheckError( + f"ExtractLow: output {self.outputs[0]} > input {self.inputs[0]}" + ) + + +class Not(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.NOT) + + +class Neg(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.NEG) + + +class Add(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ADD) + + +class Sub(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SUB) + + +class Mul(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.MUL) + + +class And(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.AND) + + +class Orr(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ORR) + + +class Xor(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.XOR) + + +class Lsl(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.LSL) + + +class Lsr(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.LSR) + + +class Asr(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ASR) + + +class Eq(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.EQ) + + +class Ne(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.NE) + + +class Slt(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SLT) + + +class Sle(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SLE) + + +class Sgt(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SGT) + + +class Sge(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SGE) + + +class Ult(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ULT) + + +class Ule(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ULE) + + +class Ugt(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.UGT) + + +class Uge(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.UGE) + + +class ExtendSign(ExtendOp): + def __init__(self, in_bits: int, out_bits: int): + super().__init__(in_bits, out_bits, BaseOp.EXTEND_SIGN) + + +class ExtendZero(ExtendOp): + def __init__(self, in_bits: int, out_bits: int): + super().__init__(in_bits, out_bits, BaseOp.EXTEND_ZERO) + + +class ExtractLow(ExtractLowOp): + def __init__(self, in_bits: int, out_bits: int): + super().__init__(in_bits, out_bits, BaseOp.EXTRACT_LOW) + + +class Popcnt(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.POPCNT) + + +class Ctz(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.CTZ) + + +class Clz(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.CLZ) + + +class Reverse(UnaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.REVERSE) + + +class RemU(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.REM_U) + + +class RemS(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.REM_S) + + +class Ror(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ROR) + + +class Rol(BinaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ROL) + + +class AddOverflow(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.ADD_OVERFLOW, out_bits=1) + + +class SubOverflow(CmpOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.SUB_OVERFLOW, out_bits=1) + + +class DivU(TernaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.DIV_U) + + +class DivS(TernaryOp): + def __init__(self, bits: int): + super().__init__(bits, BaseOp.DIV_S) + + +class Select(Operation): + def __init__(self, bits: int): + name = f"select_{bits}" + super().__init__( + name=name, + attributes=[], + inputs=[1, bits, bits], + outputs=[bits], + semantic_base=BaseOp.SELECT, + semantic_func=None, + semantic_table=None, + ) + self._check_signature() + + def _check_signature(self): + check_bits(self.inputs[1], "input[1] width") + check_bits(self.inputs[2], "input[2] width") + check_bits(self.outputs[0], "output width") + if not (self.inputs[1] == self.inputs[2] == self.outputs[0]): + raise TypeCheckError( + "Select: mismatched widths of true/false branches and output" + ) diff --git a/python/lira/ir_ser_txt.py b/python/lira/ir_ser_txt.py index 35d8ee2..8706e99 100644 --- a/python/lira/ir_ser_txt.py +++ b/python/lira/ir_ser_txt.py @@ -1,4 +1,4 @@ -from lira.ir import * +from .ir import * import re diff --git a/python/lira/ir_std.py b/python/lira/ir_std.py index 8504048..260908f 100644 --- a/python/lira/ir_std.py +++ b/python/lira/ir_std.py @@ -1,5 +1,5 @@ -from lira.ir import * -from lira.arch import * +from .ir import * +from .arch import * from dataclasses import dataclass @@ -36,8 +36,8 @@ class StmtEnv: class CondEnv: env: EnvironmentFunction cond: str - on_false: [str] - inputs: [str] + on_false: list[str] + inputs: list[str] @dataclass class StmtIndex: @@ -60,14 +60,14 @@ class StmtGather: @dataclass class StmtFold: op: Operation - args: [str] + args: list[str] @dataclass class StmtScan: op: Operation - args: [str] + args: list[str] @dataclass class StmtAlias: semantic: Snippet - args: [str] + args: list[str] diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 0000000..aad8616 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.backends._legacy:_Backend" + +[project] +name = "lira-ir" +version = "0.1.0" +description = "LIRA is a framework that provides a data-flow Intermediate Representation." +readme = "../README.md" +requires-python = ">=3.10" +dependencies = [ + "ruamel.yaml", +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov", +] + +[tool.setuptools.packages.find] +where = ["."] diff --git a/python/tests/README.md b/python/tests/README.md new file mode 100644 index 0000000..081324c --- /dev/null +++ b/python/tests/README.md @@ -0,0 +1,21 @@ +# LIRA Python Tests + +## Unit tests + +```bash +pytest python/tests/unit/ -v +``` + +With coverage: + +```bash +pytest --cov=python/lira python/tests/unit/ --cov-report=term +``` + +## Integration test + +Reads `tests/integration/reference.yaml`, writes back via [normalization script](../../tools/yaml_canonicalize.py), asserts round-trip equality. + +```bash +pytest python/tests/integration/ -v +``` diff --git a/python/tests/integration/__init__.py b/python/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/integration/integration.yaml b/python/tests/integration/integration.yaml new file mode 100644 index 0000000..cd13447 --- /dev/null +++ b/python/tests/integration/integration.yaml @@ -0,0 +1,859 @@ +name: TargetArch +attributes: [] +register_files: + - name: XRegs + attributes: [] + reg_size: + lanes_base: 32 + lanes_mult: + regs: + - name: x0 + attributes: + - zero + - name: x1 + attributes: [] + - name: x2 + attributes: [] + - name: x3 + attributes: [] + - name: x4 + attributes: [] + - name: x5 + attributes: [] + - name: x6 + attributes: [] + - name: x7 + attributes: [] + - name: x8 + attributes: [] + - name: SpecialRegs + attributes: [] + reg_size: + lanes_base: 32 + lanes_mult: + regs: + - name: pc + attributes: + - pc +system_registers: [] +environment_functions: + - name: sysCall + attributes: [] + inputs: [] + outputs: [] + - name: writeMem16 + attributes: [] + inputs: + - 32 + - 16 + outputs: [] + - name: readMem16 + attributes: [] + inputs: + - 32 + outputs: + - 16 + - name: getPC + attributes: [] + inputs: [] + outputs: + - 32 + - name: setPC + attributes: [] + inputs: + - 32 + outputs: [] +tables_int: [] +operations: + - name: add_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :add + semantic_func: + semantic_func_128: + semantic_table: + - name: lsr_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :lsr + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_5 + attributes: [] + inputs: + - 32 + outputs: + - 5 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_5_to_32 + attributes: [] + inputs: + - 5 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_1_to_32 + attributes: [] + inputs: + - 1 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: lsl_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :lsl + semantic_func: + semantic_func_128: + semantic_table: + - name: orr_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :orr + semantic_func: + semantic_func_128: + semantic_table: + - name: and_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :and + semantic_func: + semantic_func_128: + semantic_table: + - name: eq_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 1 + semantic_base: :eq + semantic_func: + semantic_func_128: + semantic_table: + - name: select_32 + attributes: [] + inputs: + - 1 + - 32 + - 32 + outputs: + - 32 + semantic_base: :select + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_1 + attributes: [] + inputs: + - 32 + outputs: + - 1 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_1_to_13 + attributes: [] + inputs: + - 1 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_13_to_32 + attributes: [] + inputs: + - 13 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_6 + attributes: [] + inputs: + - 32 + outputs: + - 6 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_6_to_13 + attributes: [] + inputs: + - 6 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_4 + attributes: [] + inputs: + - 32 + outputs: + - 4 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_4_to_13 + attributes: [] + inputs: + - 4 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_13 + attributes: [] + inputs: + - 32 + outputs: + - 13 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_13_to_32 + attributes: [] + inputs: + - 13 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_12 + attributes: [] + inputs: + - 32 + outputs: + - 12 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_12_to_32 + attributes: [] + inputs: + - 12 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_16 + attributes: [] + inputs: + - 32 + outputs: + - 16 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_7 + attributes: [] + inputs: + - 32 + outputs: + - 7 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_7_to_12 + attributes: [] + inputs: + - 7 + outputs: + - 12 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_12_to_32 + attributes: [] + inputs: + - 12 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_5_to_12 + attributes: [] + inputs: + - 5 + outputs: + - 12 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_16_to_32 + attributes: [] + inputs: + - 16 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_16_to_32 + attributes: [] + inputs: + - 16 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: +snippets: + - name: decode_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 20; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: decode_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 15; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: decode_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 7; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: encode_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 51; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 0; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 24; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 1 _t24 = const 0; + 1 32 _t25 = op extend_zero_1_to_32 _t24; + 1 32 _t26 = const 31; + 1 32 _t27 = op lsl_32 _t25 _t26; + 1 32 _t28 = op orr_32 _t23 _t27; + 1 = output 0 _t28; + - name: constraint_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 4261441663; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 51; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 31; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 1 _t5 = op extract_low_32_to_1 _t4; + 1 13 _t6 = op extend_zero_1_to_13 _t5; + 1 32 _t8 = const 12; + 1 32 _t9 = op extend_zero_13_to_32 _t6; + 1 32 _t10 = op lsl_32 _t9 _t8; + 1 32 _t12 = const 7; + 1 32 _t13 = op lsr_32 _t1 _t12; + 1 1 _t14 = op extract_low_32_to_1 _t13; + 1 13 _t15 = op extend_zero_1_to_13 _t14; + 1 32 _t17 = const 11; + 1 32 _t18 = op extend_zero_13_to_32 _t15; + 1 32 _t19 = op lsl_32 _t18 _t17; + 1 32 _t21 = op orr_32 _t10 _t19; + 1 32 _t23 = const 25; + 1 32 _t24 = op lsr_32 _t1 _t23; + 1 6 _t25 = op extract_low_32_to_6 _t24; + 1 13 _t26 = op extend_zero_6_to_13 _t25; + 1 32 _t28 = const 5; + 1 32 _t29 = op extend_zero_13_to_32 _t26; + 1 32 _t30 = op lsl_32 _t29 _t28; + 1 32 _t32 = op orr_32 _t21 _t30; + 1 32 _t34 = const 8; + 1 32 _t35 = op lsr_32 _t1 _t34; + 1 4 _t36 = op extract_low_32_to_4 _t35; + 1 13 _t37 = op extend_zero_4_to_13 _t36; + 1 32 _t39 = const 1; + 1 32 _t40 = op extend_zero_13_to_32 _t37; + 1 32 _t41 = op lsl_32 _t40 _t39; + 1 32 _t43 = op orr_32 _t32 _t41; + 1 13 _t45 = op extract_low_32_to_13 _t43; + 1 32 _t46 = op extend_sign_13_to_32 _t45; + 1 = output 0 _t46; + - name: encode_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 99; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 1 _t10 = const 0; + 1 32 _t11 = op extend_zero_1_to_32 _t10; + 1 32 _t12 = const 14; + 1 32 _t13 = op lsl_32 _t11 _t12; + 1 32 _t14 = op orr_32 _t9 _t13; + 1 32 _t15 = const 19; + 1 32 _t16 = op lsl_32 _t2 _t15; + 1 32 _t17 = op orr_32 _t14 _t16; + 1 32 _t18 = const 24; + 1 32 _t19 = op lsl_32 _t3 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 = output 0 _t20; + - name: constraint_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 99; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 20; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 12 _t5 = op extract_low_32_to_12 _t4; + 1 32 _t6 = op extend_sign_12_to_32 _t5; + 1 = output 0 _t6; + - name: encode_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 103; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 0; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 103; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 25; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 7 _t5 = op extract_low_32_to_7 _t4; + 1 12 _t6 = op extend_zero_7_to_12 _t5; + 1 32 _t8 = const 5; + 1 32 _t9 = op extend_zero_12_to_32 _t6; + 1 32 _t10 = op lsl_32 _t9 _t8; + 1 32 _t12 = const 7; + 1 32 _t13 = op lsr_32 _t1 _t12; + 1 5 _t14 = op extract_low_32_to_5 _t13; + 1 12 _t15 = op extend_zero_5_to_12 _t14; + 1 32 _t17 = op extend_zero_12_to_32 _t15; + 1 32 _t18 = op orr_32 _t10 _t17; + 1 12 _t20 = op extract_low_32_to_12 _t18; + 1 32 _t21 = op extend_sign_12_to_32 _t20; + 1 = output 0 _t21; + - name: encode_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 35; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 1 _t10 = const 1; + 1 32 _t11 = op extend_zero_1_to_32 _t10; + 1 32 _t12 = const 14; + 1 32 _t13 = op lsl_32 _t11 _t12; + 1 32 _t14 = op orr_32 _t9 _t13; + 1 32 _t15 = const 19; + 1 32 _t16 = op lsl_32 _t2 _t15; + 1 32 _t17 = op orr_32 _t14 _t16; + 1 32 _t18 = const 24; + 1 32 _t19 = op lsl_32 _t3 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 = output 0 _t20; + - name: constraint_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 4131; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: encode_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 3; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 1; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 4099; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: encode_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 3; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 5; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 20483; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: constraint_6 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 4294967295; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 115; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; +instructions: + - name: add + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - rs2 + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 51 + const_mask: 4261441663 + decode: + - decode_0 + - decode_1 + - decode_2 + encode: encode_0 + constraint_decode: constraint_0 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t7 = input 0; + 1 32 _t8 = read XRegs _t7; + 1 32 _t10 = op add_32 _t4 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: beq + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rs2 + encoding: + encoded_size: 32 + const_encoding_part: 99 + const_mask: 28799 + decode: + - decode_3 + - decode_1 + - decode_0 + encode: encode_1 + constraint_decode: constraint_1 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t6 = input 2; + 1 32 _t7 = read XRegs _t6; + 1 1 _t8 = op eq_32 _t4 _t7; + 1 32 _t10 = env getPC; + 1 32 _t11 = input 0; + 1 32 _t12 = op add_32 _t10 _t11; + 1 32 _t14 = const 4; + 1 32 _t15 = op add_32 _t10 _t14; + 1 32 _t17 = op select_32 _t8 _t12 _t15; + 1 = env setPC _t17; + - name: jalr + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 103 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_2 + constraint_decode: constraint_2 + constraint_encode: '' + semantic: | + 1 32 _t2 = env getPC; + 1 32 _t3 = const 4; + 1 32 _t4 = op add_32 _t2 _t3; + 1 32 _t8 = input 1; + 1 32 _t9 = read XRegs _t8; + 1 32 _t10 = input 0; + 1 32 _t11 = op add_32 _t9 _t10; + 1 32 _t13 = const -2; + 1 32 _t14 = op and_32 _t11 _t13; + 1 = env setPC _t14; + 1 32 _t15 = input 2; + 1 = write XRegs _t15 _t4; + - name: sh + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rs2 + encoding: + encoded_size: 32 + const_encoding_part: 4131 + const_mask: 28799 + decode: + - decode_5 + - decode_1 + - decode_0 + encode: encode_3 + constraint_decode: constraint_3 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 32 _t9 = input 2; + 1 32 _t10 = read XRegs _t9; + 1 32 _t11 = const 0; + 1 32 _t12 = op lsr_32 _t10 _t11; + 1 16 _t13 = op extract_low_32_to_16 _t12; + 1 = env writeMem16 _t6 _t13; + - name: lh + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 4099 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_4 + constraint_decode: constraint_4 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 16 _t8 = env readMem16 _t6; + 1 32 _t10 = op extend_sign_16_to_32 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: lhu + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 20483 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_5 + constraint_decode: constraint_5 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 16 _t8 = env readMem16 _t6; + 1 32 _t10 = op extend_zero_16_to_32 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: ecall + attributes: [] + operand_sizes: [] + operand_names: [] + encoding: + encoded_size: 32 + const_encoding_part: 115 + const_mask: 4294967295 + decode: [] + encode: '' + constraint_decode: constraint_6 + constraint_encode: '' + semantic: | + 1 = env sysCall; diff --git a/python/tests/integration/test_integration.py b/python/tests/integration/test_integration.py new file mode 100644 index 0000000..6f79da7 --- /dev/null +++ b/python/tests/integration/test_integration.py @@ -0,0 +1,26 @@ +import subprocess, sys +from pathlib import Path + +from python.lira import arch_ser_yaml + +PROJECT_ROOT = Path(__file__).parent.parent.parent.parent +REFERENCE = PROJECT_ROOT / "tests" / "integration" / "reference.yaml" +CANONICALIZE = PROJECT_ROOT / "tools" / "yaml_canonicalize.py" + + +def test_integration(): + ref_arch = arch_ser_yaml.read_arch(REFERENCE) + + output = PROJECT_ROOT / "python" / "tests" / "integration" / "integration.yaml" + raw = output.with_suffix(".raw.yaml") + try: + arch_ser_yaml.write_arch(ref_arch, raw) + subprocess.run( + [sys.executable, str(CANONICALIZE), str(raw), str(output)], check=True + ) + raw.unlink() + + arch2 = arch_ser_yaml.read_arch(output) + assert ref_arch == arch2, "Round-trip failed" + finally: + raw.unlink(missing_ok=True) diff --git a/python/tests/test_ir_ser.py b/python/tests/test_ir_ser.py deleted file mode 100644 index 31f2402..0000000 --- a/python/tests/test_ir_ser.py +++ /dev/null @@ -1,27 +0,0 @@ -import unittest -from lira.ir import * -from lira.ir_ser_txt import * - -class TestStatementSeqSerialization(unittest.TestCase): - def test_empty_sequence(self): - empty_seq = StatementSeq(stmts=[]) - self.assertEqual(serialize_statement_seq(empty_seq), "") - - def test_deserialize_complex_statement(self): - stmt_txt = "8 5 x 6 y = add v1 a b c" - stmt_ir = deserialize_statement(stmt_txt) - stmt_txt2 = serialize_statement(stmt_ir) - self.assertEqual(stmt_txt, stmt_txt2) - - def test_stmt_seq(self): - text = '\n'.join([ - '4 1 a = env load;', - '2c 3 b = env store x y z;', - '2c 3 b = env store x y z;', - ]) + '\n' - ir = deserialize_statement_seq(text) - text2 = serialize_statement_seq(ir) - self.assertEqual(text2, text) - -if __name__ == '__main__': - unittest.main() diff --git a/python/tests/unit/__init__.py b/python/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/tests/unit/test_arch_ser_yaml.py b/python/tests/unit/test_arch_ser_yaml.py new file mode 100644 index 0000000..2d5c95f --- /dev/null +++ b/python/tests/unit/test_arch_ser_yaml.py @@ -0,0 +1,240 @@ +import sys, tempfile +from pathlib import Path + +import pytest + +from python.lira.ir import Shape +from python.lira.arch import ( + Register, + RegisterFile, + EnvironmentFunction, + InstructionEncoding, + SystemRegister, + SystemRegisterField, + TableInt, +) +from python.lira.ir_builder import ArchBuilder, SnippetBuilder, InstructionBuilder +from python.lira.ir_ops import ( + Add, + Lsl, + Lsr, + Orr, + And, + Eq, + Select, + ExtractLow, + ExtendZero, + ExtendSign, +) +from python.lira import arch_ser_yaml + + +@pytest.fixture +def tmp_yaml(): + tmp = Path(tempfile.mktemp(suffix=".yaml")) + yield tmp + tmp.unlink(missing_ok=True) + + +def _build_decode_0_snippet(): + sb = SnippetBuilder("decode_0") + enc = sb.input(0, 32) + c7 = sb.const(7, 32) + shifted = sb.lsr(enc, c7) + low5 = sb.extract_low(shifted, 5) + extended = sb.extend_zero(low5, 32) + sb.output(extended, 0) + return sb.build() + + +def _build_decode_4_snippet(): + sb = SnippetBuilder("decode_4") + enc = sb.input(0, 32) + c20 = sb.const(20, 32) + shifted = sb.lsr(enc, c20) + low12 = sb.extract_low(shifted, 12) + extended = sb.extend_sign(low12, 32) + sb.output(extended, 0) + return sb.build() + + +def _build_constraint_36_snippet(): + sb = SnippetBuilder("constraint_36") + enc = sb.input(0, 32) + mask = sb.const(28799, 32) + masked = sb.and_(enc, mask) + expected = sb.const(20483, 32) + ok = sb.eq(masked, expected) + sb.output(ok, 0) + return sb.build() + + +@pytest.fixture(scope="module") +def rv32i_arch_lite(): + rf = RegisterFile( + "XRegs", + [], + Shape(32, None), + [Register("x0", ["zero"])] + [Register(f"x{i}") for i in range(1, 32)], + ) + + ab = ArchBuilder("rv32i_lite", []) + ab.add_register_file(rf) + + syscall = EnvironmentFunction("sysCall", [], [], []) + read_mem = EnvironmentFunction("readMem16", [], [32], [16]) + get_pc = EnvironmentFunction("getPC", [], [], [32]) + set_pc = EnvironmentFunction("setPC", [], [32], []) + ab.add_env_func(syscall) + ab.add_env_func(read_mem) + ab.add_env_func(get_pc) + ab.add_env_func(set_pc) + + ab.add_operation(Add(32)) + ab.add_operation(Lsr(32)) + ab.add_operation(Lsl(32)) + ab.add_operation(Orr(32)) + ab.add_operation(And(32)) + ab.add_operation(Eq(32)) + ab.add_operation(Select(32)) + ab.add_operation(ExtractLow(32, 5)) + ab.add_operation(ExtractLow(32, 12)) + ab.add_operation(ExtendZero(5, 32)) + ab.add_operation(ExtendZero(16, 32)) + ab.add_operation(ExtendZero(1, 32)) + ab.add_operation(ExtendSign(12, 32)) + + ab.add_snippet(_build_decode_0_snippet()) + ab.add_snippet(_build_decode_4_snippet()) + ab.add_snippet(_build_constraint_36_snippet()) + + # add instruction + enc = InstructionEncoding(32, 51, 4261441663, ["decode_0"], "", "", "") + ib = InstructionBuilder("add", [32, 32, 32], ["rs2", "rs1", "rd"], enc) + rs2 = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rd = ib.add_input_operand(2, 32) + v1 = ib.read(rf, rs1) + v2 = ib.read(rf, rs2) + r = ib.add(v1, v2) + ib.write(rf, rd, r) + ab.add_instruction(ib.build()) + + # lhu instruction + enc = InstructionEncoding(32, 20483, 28799, ["decode_4"], "", "", "") + ib = InstructionBuilder("lhu", [32, 32, 32], ["imm", "rs1", "rd"], enc) + imm = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rd = ib.add_input_operand(2, 32) + v = ib.read(rf, rs1) + addr = ib.add(v, imm) + val16 = ib.env(read_mem, [addr]) + r = ib.extend_zero(val16[0], 32) + ib.write(rf, rd, r) + ab.add_instruction(ib.build()) + + # beq instruction + enc = InstructionEncoding(32, 99, 28799, ["decode_0"], "", "", "") + ib = InstructionBuilder("beq", [32, 32, 32], ["imm", "rs1", "rs2"], enc) + imm = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rs2 = ib.add_input_operand(2, 32) + v1 = ib.read(rf, rs1) + v2 = ib.read(rf, rs2) + eq = ib.eq(v1, v2) + base = ib.env(get_pc, []) + dest = ib.add(base[0], imm) + c4 = ib.const(4, 32) + fallback = ib.add(base[0], c4) + ib.cond_env(set_pc, eq, [dest], [fallback]) + ab.add_instruction(ib.build()) + + # ecall instruction + enc = InstructionEncoding(32, 115, 0xFFFFFFFF, [], "", "", "") + ib = InstructionBuilder("ecall", [], [], enc) + ib.env(syscall, []) + ab.add_instruction(ib.build()) + + return ab.build() + + +class TestArchSerYaml: + def test_minimal_arch_with_builder(self, tmp_yaml): + rf = RegisterFile("X", [], Shape(32, None), [Register("x0"), Register("x1")]) + ab = ArchBuilder("minimal", []) + ab.add_register_file(rf) + sb = SnippetBuilder("s") + a = sb.input(0, 32) + sb.output(a, 0) + ab.add_snippet(sb.build()) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch == arch2 + + def test_instruction_via_builder(self, tmp_yaml): + rf = RegisterFile("X", [], Shape(32, None), [Register("x0")]) + enc = InstructionEncoding(32, 0, 0, [], "", "", "") + ib = InstructionBuilder("test", [5, 5], ["rs1", "rs2"], enc) + rs1 = ib.add_input_operand(0, 5) + rs2 = ib.add_input_operand(1, 5) + v = ib.read(rf, rs1) + ib.write(rf, rs2, v) + instr = ib.build() + ab = ArchBuilder("w_instr", []) + ab.add_register_file(rf) + ab.add_instruction(instr) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch == arch2 + + def test_null_fields_roundtrip(self, tmp_yaml): + op = Add(32) + op.semantic_base = None + op.semantic_func = None + ab = ArchBuilder("null_test", []) + ab.add_operation(op) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch2.operations[0].semantic_base is None + + def test_register_attributes(self, tmp_yaml): + rf = RegisterFile( + "R", [], Shape(32, None), [Register("x0", ["zero"]), Register("x1", [])] + ) + ab = ArchBuilder("attrs", []) + ab.add_register_file(rf) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch2.register_files[0].regs[0].attributes == ["zero"] + + def test_system_register(self, tmp_yaml): + field = SystemRegisterField("f", [], 0, 7) + sr = SystemRegister("csr", [], 32, [field]) + ab = ArchBuilder("sysreg", []) + ab.add_system_register(sr) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch2.system_registers[0].fields[0].lsb == 0 + + def test_table_int(self, tmp_yaml): + table = TableInt("t", [], [1, 2, 3]) + ab = ArchBuilder("tbl", []) + ab.add_table_int(table) + arch = ab.build() + arch_ser_yaml.write_arch(arch, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert arch2.tables_int[0].values == [1, 2, 3] + + def test_rv32i_lite_roundtrip(self, rv32i_arch_lite, tmp_yaml): + arch_ser_yaml.write_arch(rv32i_arch_lite, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + assert rv32i_arch_lite == arch2 + assert arch2.instructions[0].name == "add" + assert arch2.instructions[1].name == "lhu" + assert arch2.instructions[2].name == "beq" + assert arch2.instructions[3].name == "ecall" diff --git a/python/tests/unit/test_ir_builder.py b/python/tests/unit/test_ir_builder.py new file mode 100644 index 0000000..9043793 --- /dev/null +++ b/python/tests/unit/test_ir_builder.py @@ -0,0 +1,248 @@ +import pytest + +from python.lira.ir import Shape +from python.lira.arch import ( + Register, + RegisterFile, + EnvironmentFunction, + InstructionEncoding, +) +from python.lira.ir_builder import ( + SeqBuilder, + SnippetBuilder, + InstructionBuilder, + ArchBuilder, +) +from python.lira.ir_ops import Add + + +@pytest.fixture(scope="module") +def rv32i_rf(): + return RegisterFile( + "XRegs", + [], + Shape(32, None), + [Register("x0", ["zero"])] + [Register(f"x{i}") for i in range(1, 32)], + ) + + +@pytest.fixture(scope="module") +def rv32i_envs(): + return { + "sysCall": EnvironmentFunction("sysCall", [], [], []), + "readMem16": EnvironmentFunction("readMem16", [], [32], [16]), + "getPC": EnvironmentFunction("getPC", [], [], [32]), + "setPC": EnvironmentFunction("setPC", [], [32], []), + "writeMem32": EnvironmentFunction("writeMem32", [], [32, 32], []), + } + + +class TestSeqBuilder: + def test_const(self): + seq = SeqBuilder() + v = seq.const(42, 32) + assert v.width == 32 + assert v.name.startswith("_t") + + def test_add_sub_mul(self): + seq = SeqBuilder() + a = seq.const(3, 32) + b = seq.const(2, 32) + assert seq.add(a, b).width == 32 + assert seq.sub(a, b).width == 32 + assert seq.mul(a, b).width == 32 + + def test_bitwise_ops(self): + seq = SeqBuilder() + a = seq.const(0xFF, 32) + b = seq.const(0x0F, 32) + assert seq.and_(a, b).width == 32 + assert seq.orr(a, b).width == 32 + assert seq.xor(a, b).width == 32 + + def test_shift_ops(self): + seq = SeqBuilder() + a = seq.const(1, 32) + b = seq.const(4, 32) + assert seq.lsl(a, b).width == 32 + assert seq.lsr(a, b).width == 32 + assert seq.asr(a, b).width == 32 + + def test_slt(self): + seq = SeqBuilder() + a = seq.const(10, 32) + b = seq.const(20, 32) + r = seq.slt(a, b) + assert r.width == 1 + + def test_extract_low(self): + seq = SeqBuilder() + a = seq.const(0xFF, 32) + r = seq.extract_low(a, 8) + assert r.width == 8 + + def test_extend_sign(self): + seq = SeqBuilder() + a = seq.const(1, 8) + r = seq.extend_sign(a, 32) + assert r.width == 32 + + def test_extend_zero(self): + seq = SeqBuilder() + a = seq.const(1, 8) + r = seq.extend_zero(a, 32) + assert r.width == 32 + + def test_input_output(self): + seq = SeqBuilder() + inp = seq.input(0, 32) + seq.output(inp, 0) + s = seq.build() + assert s.stmts[0].kind == "input" + assert s.stmts[1].kind == "output" + + def test_operations_map(self): + snip = SnippetBuilder("test") + a = snip.const(1, 32) + b = snip.const(2, 32) + snip.add(a, b) + snip.sub(a, b) + snip.slt(a, b) + omap = snip.operations_map + assert "add_32" in omap + assert "slt_32" in omap + + def test_read_write(self, rv32i_rf): + seq = SeqBuilder() + reg = seq.input(0, 5) + v = seq.read(rv32i_rf, reg) + assert v.width == 32 + seq.write(rv32i_rf, reg, v) + kinds = {stmt.kind for stmt in seq.build().stmts} + assert "read" in kinds + assert "write" in kinds + + def test_env(self, rv32i_envs): + seq = SeqBuilder() + pc_read = rv32i_envs["getPC"] + result = seq.env(pc_read, []) + assert len(result) == 1 + assert result[0].width == 32 + + def test_cond_env(self, rv32i_envs): + seq = SeqBuilder() + cond = seq.const(1, 1) + addr = seq.const(100, 32) + fallback = seq.const(200, 32) + write_mem = rv32i_envs["writeMem32"] + result = seq.cond_env(write_mem, cond, [addr], [fallback]) + assert len(result) == 0 + + +class TestSnippetBuilder: + def test_build_snippet(self): + sb = SnippetBuilder("test") + a = sb.input(0, 32) + sb.output(a, 0) + snip = sb.build() + assert snip.name == "test" + assert len(snip.seq.stmts) == 2 + + def test_build_decode(self): + sb = SnippetBuilder("decode_rs2") + enc = sb.input(0, 32) + shift = sb.const(20, 32) + shifted = sb.lsr(enc, shift) + r = sb.extract_low(shifted, 5) + sb.output(r, 0) + snip = sb.build() + assert len(snip.seq.stmts) == 5 + + def test_build_constraint(self): + sb = SnippetBuilder("constraint") + enc = sb.input(0, 32) + mask = sb.const(0xFF, 32) + masked = sb.and_(enc, mask) + expected = sb.const(0x33, 32) + ok = sb.slt(masked, expected) + sb.output(ok, 0) + snip = sb.build() + assert len(snip.seq.stmts) == 6 + + +class TestInstructionBuilder: + def test_build_add(self, rv32i_rf): + enc = InstructionEncoding(32, 51, 4261441663, [], "", "", "") + ib = InstructionBuilder("add", [5, 5, 5], ["rd", "rs1", "rs2"], enc) + rd = ib.add_input_operand(0, 5) + rs1 = ib.add_input_operand(1, 5) + rs2 = ib.add_input_operand(2, 5) + v1 = ib.read(rv32i_rf, rs1) + v2 = ib.read(rv32i_rf, rs2) + r = ib.add(v1, v2) + ib.write(rv32i_rf, rd, r) + instr = ib.build() + assert instr.name == "add" + assert len(instr.semantic.stmts) == 7 + + def test_build_lhu(self, rv32i_rf, rv32i_envs): + enc = InstructionEncoding(32, 20483, 28799, [], "", "", "") + ib = InstructionBuilder("lhu", [32, 32, 32], ["imm", "rs1", "rd"], enc) + imm = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rd = ib.add_input_operand(2, 32) + v = ib.read(rv32i_rf, rs1) + addr = ib.add(v, imm) + val16 = ib.env(rv32i_envs["readMem16"], [addr]) + r = ib.extend_zero(val16[0], 32) + ib.write(rv32i_rf, rd, r) + instr = ib.build() + assert instr.name == "lhu" + + def test_build_beq(self, rv32i_rf, rv32i_envs): + enc = InstructionEncoding(32, 99, 28799, [], "", "", "") + ib = InstructionBuilder("beq", [32, 32, 32], ["imm", "rs1", "rs2"], enc) + imm = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rs2 = ib.add_input_operand(2, 32) + v1 = ib.read(rv32i_rf, rs1) + v2 = ib.read(rv32i_rf, rs2) + eq = ib.eq(v1, v2) + base = ib.env(rv32i_envs["getPC"], []) + dest = ib.add(base[0], imm) + c4 = ib.const(4, 32) + fallback = ib.add(base[0], c4) + ib.cond_env(rv32i_envs["setPC"], eq, [dest], [fallback]) + instr = ib.build() + assert instr.name == "beq" + + def test_build_ecall(self, rv32i_envs): + enc = InstructionEncoding(32, 115, 0xFFFFFFFF, [], "", "", "") + ib = InstructionBuilder("ecall", [], [], enc) + ib.env(rv32i_envs["sysCall"], []) + instr = ib.build() + assert instr.name == "ecall" + assert instr.semantic.stmts[0].kind == "env" + + +class TestArchBuilder: + def test_build_arch(self, rv32i_rf): + ab = ArchBuilder("test", ["attr"]) + ab.add_register_file(rv32i_rf) + arch = ab.build() + assert arch.name == "test" + assert len(arch.register_files) == 1 + + def test_add_env_operation_snippet(self): + ab = ArchBuilder("test", []) + env = EnvironmentFunction("ld", ["mem"], [32], [32]) + op = Add(32) + sb = SnippetBuilder("s1") + a = sb.input(0, 32) + sb.output(a, 0) + snip = sb.build() + ab.add_env_func(env).add_operation(op).add_snippet(snip) + arch = ab.build() + assert len(arch.environment_functions) == 1 + assert len(arch.operations) == 1 + assert len(arch.snippets) == 1 diff --git a/python/tests/unit/test_ir_ser_txt.py b/python/tests/unit/test_ir_ser_txt.py new file mode 100644 index 0000000..3d53217 --- /dev/null +++ b/python/tests/unit/test_ir_ser_txt.py @@ -0,0 +1,87 @@ +from python.lira.ir import Shape +from python.lira.arch import Register, RegisterFile, EnvironmentFunction +from python.lira.ir_ser_txt import serialize_statement_seq, deserialize_statement_seq +from python.lira.ir_builder import SeqBuilder, SnippetBuilder + + +class TestIrSerTxt: + def test_empty_sequence(self): + seq = SeqBuilder().build() + text = serialize_statement_seq(seq) + assert text == "" + + def test_built_sequence_roundtrip(self): + seq = SeqBuilder() + a = seq.input(0, 32) + b = seq.const(42, 32) + r = seq.add(a, b) + seq.output(r, 0) + stmts = seq.build() + text = serialize_statement_seq(stmts) + seq2 = deserialize_statement_seq(text) + assert seq2 == stmts + + def test_shape_with_mult(self): + seq = SeqBuilder() + a = seq.input(0, 32) + r = seq.add(a, a) + seq.output(r, 0) + stmts = seq.build() + stmts.stmts[0].shape = Shape(4, "c") + text = serialize_statement_seq(stmts) + assert "4c" in text + seq2 = deserialize_statement_seq(text) + assert seq2.stmts[0].shape.lanes_mult == "c" + + def test_read_write(self): + rf = RegisterFile("XRegs", [], Shape(32, None), [Register("x0")]) + seq = SeqBuilder() + reg = seq.input(0, 5) + v = seq.read(rf, reg) + seq.write(rf, reg, v) + stmts = seq.build() + text = serialize_statement_seq(stmts) + seq2 = deserialize_statement_seq(text) + assert seq2.stmts[1].kind == "read" + assert seq2.stmts[2].kind == "write" + + def test_env_and_cond_env(self): + get_pc = EnvironmentFunction("getPC", [], [], [32]) + write_mem = EnvironmentFunction("writeMem16", [], [32, 16], []) + seq = SeqBuilder() + seq.env(get_pc, []) + cond = seq.input(0, 1) + addr = seq.input(1, 32) + fallback = seq.input(2, 32) + seq.cond_env(write_mem, cond, [addr], [fallback]) + stmts = seq.build() + text = serialize_statement_seq(stmts) + seq2 = deserialize_statement_seq(text) + assert seq2.stmts[0].kind == "env" + assert seq2.stmts[-1].kind == "cond_env" + + def test_builder_decode_snippet(self): + sb = SnippetBuilder("decode_0") + enc = sb.input(0, 32) + c7 = sb.const(7, 32) + shifted = sb.lsr(enc, c7) + low5 = sb.extract_low(shifted, 5) + extended = sb.extend_zero(low5, 32) + sb.output(extended, 0) + snip = sb.build() + text = serialize_statement_seq(snip.seq) + seq2 = deserialize_statement_seq(text) + assert len(seq2.stmts) == 6 + + def test_builder_constraint_snippet(self): + sb = SnippetBuilder("constraint_36") + enc = sb.input(0, 32) + mask = sb.const(28799, 32) + masked = sb.and_(enc, mask) + expected = sb.const(20483, 32) + ok = sb.eq(masked, expected) + sb.output(ok, 0) + snip = sb.build() + text = serialize_statement_seq(snip.seq) + seq2 = deserialize_statement_seq(text) + assert len(seq2.stmts) == 6 diff --git a/readme.md b/readme.md deleted file mode 100644 index 36817a8..0000000 --- a/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# LIRA - -This repo contains code to work with LIRA IR and LIRA AD diff --git a/ruby/.rubocop.yml b/ruby/.rubocop.yml new file mode 100644 index 0000000..456d136 --- /dev/null +++ b/ruby/.rubocop.yml @@ -0,0 +1,6 @@ +Style/ClassAndModuleChildren: + EnforcedStyle: nested +Metrics/MethodLength: + Max: 25 +Metrics/AbcSize: + Max: 32 diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 0000000..75a6832 --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,8 @@ +# Gemfile + +source "https://rubygems.org" + +ruby "4.0.1" + +gem 'minitest', '~> 5.14' +gem 'simplecov', require: false diff --git a/ruby/Gemfile.lock b/ruby/Gemfile.lock new file mode 100644 index 0000000..2241948 --- /dev/null +++ b/ruby/Gemfile.lock @@ -0,0 +1,32 @@ +GEM + remote: https://rubygems.org/ + specs: + docile (1.4.1) + minitest (5.27.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + minitest (~> 5.14) + simplecov + +CHECKSUMS + docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e + minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5 + simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5 + simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246 + simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 + +RUBY VERSION + ruby 4.0.1 + +BUNDLED WITH + 4.0.3 diff --git a/ruby/examples/README.md b/ruby/examples/README.md new file mode 100644 index 0000000..db0074f --- /dev/null +++ b/ruby/examples/README.md @@ -0,0 +1,11 @@ +# LIRA Ruby Usage Example + +* RISC-V-like test architecture (register file, environment functions, operations, snippets, and a `blt` instruction) +* Serializes it to YAML, deserializes it back +* Asserts round-trip equality + +## Run + +```bash +ruby example.rb --output /path/to/output.yaml +``` diff --git a/ruby/examples/example.rb b/ruby/examples/example.rb new file mode 100644 index 0000000..78fc874 --- /dev/null +++ b/ruby/examples/example.rb @@ -0,0 +1,185 @@ +#!/usr/bin/env ruby + +$LOAD_PATH.unshift(File.expand_path('..', __dir__)) + +require 'optparse' + +require 'lira' +include Lira + +def build_test_arch + registers = (0...32).map { |i| Register.new("x#{i}") } + rf = RegisterFile.new('X', [], Shape.new(32, nil), registers) + + ld32 = EnvironmentFunction.new('ld32', ['mem.read'], [32], [32]) + st32 = EnvironmentFunction.new('st32', ['mem.write'], [32,32], []) + pc_read = EnvironmentFunction.new('pc_read', ['pc.read'], [], [32]) + pc_write = EnvironmentFunction.new('pc_write', ['pc.write'], [32], []) + + # op_extend_sign_inner_32 snippet + snip = SnippetBuilder.new('op_extend_sign_inner_32') + input_val = snip.input(0, 32) + width_val = snip.input(1, 32) + c32 = snip.const(32) + delta = snip.sub(c32, width_val) + temp = snip.lsl(input_val, delta) + r = snip.asr(temp, delta) + snip.output(r, 0) + extend_sign_inner = snip.build + + op_extend_sign = Operation.new( + 'extend_sign_inner_32', [], [32,32], [32], + semantic_base: nil, semantic_func: 'op_extend_sign_inner_32' + ) + + # op_extract_inner_32 snippet + snip2 = SnippetBuilder.new('op_extract_inner_32') + inp = snip2.input(0, 32) + lsb = snip2.input(1, 32) + width = snip2.input(2, 32) + c32 = snip2.const(32) + t1 = snip2.sub(c32, lsb) + shift_l = snip2.sub(t1, width) + temp = snip2.lsl(inp, shift_l) + shift_r = snip2.sub(c32, width) + temp2 = snip2.lsr(temp, shift_r) + snip2.output(temp2, 0) + extract_inner_snip = snip2.build + op_extract_inner = Operation.new( + 'extract_inner_32', [], [32,32,32], [32], + semantic_base: nil, semantic_func: 'op_extract_inner_32' + ) + + # op_orr_shifted_32 snippet + snip3 = SnippetBuilder.new('op_orr_shifted_32') + data = snip3.input(0, 32) + lsb = snip3.input(1, 32) + value = snip3.input(2, 32) + insert = snip3.lsl(value, lsb) + r = snip3.orr(data, insert) + snip3.output(r, 0) + orr_shifted_snip = snip3.build + op_orr_shifted = Operation.new( + 'orr_shifted_32', [], [32,32,32], [32], + semantic_base: nil, semantic_func: 'op_orr_shifted_32' + ) + + # decode helpers + def make_decode_extract(name, shift) + snip = SnippetBuilder.new(name) + enc = snip.input(0, 32) + shift_const = snip.const(shift) + shifted = snip.lsr(enc, shift_const) + r = snip.extract_low(shifted, 5) + snip.output(r, 0) + snip.build + end + + decode_rs1 = make_decode_extract('decode_b_rs1', 15) + decode_rs2 = make_decode_extract('decode_b_rs2', 20) + + # decode_b_imm + snip4 = SnippetBuilder.new('decode_b_imm') + enc = snip4.input(0, 32) + c1 = snip4.const(1); c4 = snip4.const(4); c5 = snip4.const(5); c6 = snip4.const(6) + c7 = snip4.const(7); c8 = snip4.const(8); c11 = snip4.const(11); c12 = snip4.const(12) + c13 = snip4.const(13); c25 = snip4.const(25); c31 = snip4.const(31) + + t1 = snip4.op(op_extract_inner, [enc, c31, c1]) + t2 = snip4.op(op_extract_inner, [enc, c25, c6]) + t3 = snip4.op(op_extract_inner, [enc, c8, c4]) + t4 = snip4.op(op_extract_inner, [enc, c7, c1]) + t5 = snip4.const(0) + t6 = snip4.op(op_orr_shifted, [t5, t1, c12]) + t7 = snip4.op(op_orr_shifted, [t5, t1, c11]) + t8 = snip4.op(op_orr_shifted, [t5, t1, c5]) + t9 = snip4.op(op_orr_shifted, [t5, t1, c1]) + imm_sext = snip4.op(op_extend_sign, [t9, c13]) + snip4.output(imm_sext, 0) + decode_imm = snip4.build + + # encode_b + snip5 = SnippetBuilder.new('encode_b') + rs1 = snip5.input(0, 5) + rs2 = snip5.input(1, 5) + imm = snip5.input(2, 32) + base = snip5.dyn_const('enc_base', 32) + c15 = snip5.const(15); c20 = snip5.const(20) + t1 = snip5.op(op_orr_shifted, [base, rs1, c15]) + t2 = snip5.op(op_orr_shifted, [t1, rs2, c20]) + r = snip5.orr(t2, imm) + snip5.output(r, 0) + encode_b_snip = snip5.build + + # blt instruction + enc_blt = InstructionEncoding.new(32, (0b100 << 12) + 0b1100011, 0, + ['decode_b_rs1', 'decode_b_rs2', 'decode_b_imm'], + 'encode_b', '', '') + instr_builder = InstructionBuilder.new('blt', [5,5,32], ['x1','x2','offset'], enc_blt) + x1 = instr_builder.add_input_operand(0, 5) + x2 = instr_builder.add_input_operand(1, 5) + offset = instr_builder.add_input_operand(2, 32) + v1 = instr_builder.read(rf, x1) + v2 = instr_builder.read(rf, x2) + cond = instr_builder.slt(v1, v2) + base = instr_builder.env(pc_read, [])[0] + dest = instr_builder.add(base, offset) + instr_builder.cond_env(pc_write, cond, [dest], []) + blt_instr = instr_builder.build + + arch_builder = ArchBuilder.new('test_arch', ['attr.1', 'attr.2']) + arch_builder.add_register_file(rf) + arch_builder.add_env_func(ld32).add_env_func(st32).add_env_func(pc_read).add_env_func(pc_write) + arch_builder.add_operation(op_extend_sign).add_operation(op_extract_inner).add_operation(op_orr_shifted) + arch_builder.add_snippet(extend_sign_inner).add_snippet(extract_inner_snip).add_snippet(orr_shifted_snip) + arch_builder.add_snippet(decode_rs1).add_snippet(decode_rs2).add_snippet(decode_imm).add_snippet(encode_b_snip) + arch_builder.add_instruction(blt_instr) + + arch_builder.build +end + +def main + options = {} + opt_parser = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [options]" + + opts.on("--output PATH", "Output YAML file (default: lira.yaml)") do |path| + options[:output] = path + end + + opts.on("--help", "Show this help message") do + puts opts + exit + end + end + + begin + opt_parser.parse! + rescue OptionParser::InvalidOption => e + puts e + puts opt_parser + exit 1 + end + + output_path = options[:output] || 'lira.yaml' + + arch = build_test_arch + + raw_path = "#{output_path}.raw" + ArchSerYaml.write_arch(arch, raw_path) + + canonicalize = File.expand_path('../../tools/yaml_canonicalize.py', __dir__) + system('python3', canonicalize, raw_path, output_path) + File.delete(raw_path) + + arch2 = ArchSerYaml.read_arch(output_path) + + if arch == arch2 + puts "Integration test passed" + else + puts "Integration test failed" + exit 1 + end +end + +main if __FILE__ == $0 diff --git a/ruby/lib/lira/version.rb b/ruby/lib/lira/version.rb new file mode 100644 index 0000000..4b950cc --- /dev/null +++ b/ruby/lib/lira/version.rb @@ -0,0 +1,3 @@ +module Lira + VERSION = "0.1.0" +end diff --git a/ruby/lira.gemspec b/ruby/lira.gemspec new file mode 100644 index 0000000..b77f37b --- /dev/null +++ b/ruby/lira.gemspec @@ -0,0 +1,16 @@ +require_relative 'lib/lira/version' + +Gem::Specification.new do |spec| + spec.name = "lira" + spec.version = Lira::VERSION + spec.authors = ["ProteusLab"] + spec.summary = "LIRA is a framework that provides a data-flow Intermediate Representation." + spec.description = "LIRA data-flow IR library for building, manipulating, and serializing instruction-set architecture descriptions." + + spec.required_ruby_version = ">= 3.0" + + spec.files = Dir["lira.rb", "lira/**/*.rb", "lib/**/*.rb"] + spec.require_paths = ["."] + + spec.add_dependency "yaml" +end diff --git a/ruby/lira.rb b/ruby/lira.rb new file mode 100644 index 0000000..29927c7 --- /dev/null +++ b/ruby/lira.rb @@ -0,0 +1,10 @@ +# lira.rb +require_relative 'lira/ir' +require_relative 'lira/arch' +require_relative 'lira/arch_ser_yaml' +require_relative 'lira/arch_utils' +require_relative 'lira/ir_builder' +require_relative 'lira/ir_ops' + +module Lira +end diff --git a/ruby/lira/arch.rb b/ruby/lira/arch.rb new file mode 100644 index 0000000..8775d25 --- /dev/null +++ b/ruby/lira/arch.rb @@ -0,0 +1,434 @@ +# lira/arch.rb +require 'set' + +module Lira + class Component + attr_accessor :name, :attributes + + def initialize(name, attributes = []) + @name = name + @attributes = attributes + end + + def to_h + { name: name, attributes: attributes } + end + + def self.from_h(hash) + new(hash[:name] || hash['name'], hash[:attributes] || hash['attributes'] || []) + end + + def ==(other) + other.is_a?(Component) && name == other.name && attributes == other.attributes + end + end + + class Snippet + attr_accessor :name, :seq + + def initialize(name, seq) + @name = name + @seq = seq + end + + def to_h + { name: name, seq: seq } + end + + def self.from_h(hash) + seq = StatementSeq.from_h(hash[:seq] || hash['seq']) + new(hash[:name] || hash['name'], seq) + end + + def ==(other) + other.is_a?(Snippet) && name == other.name && seq == other.seq + end + end + + class Operation < Component + attr_accessor :inputs, :outputs, :semantic_base, :semantic_func, :semantic_func_128, :semantic_table + + def initialize(name, attributes, inputs, outputs, + semantic_base: nil, semantic_func: nil, semantic_func_128: nil, semantic_table: nil) + super(name, attributes) + @inputs = inputs + @outputs = outputs + @semantic_base = semantic_base + @semantic_func = semantic_func + @semantic_func_128 = semantic_func_128 + @semantic_table = semantic_table + end + + def to_h + super.merge( + inputs: inputs, + outputs: outputs, + semantic_base: semantic_base, + semantic_func: semantic_func, + semantic_func_128: semantic_func_128, + semantic_table: semantic_table + ) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + inputs = hash[:inputs] || hash['inputs'] + outputs = hash[:outputs] || hash['outputs'] + + semantic_base = hash[:semantic_base] || hash['semantic_base'] + semantic_base = nil if semantic_base == {} + semantic_func = hash[:semantic_func] || hash['semantic_func'] + semantic_func_128 = hash[:semantic_func_128] || hash['semantic_func_128'] + semantic_func_128 = nil if semantic_func_128 == {} + semantic_table = hash[:semantic_table] || hash['semantic_table'] + semantic_table = nil if semantic_table == {} + + new(name, attributes, inputs, outputs, + semantic_base: semantic_base, + semantic_func: semantic_func, + semantic_func_128: semantic_func_128, + semantic_table: semantic_table) + end + + def ==(other) + super(other) && + other.is_a?(Operation) && + inputs == other.inputs && + outputs == other.outputs && + semantic_base == other.semantic_base && + semantic_func == other.semantic_func && + semantic_func_128 == other.semantic_func_128 && + semantic_table == other.semantic_table + end + end + + class Register < Component + def initialize(name, attributes = []) + super(name, attributes) + end + + def to_h + super.merge() + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + new(name, attributes) + end + + def ==(other) + super(other) && other.is_a?(Register) + end + end + + class RegisterFile < Component + attr_accessor :reg_size, :regs + + def initialize(name, attributes, reg_size, regs) + super(name, attributes) + @reg_size = reg_size + @regs = regs + end + + def reg_names + @regs.map(&:name) + end + + def regs_num + @regs.length + end + + def to_h + super.merge(reg_size: reg_size.to_h, regs: regs.map(&:to_h)) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + reg_size = Shape.from_h(hash[:reg_size] || hash['reg_size']) + regs_data = hash[:regs] || hash['regs'] || [] + regs = regs_data.map { |r| Register.from_h(r) } + new(name, attributes, reg_size, regs) + end + + def ==(other) + super(other) && + other.is_a?(RegisterFile) && + reg_size == other.reg_size && + regs == other.regs + end + end + + + class EnvironmentFunction < Component + attr_accessor :inputs, :outputs + + def initialize(name, attributes, inputs, outputs) + super(name, attributes) + @inputs = inputs + @outputs = outputs + end + + def to_h + super.merge(inputs: inputs, outputs: outputs) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + inputs = hash[:inputs] || hash['inputs'] + outputs = hash[:outputs] || hash['outputs'] + new(name, attributes, inputs, outputs) + end + + def ==(other) + super(other) && + other.is_a?(EnvironmentFunction) && + inputs == other.inputs && + outputs == other.outputs + end + end + + class SystemRegisterField < Component + attr_accessor :lsb, :msb + + def initialize(name, attributes, lsb, msb) + super(name, attributes) + @lsb = lsb + @msb = msb + end + + def to_h + super.merge(lsb: lsb, msb: msb) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + lsb = hash[:lsb] || hash['lsb'] + msb = hash[:msb] || hash['msb'] + new(name, attributes, lsb, msb) + end + + def ==(other) + super(other) && + other.is_a?(SystemRegisterField) && + lsb == other.lsb && + msb == other.msb + end + end + + class SystemRegister < Component + attr_accessor :size, :fields + + def initialize(name, attributes, size, fields) + super(name, attributes) + @size = size + @fields = fields + end + + def to_h + super.merge(size: size, fields: fields.map(&:to_h)) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + size = hash[:size] || hash['size'] + fields_data = hash[:fields] || hash['fields'] + fields = fields_data.map { |f| SystemRegisterField.from_h(f) } + new(name, attributes, size, fields) + end + + def ==(other) + super(other) && + other.is_a?(SystemRegister) && + size == other.size && + fields == other.fields + end + end + + class TableInt < Component + attr_accessor :values + + def initialize(name, attributes, values) + super(name, attributes) + @values = values + end + + def to_h + super.merge(values: values) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + values = hash[:values] || hash['values'] + new(name, attributes, values) + end + + def ==(other) + super(other) && + other.is_a?(TableInt) && + values == other.values + end + end + + class InstructionEncoding + attr_accessor :encoded_size, :const_encoding_part, :const_mask, :decode, :encode, + :constraint_decode, :constraint_encode + + def initialize(encoded_size, const_encoding_part, const_mask, decode, encode, constraint_decode, constraint_encode) + @encoded_size = encoded_size + @const_encoding_part = const_encoding_part + @const_mask = const_mask + @decode = decode + @encode = encode + @constraint_decode = constraint_decode + @constraint_encode = constraint_encode + end + + def to_h + { + encoded_size: encoded_size, + const_encoding_part: const_encoding_part, + const_mask: const_mask, + decode: decode, + encode: encode, + constraint_decode: constraint_decode, + constraint_encode: constraint_encode + } + end + + def self.from_h(hash) + new(hash[:encoded_size] || hash['encoded_size'], + hash[:const_encoding_part] || hash['const_encoding_part'], + hash[:const_mask] || hash['const_mask'], + hash[:decode] || hash['decode'], + hash[:encode] || hash['encode'], + hash[:constraint_decode] || hash['constraint_decode'], + hash[:constraint_encode] || hash['constraint_encode']) + end + + def ==(other) + other.is_a?(InstructionEncoding) && + encoded_size == other.encoded_size && + const_mask == other.const_mask && + const_encoding_part == other.const_encoding_part && + decode == other.decode && + encode == other.encode && + constraint_decode == other.constraint_decode && + constraint_encode == other.constraint_encode + end + end + + class Instruction < Component + attr_accessor :operand_sizes, :operand_names, :encoding, :semantic + + def initialize(name, attributes, operand_sizes, operand_names, encoding, semantic) + super(name, attributes) + @operand_sizes = operand_sizes + @operand_names = operand_names + @encoding = encoding + @semantic = semantic + end + + def to_h + super.merge( + operand_sizes: operand_sizes, + operand_names: operand_names, + encoding: encoding.to_h, + semantic: semantic + ) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + operand_sizes = hash[:operand_sizes] || hash['operand_sizes'] + operand_names = hash[:operand_names] || hash['operand_names'] + encoding = InstructionEncoding.from_h(hash[:encoding] || hash['encoding']) + semantic = StatementSeq.from_h(hash[:semantic] || hash['semantic']) + new(name, attributes, operand_sizes, operand_names, encoding, semantic) + end + + def ==(other) + super(other) && + other.is_a?(Instruction) && + operand_sizes == other.operand_sizes && + operand_names == other.operand_names && + encoding == other.encoding && + semantic == other.semantic + end + end + + class Arch < Component + attr_accessor :register_files, :system_registers, :environment_functions, + :tables_int, :operations, :snippets, :instructions + + def initialize(name, attributes, + register_files: [], + system_registers: [], + environment_functions: [], + tables_int: [], + operations: [], + snippets: [], + instructions: []) + super(name, attributes) + @register_files = register_files + @system_registers = system_registers + @environment_functions = environment_functions + @tables_int = tables_int + @operations = operations + @snippets = snippets + @instructions = instructions + end + + + def to_h + super.merge( + register_files: register_files.map(&:to_h), + system_registers: system_registers.map(&:to_h), + environment_functions: environment_functions.map(&:to_h), + tables_int: tables_int.map(&:to_h), + operations: operations.map(&:to_h), + snippets: snippets.map(&:to_h), + instructions: instructions.map(&:to_h) + ) + end + + def self.from_h(hash) + name = hash[:name] || hash['name'] + attributes = hash[:attributes] || hash['attributes'] || [] + register_files = (hash[:register_files] || hash['register_files']).map { |rf| RegisterFile.from_h(rf) } + system_registers = (hash[:system_registers] || hash['system_registers']).map { |sr| SystemRegister.from_h(sr) } + environment_functions = (hash[:environment_functions] || hash['environment_functions']).map { |ef| EnvironmentFunction.from_h(ef) } + tables_int = (hash[:tables_int] || hash['tables_int']).map { |ti| TableInt.from_h(ti) } + operations = (hash[:operations] || hash['operations']).map { |op| Operation.from_h(op) } + snippets = (hash[:snippets] || hash['snippets']).map { |sn| Snippet.from_h(sn) } + instructions = (hash[:instructions] || hash['instructions']).map { |ins| Instruction.from_h(ins) } + new(name, attributes, + register_files: register_files, + system_registers: system_registers, + environment_functions: environment_functions, + tables_int: tables_int, + operations: operations, + snippets: snippets, + instructions: instructions) + end + + def ==(other) + super(other) && + other.is_a?(Arch) && + register_files == other.register_files && + system_registers == other.system_registers && + environment_functions == other.environment_functions && + tables_int == other.tables_int && + operations == other.operations && + snippets == other.snippets && + instructions == other.instructions + end + end +end diff --git a/ruby/lira/arch_ser_yaml.rb b/ruby/lira/arch_ser_yaml.rb new file mode 100644 index 0000000..ba68c65 --- /dev/null +++ b/ruby/lira/arch_ser_yaml.rb @@ -0,0 +1,60 @@ +# lira/arch_ser_yaml.rb +require 'yaml' +require_relative 'ir' +require_relative 'arch' + +module Lira + module ArchSerYaml + module_function + + def to_serializable(obj) + case obj + when nil + nil + when StatementSeq + IrSerTxt.serialize_statement_seq(obj) + when Array + obj.map { |item| to_serializable(item) } + when Hash + obj.transform_keys(&:to_s).transform_values { |v| to_serializable(v) } + else + if obj.respond_to?(:to_h) + obj.to_h.transform_keys(&:to_s).transform_values { |v| to_serializable(v) } + else + obj + end + end + end + + def from_serializable(klass, data, item_class = nil) + if klass == Array + if item_class + return data.map { |elem| from_serializable(item_class, elem) } + else + return data.map { |elem| from_serializable(Object, elem) } + end + end + + if klass == StatementSeq + return IrSerTxt.deserialize_statement_seq(data) + end + + if klass.respond_to?(:from_h) + transformed = data.transform_keys(&:to_sym).transform_values { |v| from_serializable(Object, v) } + klass.from_h(transformed) + else + data + end + end + + def write_arch(arch, filepath) + data = to_serializable(arch) + File.write(filepath, YAML.dump(data)) + end + + def read_arch(filepath) + data = YAML.load_file(filepath) + from_serializable(Arch, data) + end + end +end diff --git a/ruby/lira/arch_utils.rb b/ruby/lira/arch_utils.rb new file mode 100644 index 0000000..3484a42 --- /dev/null +++ b/ruby/lira/arch_utils.rb @@ -0,0 +1,18 @@ +# lira/arch_utils.rb +require_relative 'arch' + +module Lira + ArchIndex = Struct.new(:rf, :sr, :env, :tables, :op, :snippet, :instr) + + def self.build_arch_index(arch) + ArchIndex.new( + arch.register_files.to_h { |rf| [rf.name, rf] }, + arch.system_registers.to_h { |sr| [sr.name, sr] }, + arch.environment_functions.to_h { |ef| [ef.name, ef] }, + arch.tables_int.to_h { |ti| [ti.name, ti] }, + arch.operations.to_h { |op| [op.name, op] }, + arch.snippets.to_h { |sn| [sn.name, sn] }, + arch.instructions.to_h { |ins| [ins.name, ins] } + ) + end +end diff --git a/ruby/lira/ir.rb b/ruby/lira/ir.rb new file mode 100644 index 0000000..5d83533 --- /dev/null +++ b/ruby/lira/ir.rb @@ -0,0 +1,147 @@ +# lira/ir.rb +require 'set' + +module Lira + class Shape + attr_accessor :lanes_base, :lanes_mult + + def initialize(lanes_base, lanes_mult) + @lanes_base = lanes_base + @lanes_mult = lanes_mult + end + + def to_h + { lanes_base: lanes_base, lanes_mult: lanes_mult } + end + + def self.from_h(hash) + lanes_base = hash[:lanes_base] || hash['lanes_base'] + lanes_mult = hash[:lanes_mult] || hash['lanes_mult'] + lanes_mult = nil if lanes_mult == {} + new(lanes_base, lanes_mult) + end + + def ==(other) + other.is_a?(Shape) && lanes_base == other.lanes_base && lanes_mult == other.lanes_mult + end + end + + class Statement + attr_accessor :shape, :outputs, :outputs_types, :kind, :specifier, :inputs + + def initialize(shape, outputs, outputs_types, kind, specifier, inputs) + @shape = shape + @outputs = outputs + @outputs_types = outputs_types + @kind = kind + @specifier = specifier + @inputs = inputs + end + + def to_h + { + shape: shape.to_h, + outputs: outputs, + outputs_types: outputs_types, + kind: kind, + specifier: specifier, + inputs: inputs + } + end + + def self.from_h(hash) + shape = Shape.from_h(hash[:shape] || hash['shape']) + new(shape, + hash[:outputs] || hash['outputs'], + hash[:outputs_types] || hash['outputs_types'], + hash[:kind] || hash['kind'], + hash[:specifier] || hash['specifier'], + hash[:inputs] || hash['inputs']) + end + + def ==(other) + other.is_a?(Statement) && + shape == other.shape && + outputs == other.outputs && + outputs_types == other.outputs_types && + kind == other.kind && + specifier == other.specifier && + inputs == other.inputs + end + + def input(id, seq) + target = @inputs[id] + seq.stmts.each do |stmt| + return stmt if stmt.outputs.include?(target) + end + raise "input #{target} not found" + end + end + + class StatementSeq + attr_accessor :stmts + + def initialize(stmts) + @stmts = stmts + end + + def to_h + { stmts: stmts.map(&:to_h) } + end + + def self.from_h(data) + if data.is_a?(String) + IrSerTxt.deserialize_statement_seq(data) + else + stmts_data = data[:stmts] || data['stmts'] + stmts = stmts_data.map { |stmt_h| Statement.from_h(stmt_h) } + new(stmts) + end + end + + def ==(other) + other.is_a?(StatementSeq) && stmts == other.stmts + end + end + + module IrSerTxt + def self.serialize_shape(shape) + "#{shape.lanes_base}#{shape.lanes_mult || ''}" + end + + def self.deserialize_shape(s) + m = s.match(/\A(\d+)(.*)\z/) + raise "invalid shape: #{s}" unless m + Shape.new(m[1].to_i, m[2].empty? ? nil : m[2]) + end + + def self.serialize_statement(stmt) + shape_str = serialize_shape(stmt.shape) + out_parts = stmt.outputs_types.zip(stmt.outputs).flat_map { |typ, name| [typ.to_s, name] } + parts = [shape_str] + out_parts + ['=', stmt.kind, stmt.specifier] + stmt.inputs + parts.join(' ') + end + + def self.deserialize_statement(s) + m = s.match(/\A(\w+)\s+(.*?)\s*=\s*(\w+)\s+(\S+)\s*(.*)\z/) + raise "invalid statement: #{s}" unless m + shape_str, outputs_str, kind, specifier, inputs_str = m.captures + shape = deserialize_shape(shape_str) + pairs = outputs_str.split + outputs_types = (0...pairs.length).step(2).map { |i| pairs[i].to_i } + outputs = (0...pairs.length).step(2).map { |i| pairs[i+1] } + inputs = inputs_str.empty? ? [] : inputs_str.split + Statement.new(shape, outputs, outputs_types, kind, specifier, inputs) + end + + def self.serialize_statement_seq(seq) + seq.stmts.map { |s| serialize_statement(s) + ";\n" }.join + end + + def self.deserialize_statement_seq(s) + raw_stmts = s.split(';').map(&:strip).reject(&:empty?) + stmts = raw_stmts.map { |raw| deserialize_statement(raw) } + StatementSeq.new(stmts) + end + end +end diff --git a/ruby/lira/ir_builder.rb b/ruby/lira/ir_builder.rb new file mode 100644 index 0000000..6e8850d --- /dev/null +++ b/ruby/lira/ir_builder.rb @@ -0,0 +1,694 @@ +# lira/ir_builder.rb +require_relative 'ir' +require_relative 'arch' +require_relative 'ir_ops' + +module Lira + class Value + attr_accessor :name, :width + + def initialize(name, width = 32) + @name = name + @width = width + @shape = Shape.new(1, nil) + end + + def to_s + name + end + + def inspect + "Value(#{name}, #{width})" + end + + def ==(other) + other.is_a?(Value) && name == other.name && width == other.width + end + end + + class SeqBuilder + attr_reader :stmts, :temp_counter + + def initialize + @stmts = [] + @temp_counter = 0 + end + + def new_temp(width = 32) + @temp_counter += 1 + Value.new("_t#{@temp_counter}", width) + end + + def emit_op(op, inputs, out_bits) + out = new_temp(out_bits) + add_op(op, inputs, [out.name]) + out + end + + def check_width_match(a, b) + raise "width mismatch: #{a.width} != #{b.width}" if a.width != b.width + end + + # ------------------------------------------------------------------ + # NOTE: Building ruby/lira/ir_ops.rb objects + # ------------------------------------------------------------------ + def add(a, b) + check_width_match(a, b) + emit_op(Add.new(a.width), [a.name, b.name], a.width) + end + + def sub(a, b) + check_width_match(a, b) + emit_op(Sub.new(a.width), [a.name, b.name], a.width) + end + + def mul(a, b) + check_width_match(a, b) + emit_op(Mul.new(a.width), [a.name, b.name], a.width) + end + + def and_(a, b) + check_width_match(a, b) + emit_op(And.new(a.width), [a.name, b.name], a.width) + end + + def orr(a, b) + check_width_match(a, b) + emit_op(Orr.new(a.width), [a.name, b.name], a.width) + end + + def xor(a, b) + check_width_match(a, b) + emit_op(Xor.new(a.width), [a.name, b.name], a.width) + end + + def lsl(a, b) + check_width_match(a, b) + emit_op(Lsl.new(a.width), [a.name, b.name], a.width) + end + + def lsr(a, b) + check_width_match(a, b) + emit_op(Lsr.new(a.width), [a.name, b.name], a.width) + end + + def asr(a, b) + check_width_match(a, b) + emit_op(Asr.new(a.width), [a.name, b.name], a.width) + end + + def slt(a, b) + check_width_match(a, b) + emit_op(Slt.new(a.width), [a.name, b.name], 1) + end + + def sle(a, b) + check_width_match(a, b) + emit_op(Sle.new(a.width), [a.name, b.name], 1) + end + + def sgt(a, b) + check_width_match(a, b) + emit_op(Sgt.new(a.width), [a.name, b.name], 1) + end + + def sge(a, b) + check_width_match(a, b) + emit_op(Sge.new(a.width), [a.name, b.name], 1) + end + + def ult(a, b) + check_width_match(a, b) + emit_op(Ult.new(a.width), [a.name, b.name], 1) + end + + def ule(a, b) + check_width_match(a, b) + emit_op(Ule.new(a.width), [a.name, b.name], 1) + end + + def ugt(a, b) + check_width_match(a, b) + emit_op(Ugt.new(a.width), [a.name, b.name], 1) + end + + def uge(a, b) + check_width_match(a, b) + emit_op(Uge.new(a.width), [a.name, b.name], 1) + end + + def eq(a, b) + check_width_match(a, b) + emit_op(Eq.new(a.width), [a.name, b.name], 1) + end + + def ne(a, b) + check_width_match(a, b) + emit_op(Ne.new(a.width), [a.name, b.name], 1) + end + + def rem_u(a, b) + check_width_match(a, b) + emit_op(RemU.new(a.width), [a.name, b.name], a.width) + end + + def rem_s(a, b) + check_width_match(a, b) + emit_op(RemS.new(a.width), [a.name, b.name], a.width) + end + + def ror(a, b) + check_width_match(a, b) + emit_op(Ror.new(a.width), [a.name, b.name], a.width) + end + + def rol(a, b) + check_width_match(a, b) + emit_op(Rol.new(a.width), [a.name, b.name], a.width) + end + + def add_overflow(a, b) + check_width_match(a, b) + emit_op(AddOverflow.new(a.width), [a.name, b.name], 1) + end + + def sub_overflow(a, b) + check_width_match(a, b) + emit_op(SubOverflow.new(a.width), [a.name, b.name], 1) + end + + def not_(a) + emit_op(Not.new(a.width), [a.name], a.width) + end + + def neg(a) + emit_op(Neg.new(a.width), [a.name], a.width) + end + + def popcnt(a) + emit_op(Popcnt.new(a.width), [a.name], a.width) + end + + def ctz(a) + emit_op(Ctz.new(a.width), [a.name], a.width) + end + + def clz(a) + emit_op(Clz.new(a.width), [a.name], a.width) + end + + def reverse(a) + emit_op(Reverse.new(a.width), [a.name], a.width) + end + + def extend_sign(a, to_width) + raise "extend_sign: input width #{a.width} >= output width #{to_width}" if a.width >= to_width + emit_op(ExtendSign.new(a.width, to_width), [a.name], to_width) + end + + def extend_zero(a, to_width) + raise "extend_zero: input width #{a.width} >= output width #{to_width}" if a.width >= to_width + emit_op(ExtendZero.new(a.width, to_width), [a.name], to_width) + end + + def extract_low(a, out_width) + raise "extract_low: output width #{out_width} > input width #{a.width}" if out_width > a.width + emit_op(ExtractLow.new(a.width, out_width), [a.name], out_width) + end + + def div_u(a, b, default) + check_width_match(a, b) + raise "div_u: default width mismatch" if a.width != default.width + emit_op(DivU.new(a.width), [a.name, b.name, default.name], a.width) + end + + def div_s(a, b, default) + check_width_match(a, b) + raise "div_s: default width mismatch" if a.width != default.width + emit_op(DivS.new(a.width), [a.name, b.name, default.name], a.width) + end + + def select(cond, true_val, false_val) + raise "select: condition must be 1-bit" unless cond.width == 1 + raise "select: true/false widths mismatch" unless true_val.width == false_val.width + emit_op( + Select.new(true_val.width), + [cond.name, true_val.name, false_val.name], + true_val.width + ) + end + + + # ------------------------------------------------------------------ + # NOTE: Building ruby/lira/ir_std.rb objects + # ------------------------------------------------------------------ + def read(rf, rsi, shape = Shape.new(1, nil)) + width = rf.reg_size.lanes_base + out = new_temp(width) + stmt = Statement.new(shape, [out.name], [width], 'read', rf.name, [rsi.name]) + @stmts << stmt + out + end + + def write(rf, rsi, value, shape = Shape.new(1, nil)) + stmt = Statement.new(shape, [], [], 'write', rf.name, [rsi.name, value.name]) + @stmts << stmt + end + + def const(value, width = 32) + out = new_temp(width) + stmt = Statement.new(Shape.new(1, nil), [out.name], [width], 'const', value.to_s, []) + @stmts << stmt + out + end + + def dyn_const(name, width = 32) + out = new_temp(width) + stmt = Statement.new(Shape.new(1, nil), [out.name], [width], 'dyn_const', name, []) + @stmts << stmt + out + end + + def env(env_func, inputs) + outputs = env_func.outputs.map { |w| new_temp(w) } + stmt = Statement.new( + Shape.new(1, nil), + outputs.map(&:name), + env_func.outputs, + 'env', + env_func.name, + inputs.map(&:name) + ) + @stmts << stmt + outputs + end + + def cond_env(env_func, cond, inputs, on_false) + outputs = env_func.outputs.map { |w| new_temp(w) } + all_inputs = [cond.name] + inputs.map(&:name) + on_false.map(&:name) + stmt = Statement.new( + Shape.new(1, nil), + outputs.map(&:name), + env_func.outputs, + 'cond_env', + env_func.name, + all_inputs + ) + @stmts << stmt + outputs + end + + def input(idx, width = 32) + out = new_temp(width) + stmt = Statement.new(Shape.new(1, nil), [out.name], [width], 'input', idx.to_s, []) + @stmts << stmt + out + end + + def output(value, idx) + stmt = Statement.new(Shape.new(1, nil), [], [], 'output', idx.to_s, [value.name]) + @stmts << stmt + end + + def add_op(operation, inputs, outputs, shape = Shape.new(1, nil)) + stmt = Statement.new(shape, outputs, operation.outputs, 'op', operation.name, inputs) + @stmts << stmt + end + + def op(operation, inputs) + raise "Operation #{operation.name} has #{operation.outputs.size} outputs, use op_multi" if operation.outputs.size != 1 + emit_op(operation, inputs.map(&:name), operation.outputs.first) + end + + def op_multi(operation, inputs) + outputs = operation.outputs.map { |w| new_temp(w) } + add_op(operation, inputs.map(&:name), outputs.map(&:name)) + outputs + end + + def build + StatementSeq.new(@stmts) + end + + def +(other) + @stmts.concat(other.stmts) + @temp_counter = [@temp_counter, other.temp_counter].max + self + end + end + + class BaseBuilder + attr_reader :seq + + def initialize + @seq = SeqBuilder.new + @op_cache = {} + end + + def cache_op(op_class, *args) + op = op_class.new(*args) + @op_cache[op.name] ||= op + end + + def operations_map + @op_cache.each_value.to_h { |op| [op.name, op] } + end + + # ------------------------------------------------------------------ + # NOTE: Building ruby/lira/ir_ops.rb objects + # ------------------------------------------------------------------ + def add(a, b) + cache_op(Add, a.width) + @seq.add(a, b) + end + + def sub(a, b) + cache_op(Sub, a.width) + @seq.sub(a, b) + end + + def mul(a, b) + cache_op(Mul, a.width) + @seq.mul(a, b) + end + + def and_(a, b) + cache_op(And, a.width) + @seq.and_(a, b) + end + + def orr(a, b) + cache_op(Orr, a.width) + @seq.orr(a, b) + end + + def xor(a, b) + cache_op(Xor, a.width) + @seq.xor(a, b) + end + + def lsl(a, b) + cache_op(Lsl, a.width) + @seq.lsl(a, b) + end + + def lsr(a, b) + cache_op(Lsr, a.width) + @seq.lsr(a, b) + end + + def asr(a, b) + cache_op(Asr, a.width) + @seq.asr(a, b) + end + + def slt(a, b) + cache_op(Slt, a.width) + @seq.slt(a, b) + end + + def sle(a, b) + cache_op(Sle, a.width) + @seq.sle(a, b) + end + + def sgt(a, b) + cache_op(Sgt, a.width) + @seq.sgt(a, b) + end + + def sge(a, b) + cache_op(Sge, a.width) + @seq.sge(a, b) + end + + def ult(a, b) + cache_op(Ult, a.width) + @seq.ult(a, b) + end + + def ule(a, b) + cache_op(Ule, a.width) + @seq.ule(a, b) + end + + def ugt(a, b) + cache_op(Ugt, a.width) + @seq.ugt(a, b) + end + + def uge(a, b) + cache_op(Uge, a.width) + @seq.uge(a, b) + end + + def eq(a, b) + cache_op(Eq, a.width) + @seq.eq(a, b) + end + + def ne(a, b) + cache_op(Ne, a.width) + @seq.ne(a, b) + end + + def rem_u(a, b) + cache_op(RemU, a.width) + @seq.rem_u(a, b) + end + + def rem_s(a, b) + cache_op(RemS, a.width) + @seq.rem_s(a, b) + end + + def ror(a, b) + cache_op(Ror, a.width) + @seq.ror(a, b) + end + + def rol(a, b) + cache_op(Rol, a.width) + @seq.rol(a, b) + end + + def add_overflow(a, b) + cache_op(AddOverflow, a.width) + @seq.add_overflow(a, b) + end + + def sub_overflow(a, b) + cache_op(SubOverflow, a.width) + @seq.sub_overflow(a, b) + end + + def not_(a) + cache_op(Not, a.width) + @seq.not_(a) + end + + def neg(a) + cache_op(Neg, a.width) + @seq.neg(a) + end + + def popcnt(a) + cache_op(Popcnt, a.width) + @seq.popcnt(a) + end + + def ctz(a) + cache_op(Ctz, a.width) + @seq.ctz(a) + end + + def clz(a) + cache_op(Clz, a.width) + @seq.clz(a) + end + + def reverse(a) + cache_op(Reverse, a.width) + @seq.reverse(a) + end + + def extend_sign(a, to_width) + cache_op(ExtendSign, a.width, to_width) + @seq.extend_sign(a, to_width) + end + + def extend_zero(a, to_width) + cache_op(ExtendZero, a.width, to_width) + @seq.extend_zero(a, to_width) + end + + def extract_low(a, out_width) + cache_op(ExtractLow, a.width, out_width) + @seq.extract_low(a, out_width) + end + + def div_u(a, b, default) + cache_op(DivU, a.width) + @seq.div_u(a, b, default) + end + + def div_s(a, b, default) + cache_op(DivS, a.width) + @seq.div_s(a, b, default) + end + + def select(cond, true_val, false_val) + cache_op(Select, true_val.width) + @seq.select(cond, true_val, false_val) + end + + # ------------------------------------------------------------------ + # NOTE: Building ruby/lira/ir_std.rb objects + # ------------------------------------------------------------------ + def read(rf, rsi, shape = Shape.new(1, nil)) + @seq.read(rf, rsi, shape) + end + + def write(rf, rsi, value, shape = Shape.new(1, nil)) + @seq.write(rf, rsi, value, shape) + end + + def const(value, width = 32) + @seq.const(value, width) + end + + def dyn_const(name, width = 32) + @seq.dyn_const(name, width) + end + + def env(env_func, inputs) + @seq.env(env_func, inputs) + end + + def cond_env(env_func, cond, inputs, on_false) + @seq.cond_env(env_func, cond, inputs, on_false) + end + + def input(idx, width = 32) + @seq.input(idx, width) + end + + def output(value, idx) + @seq.output(value, idx) + end + + def op(operation, inputs) + @op_cache[operation.name] ||= operation + @seq.op(operation, inputs) + end + + def op_multi(operation, inputs) + @seq.op_multi(operation, inputs) + end + end + + class SnippetBuilder < BaseBuilder + attr_reader :name + + def initialize(name) + super() + @name = name + end + + def build + Snippet.new(@name, @seq.build) + end + end + + class InstructionBuilder < BaseBuilder + attr_reader :name, :operand_sizes, :operand_names, :encoding + + def initialize(name, operand_sizes, operand_names, encoding) + super() + @name = name + @operand_sizes = operand_sizes + @operand_names = operand_names + @encoding = encoding + end + + def add_input_operand(idx, width = nil) + w = width || (@operand_sizes[idx] if idx < @operand_sizes.size) || 32 + input(idx, w) + end + + def build + Instruction.new( + @name, [], + @operand_sizes, @operand_names, + @encoding, + @seq.build + ) + end + end + + class ArchBuilder + attr_reader :name, :attributes, :register_files, :system_registers, + :environment_functions, :tables_int, :operations, + :snippets, :instructions + + def initialize(name, attributes = []) + @name = name + @attributes = attributes + @register_files = [] + @system_registers = [] + @environment_functions = [] + @tables_int = [] + @operations = [] + @snippets = [] + @instructions = [] + end + + def add_register_file(rf) + @register_files << rf + self + end + + def add_system_register(sr) + @system_registers << sr + self + end + + def add_env_func(env) + @environment_functions << env + self + end + + def add_table_int(table) + @tables_int << table + self + end + + def add_operation(op) + @operations << op + self + end + + def add_snippet(snippet) + @snippets << snippet + self + end + + def add_instruction(instr) + @instructions << instr + self + end + + def build + Arch.new( + @name, @attributes, + register_files: @register_files, + system_registers: @system_registers, + environment_functions: @environment_functions, + tables_int: @tables_int, + operations: @operations, + snippets: @snippets, + instructions: @instructions + ) + end + end +end diff --git a/ruby/lira/ir_ops.rb b/ruby/lira/ir_ops.rb new file mode 100644 index 0000000..1afc94e --- /dev/null +++ b/ruby/lira/ir_ops.rb @@ -0,0 +1,301 @@ +# lira/ir_ops.rb +require_relative 'arch' + +module Lira + class TypeCheckError < StandardError; end + + module BaseOp + NOT = :not + NEG = :neg + ADD = :add + SUB = :sub + MUL = :mul + AND = :and + ORR = :orr + XOR = :xor + LSL = :lsl + LSR = :lsr + ASR = :asr + EQ = :eq + NE = :ne + SLT = :slt + SLE = :sle + SGT = :sgt + SGE = :sge + ULT = :ult + ULE = :ule + UGT = :ugt + UGE = :uge + DIV_U = :div_u + DIV_S = :div_s + REM_U = :rem_u + REM_S = :rem_s + ROR = :ror + ROL = :rol + ADD_OVERFLOW = :add_overflow + SUB_OVERFLOW = :sub_overflow + SELECT = :select + EXTEND_SIGN = :extend_sign + EXTEND_ZERO = :extend_zero + EXTRACT_LOW = :extract_low + POPCNT = :popcnt + CTZ = :ctz + CLZ = :clz + REVERSE = :reverse + end + + class UnaryOp < Operation + def initialize(out_bits, semantic_base, name: nil) + name ||= "#{semantic_base}_#{out_bits}" + super(name, [], [out_bits], [out_bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input width must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'output width must be positive' unless outputs[0] > 0 + raise TypeCheckError, 'input != output' unless inputs[0] == outputs[0] + end + end + + class BinaryOp < Operation + def initialize(bits, semantic_base, name: nil) + name ||= "#{semantic_base}_#{bits}" + super(name, [], [bits, bits], [bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input[0] must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'input[1] must be positive' unless inputs[1] > 0 + raise TypeCheckError, 'output must be positive' unless outputs[0] > 0 + unless inputs[0] == inputs[1] && inputs[0] == outputs[0] + raise TypeCheckError, "mismatched widths: #{inputs} -> #{outputs[0]}" + end + end + end + + class CmpOp < Operation + def initialize(bits, semantic_base, out_bits = 1, name: nil) + name ||= "#{semantic_base}_#{bits}" + super(name, [], [bits, bits], [out_bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input[0] must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'input[1] must be positive' unless inputs[1] > 0 + raise TypeCheckError, 'output must be positive' unless outputs[0] > 0 + raise TypeCheckError, 'input widths differ' unless inputs[0] == inputs[1] + end + end + + class TernaryOp < Operation + def initialize(bits, semantic_base, name: nil) + name ||= "#{semantic_base}_#{bits}" + super(name, [], [bits, bits, bits], [bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input[0] must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'input[1] must be positive' unless inputs[1] > 0 + raise TypeCheckError, 'input[2] must be positive' unless inputs[2] > 0 + raise TypeCheckError, 'output must be positive' unless outputs[0] > 0 + unless inputs[0] == inputs[1] && inputs[0] == inputs[2] && inputs[0] == outputs[0] + raise TypeCheckError, "mismatched widths: #{inputs} -> #{outputs[0]}" + end + end + end + + class ExtendOp < Operation + def initialize(in_bits, out_bits, semantic_base, name: nil) + name ||= "#{semantic_base}_#{in_bits}_to_#{out_bits}" + super(name, [], [in_bits], [out_bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input width must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'output width must be positive' unless outputs[0] > 0 + raise TypeCheckError, 'input >= output' unless inputs[0] < outputs[0] + end + end + + class ExtractLowOp < Operation + def initialize(in_bits, out_bits, semantic_base, name: nil) + name ||= "#{semantic_base}_#{in_bits}_to_#{out_bits}" + super(name, [], [in_bits], [out_bits], + semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'input width must be positive' unless inputs[0] > 0 + raise TypeCheckError, 'output width must be positive' unless outputs[0] > 0 + raise TypeCheckError, 'output > input' unless outputs[0] <= inputs[0] + end + end + + class Not < UnaryOp + def initialize(bits); super(bits, BaseOp::NOT); end + end + + class Neg < UnaryOp + def initialize(bits); super(bits, BaseOp::NEG); end + end + + class Add < BinaryOp + def initialize(bits); super(bits, BaseOp::ADD); end + end + + class Sub < BinaryOp + def initialize(bits); super(bits, BaseOp::SUB); end + end + + class Mul < BinaryOp + def initialize(bits); super(bits, BaseOp::MUL); end + end + + class And < BinaryOp + def initialize(bits); super(bits, BaseOp::AND); end + end + + class Orr < BinaryOp + def initialize(bits); super(bits, BaseOp::ORR); end + end + + class Xor < BinaryOp + def initialize(bits); super(bits, BaseOp::XOR); end + end + + class Lsl < BinaryOp + def initialize(bits); super(bits, BaseOp::LSL); end + end + + class Lsr < BinaryOp + def initialize(bits); super(bits, BaseOp::LSR); end + end + + class Asr < BinaryOp + def initialize(bits); super(bits, BaseOp::ASR); end + end + + class Eq < CmpOp + def initialize(bits); super(bits, BaseOp::EQ); end + end + + class Ne < CmpOp + def initialize(bits); super(bits, BaseOp::NE); end + end + + class Slt < CmpOp + def initialize(bits); super(bits, BaseOp::SLT); end + end + + class Sle < CmpOp + def initialize(bits); super(bits, BaseOp::SLE); end + end + + class Sgt < CmpOp + def initialize(bits); super(bits, BaseOp::SGT); end + end + + class Sge < CmpOp + def initialize(bits); super(bits, BaseOp::SGE); end + end + + class Ult < CmpOp + def initialize(bits); super(bits, BaseOp::ULT); end + end + + class Ule < CmpOp + def initialize(bits); super(bits, BaseOp::ULE); end + end + + class Ugt < CmpOp + def initialize(bits); super(bits, BaseOp::UGT); end + end + + class Uge < CmpOp + def initialize(bits); super(bits, BaseOp::UGE); end + end + + class ExtendSign < ExtendOp + def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTEND_SIGN); end + end + + class ExtendZero < ExtendOp + def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTEND_ZERO); end + end + + class ExtractLow < ExtractLowOp + def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTRACT_LOW); end + end + + class Popcnt < UnaryOp + def initialize(bits); super(bits, BaseOp::POPCNT); end + end + + class Ctz < UnaryOp + def initialize(bits); super(bits, BaseOp::CTZ); end + end + + class Clz < UnaryOp + def initialize(bits); super(bits, BaseOp::CLZ); end + end + + class Reverse < UnaryOp + def initialize(bits); super(bits, BaseOp::REVERSE); end + end + + class RemU < BinaryOp + def initialize(bits); super(bits, BaseOp::REM_U); end + end + + class RemS < BinaryOp + def initialize(bits); super(bits, BaseOp::REM_S); end + end + + class Ror < BinaryOp + def initialize(bits); super(bits, BaseOp::ROR); end + end + + class Rol < BinaryOp + def initialize(bits); super(bits, BaseOp::ROL); end + end + + class AddOverflow < CmpOp + def initialize(bits); super(bits, BaseOp::ADD_OVERFLOW, out_bits: 1); end + end + + class SubOverflow < CmpOp + def initialize(bits); super(bits, BaseOp::SUB_OVERFLOW, out_bits: 1); end + end + + class DivU < TernaryOp + def initialize(bits); super(bits, BaseOp::DIV_U); end + end + + class DivS < TernaryOp + def initialize(bits); super(bits, BaseOp::DIV_S); end + end + + class Select < Operation + def initialize(bits) + name = "select_#{bits}" + super(name, [], [1, bits, bits], [bits], + semantic_base: BaseOp::SELECT, semantic_func: nil, semantic_table: nil) + check_signature + end + + def check_signature + raise TypeCheckError, 'true/false branches mismatch' unless inputs[1] == inputs[2] && inputs[1] == outputs[0] + end + end +end diff --git a/ruby/lira/ir_ser_txt.rb b/ruby/lira/ir_ser_txt.rb new file mode 100644 index 0000000..e381376 --- /dev/null +++ b/ruby/lira/ir_ser_txt.rb @@ -0,0 +1,39 @@ +# lira/ir_ser_txt.rb +require_relative 'ir' + +def serialize_shape(shape) + "#{shape.lanes_base}#{shape.lanes_mult}" +end + +def deserialize_shape(s) + m = s.match(/^(\d+)(.*)$/) + Shape.new(m[1].to_i, m[2].empty? ? nil : m[2]) +end + +def serialize_statement(stmt) + shape_str = serialize_shape(stmt.shape) + out_parts = stmt.outputs_types.zip(stmt.outputs).flat_map { |typ, name| [typ.to_s, name] } + parts = [shape_str] + out_parts + ['=', stmt.kind, stmt.specifier] + stmt.inputs + parts.join(' ') +end + +def deserialize_statement(s) + m = s.match(/^(\w+)\s+(.*?)\s*=\s*(\w+)\s+(\S+)\s*(.*)$/) + shape_str, outputs_str, kind, specifier, inputs_str = m.captures + shape = deserialize_shape(shape_str) + pairs = outputs_str.split + outputs_types = (0...pairs.length).step(2).map { |i| pairs[i].to_i } + outputs = (1...pairs.length).step(2).map { |i| pairs[i] } + inputs = inputs_str.empty? ? [] : inputs_str.split + Statement.new(shape, outputs, outputs_types, kind, specifier, inputs) +end + +def serialize_statement_seq(seq) + seq.stmts.map { |s| "#{serialize_statement(s)};\n" }.join +end + +def deserialize_statement_seq(s) + raw_stmts = s.split(';').map(&:strip).reject(&:empty?) + stmts = raw_stmts.map { |raw| deserialize_statement(raw) } + StatementSeq.new(stmts) +end diff --git a/ruby/lira/ir_std.rb b/ruby/lira/ir_std.rb new file mode 100644 index 0000000..84961c1 --- /dev/null +++ b/ruby/lira/ir_std.rb @@ -0,0 +1,94 @@ +# lira/ir_std.rb +require_relative 'ir' +require_relative 'arch' + +class StmtInput + attr_accessor :id_ + + def initialize(id_) + @id_ = id_ + end +end + +class StmtOutput + attr_accessor :id_, :value + + def initialize(id_, value) + @id_ = id_ + @value = value + end +end + +class StmtRead + attr_accessor :rf, :rsi + + def initialize(rf, rsi) + @rf = rf + @rsi = rsi + end +end + +class StmtWrite + attr_accessor :rf, :rsi, :value + + def initialize(rf, rsi, value) + @rf = rf + @rsi = rsi + @value = value + end +end + +class StmtOp + attr_accessor :op, :args + + def initialize(op, args) + @op = op + @args = args + end +end + +class StmtEnv + attr_accessor :env, :args + + def initialize(env, args) + @env = env + @args = args + end +end + +class CondEnv + attr_accessor :env, :cond, :on_false, :inputs + + def initialize(env, cond, on_false, inputs) + @env = env + @cond = cond + @on_false = on_false + @inputs = inputs + end +end + +class StmtIndex; end +class StmtConst + attr_accessor :value + def initialize(value); @value = value; end +end +class StmtDynConst + attr_accessor :name + def initialize(name); @name = name; end +end +class StmtGather + attr_accessor :value, :index, :default + def initialize(value, index, default); @value = value; @index = index; @default = default; end +end +class StmtFold + attr_accessor :op, :args + def initialize(op, args); @op = op; @args = args; end +end +class StmtScan + attr_accessor :op, :args + def initialize(op, args); @op = op; @args = args; end +end +class StmtAlias + attr_accessor :semantic, :args + def initialize(semantic, args); @semantic = semantic; @args = args; end +end diff --git a/ruby/tests/README.md b/ruby/tests/README.md new file mode 100644 index 0000000..534f184 --- /dev/null +++ b/ruby/tests/README.md @@ -0,0 +1,21 @@ +# LIRA Ruby Tests + +## Unit tests + +```bash +ruby -I ruby -I ruby/lib ruby/tests/unit/*.rb +``` + +## Unit coverage + +```bash +ruby -r simplecov -I ruby -I ruby/lib -e 'SimpleCov.start; Dir["ruby/tests/unit/*.rb"].each { |f| require_relative f }' +``` + +## Integration test + +Reads `tests/integration/reference.yaml`, writes back via [normalization script](../../tools/yaml_canonicalize.py), asserts round-trip equality. + +```bash +ruby -I ruby -I ruby/lib ruby/tests/integration/test_integration.rb +``` diff --git a/ruby/tests/integration/test_integration.rb b/ruby/tests/integration/test_integration.rb new file mode 100644 index 0000000..b838a0b --- /dev/null +++ b/ruby/tests/integration/test_integration.rb @@ -0,0 +1,28 @@ +$LOAD_PATH.unshift(File.expand_path('../..', __dir__)) +require 'lira' +require 'minitest/autorun' + +include Lira + +PROJECT_ROOT = File.expand_path('../../..', __dir__) +REFERENCE = File.join(PROJECT_ROOT, 'tests', 'integration', 'reference.yaml') +CANONICALIZE = File.join(PROJECT_ROOT, 'tools', 'yaml_canonicalize.py') +OUTPUT = File.join(__dir__, 'integration.yaml') + +class TestIntegration < Minitest::Test + def test_roundtrip_from_reference + ref_arch = ArchSerYaml.read_arch(REFERENCE) + + raw = "#{OUTPUT}.raw" + begin + ArchSerYaml.write_arch(ref_arch, raw) + system('python3', CANONICALIZE, raw, OUTPUT) + File.delete(raw) + + arch2 = ArchSerYaml.read_arch(OUTPUT) + assert_equal ref_arch, arch2, 'round-trip equality failed' + ensure + File.delete(raw) if File.exist?(raw) + end + end +end diff --git a/ruby/tests/unit/test_arch_ser_yaml.rb b/ruby/tests/unit/test_arch_ser_yaml.rb new file mode 100644 index 0000000..182ca1f --- /dev/null +++ b/ruby/tests/unit/test_arch_ser_yaml.rb @@ -0,0 +1,143 @@ +$LOAD_PATH.unshift(File.expand_path('../..', __dir__)) +require 'lira' +require 'minitest/autorun' +require 'tempfile' + +include Lira + +class TestArchSerYaml < Minitest::Test + def setup + @tmp = Tempfile.new(['lira_test', '.yaml']) + end + + def teardown + @tmp.close + @tmp.unlink + end + + def test_minimal_arch_with_builder + rf = RegisterFile.new('X', [], Shape.new(32, nil), [Register.new('x0'), Register.new('x1')]) + ab = ArchBuilder.new('minimal', []) + ab.add_register_file(rf) + sb = SnippetBuilder.new('s') + a = sb.input(0, 32) + sb.output(a, 0) + ab.add_snippet(sb.build) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal arch, arch2 + end + + def test_instruction_via_builder + rf = RegisterFile.new('X', [], Shape.new(32, nil), [Register.new('x0')]) + enc = InstructionEncoding.new(32, 0, 0, [], '', '', '') + ib = InstructionBuilder.new('test', [5, 5], ['rs1', 'rs2'], enc) + rs1 = ib.add_input_operand(0, 5) + rs2 = ib.add_input_operand(1, 5) + v = ib.read(rf, rs1) + ib.write(rf, rs2, v) + instr = ib.build + ab = ArchBuilder.new('w_instr', []) + ab.add_register_file(rf) + ab.add_instruction(instr) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal arch, arch2 + end + + def test_null_fields_roundtrip + op = Add.new(32) + op.semantic_base = nil + op.semantic_func = nil + ab = ArchBuilder.new('null_test', []) + ab.add_operation(op) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_nil arch2.operations[0].semantic_base + end + + def test_register_attributes + rf = RegisterFile.new('R', [], Shape.new(32, nil), [Register.new('x0', ['zero']), Register.new('x1', [])]) + ab = ArchBuilder.new('attrs', []) + ab.add_register_file(rf) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal ['zero'], arch2.register_files[0].regs[0].attributes + end + + def test_system_register + field = SystemRegisterField.new('f', [], 0, 7) + sr = SystemRegister.new('csr', [], 32, [field]) + ab = ArchBuilder.new('sysreg', []) + ab.add_system_register(sr) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal 0, arch2.system_registers[0].fields[0].lsb + end + + def test_table_int + table = TableInt.new('t', [], [1, 2, 3]) + ab = ArchBuilder.new('tbl', []) + ab.add_table_int(table) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal [1, 2, 3], arch2.tables_int[0].values + end + + def test_rv32i_lite_roundtrip + rf = RegisterFile.new('XRegs', [], Shape.new(32, nil), + [Register.new('x0', ['zero'])] + (1...32).map { |i| Register.new("x#{i}") }) + + ab = ArchBuilder.new('rv32i_lite', []) + ab.add_register_file(rf) + + syscall = EnvironmentFunction.new('sysCall', [], [], []) + read_mem = EnvironmentFunction.new('readMem16', [], [32], [16]) + get_pc = EnvironmentFunction.new('getPC', [], [], [32]) + set_pc = EnvironmentFunction.new('setPC', [], [32], []) + ab.add_env_func(syscall).add_env_func(read_mem).add_env_func(get_pc).add_env_func(set_pc) + + ab.add_operation(Add.new(32)) + ab.add_operation(Lsr.new(32)) + ab.add_operation(Lsl.new(32)) + + # decode_0 snippet + sb = SnippetBuilder.new('decode_0') + enc = sb.input(0, 32) + c7 = sb.const(7, 32) + shifted = sb.lsr(enc, c7) + low5 = sb.extract_low(shifted, 5) + extended = sb.extend_zero(low5, 32) + sb.output(extended, 0) + ab.add_snippet(sb.build) + + # add instruction + enc = InstructionEncoding.new(32, 51, 0, [], '', '', '') + ib = InstructionBuilder.new('add', [32, 32, 32], ['rs2', 'rs1', 'rd'], enc) + rs2 = ib.add_input_operand(0, 32) + rs1 = ib.add_input_operand(1, 32) + rd = ib.add_input_operand(2, 32) + v1 = ib.read(rf, rs1) + v2 = ib.read(rf, rs2) + r = ib.add(v1, v2) + ib.write(rf, rd, r) + ab.add_instruction(ib.build) + + # ecall + enc = InstructionEncoding.new(32, 115, 0, [], '', '', '') + ib = InstructionBuilder.new('ecall', [], [], enc) + ib.env(syscall, []) + ab.add_instruction(ib.build) + + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + assert_equal arch, arch2 + end +end diff --git a/ruby/tests/unit/test_ir_builder.rb b/ruby/tests/unit/test_ir_builder.rb new file mode 100644 index 0000000..beabab6 --- /dev/null +++ b/ruby/tests/unit/test_ir_builder.rb @@ -0,0 +1,217 @@ +$LOAD_PATH.unshift(File.expand_path('../..', __dir__)) +require 'lira' +require 'minitest/autorun' + +include Lira + +class TestSeqBuilder < Minitest::Test + def test_const + seq = SeqBuilder.new + v = seq.const(42, 32) + assert_equal 32, v.width + assert v.name.start_with?('_t') + end + + def test_add_sub_mul + seq = SeqBuilder.new + a = seq.const(3, 32) + b = seq.const(2, 32) + assert_equal 32, seq.add(a, b).width + assert_equal 32, seq.sub(a, b).width + assert_equal 32, seq.mul(a, b).width + end + + def test_bitwise_ops + seq = SeqBuilder.new + a = seq.const(0xFF, 32) + b = seq.const(0x0F, 32) + assert_equal 32, seq.and_(a, b).width + assert_equal 32, seq.orr(a, b).width + assert_equal 32, seq.xor(a, b).width + end + + def test_shift_ops + seq = SeqBuilder.new + a = seq.const(1, 32) + b = seq.const(4, 32) + assert_equal 32, seq.lsl(a, b).width + assert_equal 32, seq.lsr(a, b).width + assert_equal 32, seq.asr(a, b).width + end + + def test_slt + seq = SeqBuilder.new + a = seq.const(10, 32) + b = seq.const(20, 32) + r = seq.slt(a, b) + assert_equal 1, r.width + end + + def test_extract_low + seq = SeqBuilder.new + a = seq.const(0xFF, 32) + r = seq.extract_low(a, 8) + assert_equal 8, r.width + end + + def test_extend_sign + seq = SeqBuilder.new + a = seq.const(1, 8) + r = seq.extend_sign(a, 32) + assert_equal 32, r.width + end + + def test_extend_zero + seq = SeqBuilder.new + a = seq.const(1, 8) + r = seq.extend_zero(a, 32) + assert_equal 32, r.width + end + + def test_input_output + seq = SeqBuilder.new + inp = seq.input(0, 32) + seq.output(inp, 0) + s = seq.build + assert_equal 'input', s.stmts[0].kind + assert_equal 'output', s.stmts[1].kind + end + + def test_operations_map + snip = SnippetBuilder.new('test') + a = snip.const(1, 32) + b = snip.const(2, 32) + snip.add(a, b) + snip.sub(a, b) + snip.slt(a, b) + omap = snip.operations_map + assert omap.key?('add_32') + assert omap.key?('slt_32') + end + + def test_read_write + rf = RegisterFile.new('XRegs', [], Shape.new(32, nil), [Register.new('x0')]) + seq = SeqBuilder.new + reg = seq.input(0, 5) + v = seq.read(rf, reg) + assert_equal 32, v.width + seq.write(rf, reg, v) + s = seq.build + kinds = s.stmts.map(&:kind) + assert kinds.include?('read') + assert kinds.include?('write') + end + + def test_env + get_pc = EnvironmentFunction.new('getPC', [], [], [32]) + seq = SeqBuilder.new + result = seq.env(get_pc, []) + assert_equal 1, result.length + assert_equal 32, result[0].width + end + + def test_cond_env + write_mem = EnvironmentFunction.new('writeMem32', [], [32, 32], []) + seq = SeqBuilder.new + cond = seq.const(1, 1) + addr = seq.const(100, 32) + fallback = seq.const(200, 32) + result = seq.cond_env(write_mem, cond, [addr], [fallback]) + assert_equal 0, result.length + end +end + +class TestSnippetBuilder < Minitest::Test + def test_build_snippet + sb = SnippetBuilder.new('test') + a = sb.input(0, 32) + sb.output(a, 0) + snip = sb.build + assert_equal 'test', snip.name + assert_equal 2, snip.seq.stmts.length + end + + def test_build_decode + sb = SnippetBuilder.new('decode_rs2') + enc = sb.input(0, 32) + shift = sb.const(20, 32) + shifted = sb.lsr(enc, shift) + r = sb.extract_low(shifted, 5) + sb.output(r, 0) + snip = sb.build + assert_equal 5, snip.seq.stmts.length + end + + def test_build_constraint + sb = SnippetBuilder.new('constraint') + enc = sb.input(0, 32) + mask = sb.const(0xFF, 32) + masked = sb.and_(enc, mask) + expected = sb.const(0x33, 32) + ok = sb.slt(masked, expected) + sb.output(ok, 0) + snip = sb.build + assert_equal 6, snip.seq.stmts.length + end +end + +class TestInstructionBuilder < Minitest::Test + def setup + @rf = RegisterFile.new('XRegs', [], Shape.new(32, nil), + (0...32).map { |i| Register.new("x#{i}") }) + @get_pc = EnvironmentFunction.new('getPC', [], [], [32]) + @set_pc = EnvironmentFunction.new('setPC', [], [32], []) + @read_mem = EnvironmentFunction.new('readMem16', [], [32], [16]) + @syscall = EnvironmentFunction.new('sysCall', [], [], []) + end + + def test_build_add + enc = InstructionEncoding.new(32, 51, 0, [], '', '', '') + ib = InstructionBuilder.new('add', [5, 5, 5], ['rd', 'rs1', 'rs2'], enc) + rd = ib.add_input_operand(0, 5) + rs1 = ib.add_input_operand(1, 5) + rs2 = ib.add_input_operand(2, 5) + v1 = ib.read(@rf, rs1) + v2 = ib.read(@rf, rs2) + r = ib.add(v1, v2) + ib.write(@rf, rd, r) + instr = ib.build + assert_equal 'add', instr.name + assert_equal 7, instr.semantic.stmts.length + end + + def test_build_ecall + enc = InstructionEncoding.new(32, 115, 0, [], '', '', '') + ib = InstructionBuilder.new('ecall', [], [], enc) + ib.env(@syscall, []) + instr = ib.build + assert_equal 'ecall', instr.name + assert_equal 'env', instr.semantic.stmts[0].kind + end +end + +class TestArchBuilder < Minitest::Test + def test_build_arch + rf = RegisterFile.new('XRegs', [], Shape.new(32, nil), [Register.new('x0')]) + ab = ArchBuilder.new('test', ['attr']) + ab.add_register_file(rf) + arch = ab.build + assert_equal 'test', arch.name + assert_equal 1, arch.register_files.length + end + + def test_add_env_operation_snippet + ab = ArchBuilder.new('test', []) + env = EnvironmentFunction.new('ld', ['mem'], [32], [32]) + op = Add.new(32) + sb = SnippetBuilder.new('s1') + a = sb.input(0, 32) + sb.output(a, 0) + snip = sb.build + ab.add_env_func(env).add_operation(op).add_snippet(snip) + arch = ab.build + assert_equal 1, arch.environment_functions.length + assert_equal 1, arch.operations.length + assert_equal 1, arch.snippets.length + end +end diff --git a/ruby/tests/unit/test_ir_ser_txt.rb b/ruby/tests/unit/test_ir_ser_txt.rb new file mode 100644 index 0000000..8acc922 --- /dev/null +++ b/ruby/tests/unit/test_ir_ser_txt.rb @@ -0,0 +1,95 @@ +$LOAD_PATH.unshift(File.expand_path('../..', __dir__)) +require 'lira' +require 'minitest/autorun' + +include Lira + +class TestIrSerTxt < Minitest::Test + def test_empty_sequence + seq = SeqBuilder.new.build + text = IrSerTxt.serialize_statement_seq(seq) + assert_equal '', text + end + + def test_built_sequence_roundtrip + seq = SeqBuilder.new + a = seq.input(0, 32) + b = seq.const(42, 32) + r = seq.add(a, b) + seq.output(r, 0) + stmts = seq.build + text = IrSerTxt.serialize_statement_seq(stmts) + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal stmts, seq2 + end + + def test_shape_with_mult + seq = SeqBuilder.new + a = seq.input(0, 32) + r = seq.add(a, a) + seq.output(r, 0) + stmts = seq.build + stmts.stmts[0].shape = Shape.new(4, 'c') + text = IrSerTxt.serialize_statement_seq(stmts) + assert text.include?('4c') + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal 'c', seq2.stmts[0].shape.lanes_mult + end + + def test_read_write + rf = RegisterFile.new('XRegs', [], Shape.new(32, nil), [Register.new('x0')]) + seq = SeqBuilder.new + reg = seq.input(0, 5) + v = seq.read(rf, reg) + seq.write(rf, reg, v) + stmts = seq.build + text = IrSerTxt.serialize_statement_seq(stmts) + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal 'read', seq2.stmts[1].kind + assert_equal 'write', seq2.stmts[2].kind + end + + def test_env_and_cond_env + get_pc = EnvironmentFunction.new('getPC', [], [], [32]) + write_mem = EnvironmentFunction.new('writeMem16', [], [32, 16], []) + seq = SeqBuilder.new + seq.env(get_pc, []) + cond = seq.input(0, 1) + addr = seq.input(1, 32) + fallback = seq.input(2, 32) + seq.cond_env(write_mem, cond, [addr], [fallback]) + stmts = seq.build + text = IrSerTxt.serialize_statement_seq(stmts) + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal 'env', seq2.stmts[0].kind + assert_equal 'cond_env', seq2.stmts[-1].kind + end + + def test_builder_decode_snippet + sb = SnippetBuilder.new('decode_0') + enc = sb.input(0, 32) + c7 = sb.const(7, 32) + shifted = sb.lsr(enc, c7) + low5 = sb.extract_low(shifted, 5) + extended = sb.extend_zero(low5, 32) + sb.output(extended, 0) + snip = sb.build + text = IrSerTxt.serialize_statement_seq(snip.seq) + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal 6, seq2.stmts.length + end + + def test_builder_constraint_snippet + sb = SnippetBuilder.new('constraint_36') + enc = sb.input(0, 32) + mask = sb.const(28_799, 32) + masked = sb.and_(enc, mask) + expected = sb.const(20_483, 32) + ok = sb.eq(masked, expected) + sb.output(ok, 0) + snip = sb.build + text = IrSerTxt.serialize_statement_seq(snip.seq) + seq2 = IrSerTxt.deserialize_statement_seq(text) + assert_equal 6, seq2.stmts.length + end +end diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 903a915..a6caca7 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -31,6 +31,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "cfg-if" version = "1.0.4" @@ -50,10 +56,20 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.18" +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] [[package]] name = "libc" @@ -61,6 +77,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "lira" version = "0.1.0" @@ -70,20 +92,29 @@ dependencies = [ "log", "regex", "serde", - "serde_json", + "serde_yaml", + "yaml-rust", +] + +[[package]] +name = "lira-tests" +version = "0.1.0" +dependencies = [ + "anyhow", + "lira", ] [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "once_cell" @@ -117,9 +148,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -140,9 +171,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "ryu" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "serde" @@ -175,23 +212,22 @@ dependencies = [ ] [[package]] -name = "serde_json" -version = "1.0.149" +name = "serde_yaml" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "itoa", - "memchr", + "indexmap", + "ryu", "serde", - "serde_core", - "zmij", + "yaml-rust", ] [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -212,9 +248,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] @@ -225,28 +261,31 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", "syn", ] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 214466d..724098b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "3" -members = ["lira"] +members = ["lira", "tests"] [workspace.dependencies] anyhow = "1.0.102" @@ -11,7 +11,8 @@ clap = "4.6.1" ahash = "0.8.12" regex = "1.12.3" serde = "1.0.228" -serde_json = "1.0.149" +serde_yaml = "0.8" +yaml-rust = "0.4" [profile.work] inherits = "dev" diff --git a/rust/lira/Cargo.toml b/rust/lira/Cargo.toml index 5dbfea3..6309bbb 100644 --- a/rust/lira/Cargo.toml +++ b/rust/lira/Cargo.toml @@ -9,4 +9,5 @@ anyhow = { workspace = true } ahash = { workspace = true, features = ["serde"] } regex = { workspace = true } serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } +serde_yaml = { workspace = true } +yaml-rust = { workspace = true } diff --git a/rust/lira/src/arch.rs b/rust/lira/src/arch.rs index 0904dda..0f0ed32 100644 --- a/rust/lira/src/arch.rs +++ b/rust/lira/src/arch.rs @@ -20,17 +20,28 @@ pub struct Operation { pub semantic_table: Option, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct Register { + pub name: String, + #[serde(default)] + pub attributes: Vec, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] pub struct RegisterFile { pub name: String, pub attributes: Vec, pub reg_size: Shape, - pub reg_names: Vec, + pub regs: Vec, } impl RegisterFile { + pub fn reg_names(&self) -> Vec<&str> { + self.regs.iter().map(|r| r.name.as_str()).collect() + } + pub fn regs_num(&self) -> usize { - self.reg_names.len() + self.regs.len() } } @@ -69,6 +80,7 @@ pub struct TableInt { pub struct InstructionEncoding { pub encoded_size: usize, pub const_encoding_part: usize, + pub const_mask: usize, /// Names of snippets /// /// `[encoding_size -> operand_size]` diff --git a/rust/lira/src/arch_ser_txt.rs b/rust/lira/src/arch_ser_txt.rs deleted file mode 100644 index e1d7c7c..0000000 --- a/rust/lira/src/arch_ser_txt.rs +++ /dev/null @@ -1,125 +0,0 @@ -use std::path::Path; - -use anyhow::Context; -use serde_json::json; - -use crate::*; - -impl Arch { - pub fn write_to_file(&self, folder_path: &Path) -> anyhow::Result<()> { - if folder_path.exists() { - std::fs::remove_dir_all(folder_path)?; - } - std::fs::create_dir_all(folder_path) - .with_context(|| anyhow::anyhow!("failed to create dir to {folder_path:?}"))?; - - macro_rules! write_json { - ($data:expr, $path:literal $(, $($arg:tt)*)?) => { - let path = folder_path.join(format!($path $(, $($arg)*)?)); - let content = serde_json::to_string($data)?; - std::fs::write(&path, content)?; - }; - } - - let arch = json!({ "name": self.name, "attributes": self.attributes, }); - write_json!(&arch, "arch.json"); - - write_json!(&self.register_files, "register_files.json"); - write_json!(&self.system_registers, "system_registers.json"); - write_json!(&self.environment_functions, "environment_functions.json"); - write_json!(&self.tables_int, "tables_int.json"); - - let index = json!({ - "operations": self.operations.iter().map(|op| &op.name).collect::>(), - "snippets": self.snippets.iter().map(|s| &s.name).collect::>(), - "instructions": self.instructions.iter().map(|i| &i.name).collect::>(), - }); - write_json!(&index, "index.json"); - - let ops_dir = folder_path.join("operations"); - std::fs::create_dir(&ops_dir)?; - for op in &self.operations { - let path = ops_dir.join(format!("{}.json", op.name)); - std::fs::write(&path, serde_json::to_string(op)?)?; - } - - let snippets_dir = folder_path.join("snippets"); - std::fs::create_dir(&snippets_dir)?; - for snippet in &self.snippets { - let path = snippets_dir.join(format!("{}.lira", snippet.name)); - std::fs::write(&path, snippet.seq.to_string())?; - } - - let instr_dir = folder_path.join("instructions"); - std::fs::create_dir(&instr_dir)?; - for instr in &self.instructions { - let json_path = instr_dir.join(format!("{}.json", instr.name)); - std::fs::write(&json_path, serde_json::to_string(&instr)?)?; - let lira_path = instr_dir.join(format!("{}.lira", instr.name)); - std::fs::write(&lira_path, instr.semantic.to_string())?; - } - - Ok(()) - } - - pub fn read_from_file(folder_path: &Path) -> anyhow::Result { - macro_rules! read_json { - ($path:literal $(, $($arg:tt)*)?) => {{ - let path = folder_path.join(format!($path $(, $($arg)*)?)); - let content = std::fs::read_to_string(&path) - .with_context(|| format!("failed to read {:?}", path))?; - serde_json::from_str(&content) - .with_context(|| format!("invalid JSON in {:?}", path))? - }}; - } - - let arch_info: serde_json::Value = read_json!("arch.json"); - let name = arch_info["name"].as_str().unwrap().to_string(); - let attributes: Vec = serde_json::from_value(arch_info["attributes"].clone())?; - - let register_files = read_json!("register_files.json"); - let system_registers = read_json!("system_registers.json"); - let environment_functions = read_json!("environment_functions.json"); - let tables_int = read_json!("tables_int.json"); - - let index: serde_json::Value = read_json!("index.json"); - let op_names: Vec = serde_json::from_value(index["operations"].clone())?; - let snippet_names: Vec = serde_json::from_value(index["snippets"].clone())?; - let instr_names: Vec = serde_json::from_value(index["instructions"].clone())?; - - let mut operations = Vec::new(); - for op_name in &op_names { - let op: Operation = read_json!("operations/{}.json", op_name); - operations.push(op); - } - - let mut snippets = Vec::new(); - for name in snippet_names { - let lira_path = folder_path.join(format!("snippets/{}.lira", name)); - let content = std::fs::read_to_string(&lira_path)?; - let seq = StatementSeq::parse(&content)?; - snippets.push(Snippet { name, seq }); - } - - let mut instructions = Vec::new(); - for instr_name in &instr_names { - let mut instr: Instruction = read_json!("instructions/{}.json", instr_name); - let lira_path = folder_path.join(format!("instructions/{}.lira", instr_name)); - let lira_content = std::fs::read_to_string(&lira_path)?; - instr.semantic = StatementSeq::parse(&lira_content)?; - instructions.push(instr); - } - - Ok(Self { - name, - attributes, - register_files, - system_registers, - environment_functions, - tables_int, - operations, - snippets, - instructions, - }) - } -} diff --git a/rust/lira/src/arch_ser_yaml.rs b/rust/lira/src/arch_ser_yaml.rs new file mode 100644 index 0000000..fe2ec4c --- /dev/null +++ b/rust/lira/src/arch_ser_yaml.rs @@ -0,0 +1,471 @@ +use std::path::Path; + +use regex::Regex; +use serde::Deserialize; +use yaml_rust::yaml::Hash; +use yaml_rust::{Yaml, YamlEmitter}; + +use crate::*; + +fn blockify_yaml(yaml: &str) -> String { + let re = Regex::new(r#"(?m)^(\s*)(seq|semantic):\s*"(.*)"$"#).unwrap(); + let mut result = yaml.to_string(); + let mut offset: isize = 0; + + let caps: Vec<_> = re.captures_iter(yaml).collect(); + for cap in caps { + let indent = cap.get(1).unwrap().as_str(); + let key = cap.get(2).unwrap().as_str(); + let body = cap.get(3).unwrap().as_str().replace("\\n", "\n"); + let body_indent = format!("{} ", indent); + + let mut replacement = format!("{}{}: |\n", indent, key); + for line in body.lines() { + replacement.push_str(&body_indent); + replacement.push_str(line); + replacement.push('\n'); + } + if body.ends_with('\n') { + replacement.push('\n'); + } + + let cap_start = cap.get(0).unwrap().start() as isize + offset; + let cap_end = cap.get(0).unwrap().end() as isize + offset; + let old_len = (cap_end - cap_start) as usize; + result.replace_range(cap_start as usize..cap_end as usize, &replacement); + offset += replacement.len() as isize - old_len as isize; + } + + result +} + +fn arch_to_yaml(arch: &Arch) -> anyhow::Result { + let snippets: Vec = arch + .snippets + .iter() + .map(|s| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(s.name.clone())); + map.insert(Yaml::String("seq".into()), Yaml::String(s.seq.to_string())); + Yaml::Hash(map) + }) + .collect(); + + let instructions: Vec = arch + .instructions + .iter() + .map(|i| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(i.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + i.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + map.insert( + Yaml::String("operand_sizes".into()), + Yaml::Array( + i.operand_sizes + .iter() + .map(|&sz| Yaml::Integer(sz as i64)) + .collect(), + ), + ); + map.insert( + Yaml::String("operand_names".into()), + Yaml::Array( + i.operand_names + .iter() + .map(|n| Yaml::String(n.clone())) + .collect(), + ), + ); + { + let mut enc_map = Hash::new(); + enc_map.insert( + Yaml::String("encoded_size".into()), + Yaml::Integer(i.encoding.encoded_size as i64), + ); + enc_map.insert( + Yaml::String("const_encoding_part".into()), + Yaml::Integer(i.encoding.const_encoding_part as i64), + ); + enc_map.insert( + Yaml::String("const_mask".into()), + Yaml::Integer(i.encoding.const_mask as i64), + ); + enc_map.insert( + Yaml::String("decode".into()), + Yaml::Array( + i.encoding + .decode + .iter() + .map(|d| Yaml::String(d.clone())) + .collect(), + ), + ); + enc_map.insert( + Yaml::String("encode".into()), + Yaml::String(i.encoding.encode.clone()), + ); + enc_map.insert( + Yaml::String("constraint_decode".into()), + Yaml::String(i.encoding.constraint_decode.clone()), + ); + enc_map.insert( + Yaml::String("constraint_encode".into()), + Yaml::String(i.encoding.constraint_encode.clone()), + ); + map.insert(Yaml::String("encoding".into()), Yaml::Hash(enc_map)); + } + map.insert( + Yaml::String("semantic".into()), + Yaml::String(i.semantic.to_string()), + ); + Yaml::Hash(map) + }) + .collect(); + + let register_files: Vec = arch + .register_files + .iter() + .map(|rf| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(rf.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + rf.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + { + let mut shape = Hash::new(); + shape.insert( + Yaml::String("lanes_base".into()), + Yaml::Integer(rf.reg_size.lanes_base as i64), + ); + shape.insert( + Yaml::String("lanes_mult".into()), + match &rf.reg_size.lanes_mult { + Some(m) => Yaml::String(m.clone()), + None => Yaml::Null, + }, + ); + map.insert(Yaml::String("reg_size".into()), Yaml::Hash(shape)); + } + map.insert( + Yaml::String("regs".into()), + Yaml::Array( + rf.regs + .iter() + .map(|r| { + let mut rm = Hash::new(); + rm.insert(Yaml::String("name".into()), Yaml::String(r.name.clone())); + rm.insert( + Yaml::String("attributes".into()), + Yaml::Array( + r.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + Yaml::Hash(rm) + }) + .collect(), + ), + ); + Yaml::Hash(map) + }) + .collect(); + + let env_funcs: Vec = arch + .environment_functions + .iter() + .map(|ef| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(ef.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + ef.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + map.insert( + Yaml::String("inputs".into()), + Yaml::Array( + ef.inputs + .iter() + .map(|&sz| Yaml::Integer(sz as i64)) + .collect(), + ), + ); + map.insert( + Yaml::String("outputs".into()), + Yaml::Array( + ef.outputs + .iter() + .map(|&sz| Yaml::Integer(sz as i64)) + .collect(), + ), + ); + Yaml::Hash(map) + }) + .collect(); + + let operations: Vec = arch + .operations + .iter() + .map(|op| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(op.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + op.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + map.insert( + Yaml::String("inputs".into()), + Yaml::Array( + op.inputs + .iter() + .map(|&sz| Yaml::Integer(sz as i64)) + .collect(), + ), + ); + map.insert( + Yaml::String("outputs".into()), + Yaml::Array( + op.outputs + .iter() + .map(|&sz| Yaml::Integer(sz as i64)) + .collect(), + ), + ); + if let Some(ref base) = op.semantic_base { + map.insert( + Yaml::String("semantic_base".into()), + Yaml::String(base.clone()), + ); + } else { + map.insert(Yaml::String("semantic_base".into()), Yaml::Null); + } + if let Some(ref func) = op.semantic_func { + map.insert( + Yaml::String("semantic_func".into()), + Yaml::String(func.clone()), + ); + } else { + map.insert(Yaml::String("semantic_func".into()), Yaml::Null); + } + map.insert(Yaml::String("semantic_func_128".into()), Yaml::Null); + map.insert(Yaml::String("semantic_table".into()), Yaml::Null); + Yaml::Hash(map) + }) + .collect(); + + let system_registers: Vec = arch + .system_registers + .iter() + .map(|sr| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(sr.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + sr.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + map.insert(Yaml::String("size".into()), Yaml::Integer(sr.size as i64)); + map.insert( + Yaml::String("fields".into()), + Yaml::Array( + sr.fields + .iter() + .map(|f| { + let mut fm = Hash::new(); + fm.insert(Yaml::String("name".into()), Yaml::String(f.name.clone())); + fm.insert( + Yaml::String("attributes".into()), + Yaml::Array( + f.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + fm.insert(Yaml::String("lsb".into()), Yaml::Integer(f.lsb as i64)); + fm.insert(Yaml::String("msb".into()), Yaml::Integer(f.msb as i64)); + Yaml::Hash(fm) + }) + .collect(), + ), + ); + Yaml::Hash(map) + }) + .collect(); + + let tables_int: Vec = arch + .tables_int + .iter() + .map(|t| { + let mut map = Hash::new(); + map.insert(Yaml::String("name".into()), Yaml::String(t.name.clone())); + map.insert( + Yaml::String("attributes".into()), + Yaml::Array( + t.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + map.insert( + Yaml::String("values".into()), + Yaml::Array(t.values.iter().map(|&v| Yaml::Integer(v as i64)).collect()), + ); + Yaml::Hash(map) + }) + .collect(); + + let mut root = Hash::new(); + root.insert(Yaml::String("name".into()), Yaml::String(arch.name.clone())); + root.insert( + Yaml::String("attributes".into()), + Yaml::Array( + arch.attributes + .iter() + .map(|a| Yaml::String(a.clone())) + .collect(), + ), + ); + root.insert( + Yaml::String("register_files".into()), + Yaml::Array(register_files), + ); + root.insert( + Yaml::String("system_registers".into()), + Yaml::Array(system_registers), + ); + root.insert( + Yaml::String("environment_functions".into()), + Yaml::Array(env_funcs), + ); + root.insert(Yaml::String("tables_int".into()), Yaml::Array(tables_int)); + root.insert(Yaml::String("operations".into()), Yaml::Array(operations)); + root.insert(Yaml::String("snippets".into()), Yaml::Array(snippets)); + root.insert( + Yaml::String("instructions".into()), + Yaml::Array(instructions), + ); + + let doc = Yaml::Hash(root); + let mut out = String::new(); + let mut emitter = YamlEmitter::new(&mut out); + emitter.dump(&doc)?; + + Ok(out) +} + +#[derive(Debug, Clone, Deserialize)] +struct SerializableSnippet { + name: String, + seq: String, +} + +#[derive(Debug, Clone, Deserialize)] +struct SerializableInstruction { + name: String, + attributes: Vec, + operand_sizes: Vec, + operand_names: Vec, + encoding: InstructionEncoding, + semantic: String, +} + +#[derive(Debug, Clone, Deserialize)] +struct SerializableArch { + name: String, + attributes: Vec, + register_files: Vec, + system_registers: Vec, + environment_functions: Vec, + tables_int: Vec, + operations: Vec, + snippets: Vec, + instructions: Vec, +} + +impl SerializableArch { + fn into_arch(self) -> anyhow::Result { + let snippets = self + .snippets + .into_iter() + .map(|s| { + Ok(Snippet { + name: s.name, + seq: StatementSeq::parse(&s.seq)?, + }) + }) + .collect::>>()?; + + let instructions = self + .instructions + .into_iter() + .map(|i| { + Ok(Instruction { + name: i.name, + attributes: i.attributes, + operand_sizes: i.operand_sizes, + operand_names: i.operand_names, + encoding: i.encoding, + semantic: StatementSeq::parse(&i.semantic)?, + }) + }) + .collect::>>()?; + + Ok(Arch { + name: self.name, + attributes: self.attributes, + register_files: self.register_files, + system_registers: self.system_registers, + environment_functions: self.environment_functions, + tables_int: self.tables_int, + operations: self.operations, + snippets, + instructions, + }) + } +} + +impl Arch { + pub fn write_yaml(&self, path: impl AsRef) -> anyhow::Result<()> { + let yaml = arch_to_yaml(self)?; + let yaml = blockify_yaml(&yaml); + std::fs::write(path, yaml)?; + Ok(()) + } + + pub fn read_yaml(path: impl AsRef) -> anyhow::Result { + let content = std::fs::read_to_string(path)?; + let serializable: SerializableArch = serde_yaml::from_str(&content)?; + serializable.into_arch() + } +} diff --git a/rust/lira/src/lib.rs b/rust/lira/src/lib.rs index ce9d8f9..592fd16 100644 --- a/rust/lira/src/lib.rs +++ b/rust/lira/src/lib.rs @@ -1,5 +1,5 @@ mod arch; -mod arch_ser_txt; +mod arch_ser_yaml; mod arch_utils; mod ir; mod ir_ser_txt; diff --git a/rust/lira/tests/integration.rs b/rust/lira/tests/integration.rs deleted file mode 100644 index 8d9e477..0000000 --- a/rust/lira/tests/integration.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::path::PathBuf; - -use lira::*; - -#[test] -fn integration() { - let input = std::env::var("LIRA_TEST_INTEGRATION_INPUT"); - let output = std::env::var("LIRA_TEST_INTEGRATION_INPUT"); - assert!(input.is_ok(), "please pass paths through env"); - let input = PathBuf::from(input.unwrap()); - let output = PathBuf::from(output.unwrap()); - - // Compare to python-generated - let arch = Arch::read_from_file(&input).unwrap(); - arch.write_to_file(&output).unwrap(); - // Round trip - let arch2 = Arch::read_from_file(&output).unwrap(); - - assert_eq!(arch.register_files, arch2.register_files); - assert_eq!(arch.system_registers, arch2.system_registers); - assert_eq!(arch.environment_functions, arch2.environment_functions); - assert_eq!(arch.tables_int, arch2.tables_int); - assert_eq!(arch.operations, arch2.operations); - assert_eq!(arch.snippets, arch2.snippets); - assert_eq!(arch.instructions, arch2.instructions); - assert_eq!(arch, arch2); -} diff --git a/rust/tests/Cargo.toml b/rust/tests/Cargo.toml new file mode 100644 index 0000000..8f595ab --- /dev/null +++ b/rust/tests/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "lira-tests" +version = "0.1.0" +edition = "2024" + +[[test]] +name = "cross_language" +path = "../../tests/cross_language/test_cross.rs" + +[[test]] +name = "integration" +path = "integration/main.rs" + +[dependencies] +lira = { path = "../lira" } +anyhow = { workspace = true } diff --git a/rust/tests/README.md b/rust/tests/README.md new file mode 100644 index 0000000..6a8ca21 --- /dev/null +++ b/rust/tests/README.md @@ -0,0 +1,15 @@ +# LIRA Rust Tests + +## Integration test + +Reads `tests/integration/reference.yaml`, writes back via [normalization script](../../tools/yaml_canonicalize.py), asserts round-trip equality. + +```bash +cargo test -p lira-tests --test integration +``` + +## All tests + +```bash +cargo test -p lira-tests +``` diff --git a/rust/tests/integration/main.rs b/rust/tests/integration/main.rs new file mode 100644 index 0000000..3d965a4 --- /dev/null +++ b/rust/tests/integration/main.rs @@ -0,0 +1,48 @@ +use std::path::PathBuf; + +use lira::*; + +fn project_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .to_path_buf() +} + +#[test] +fn test_roundtrip_from_reference() { + let ref_path = project_root() + .join("tests") + .join("integration") + .join("reference.yaml"); + let ref_arch = Arch::read_yaml(&ref_path).unwrap(); + + let output = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("integration") + .join("integration.yaml"); + std::fs::create_dir_all(output.parent().unwrap()).ok(); + let raw = output.with_extension("raw.yaml"); + + ref_arch.write_yaml(&raw).unwrap(); + let canonicalize = project_root().join("tools").join("yaml_canonicalize.py"); + std::process::Command::new("python3") + .arg(&canonicalize) + .arg(&raw) + .arg(&output) + .status() + .unwrap(); + std::fs::remove_file(&raw).ok(); + + let arch2 = Arch::read_yaml(&output).unwrap(); + + assert_eq!(ref_arch.register_files, arch2.register_files); + assert_eq!(ref_arch.system_registers, arch2.system_registers); + assert_eq!(ref_arch.environment_functions, arch2.environment_functions); + assert_eq!(ref_arch.tables_int, arch2.tables_int); + assert_eq!(ref_arch.operations, arch2.operations); + assert_eq!(ref_arch.snippets, arch2.snippets); + assert_eq!(ref_arch.instructions, arch2.instructions); + assert_eq!(ref_arch, arch2); +} diff --git a/tests/cross_language/test_cross.py b/tests/cross_language/test_cross.py new file mode 100644 index 0000000..88b9ebf --- /dev/null +++ b/tests/cross_language/test_cross.py @@ -0,0 +1,40 @@ +import sys +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +from python.lira import arch_ser_yaml + +CROSS_DIR = Path(__file__).parent +REFERENCE = Path(__file__).parent.parent / "integration" / "reference.yaml" + +PY_OUT = CROSS_DIR / "py_native.yaml" +RB_OUT = CROSS_DIR / "rb_native.yaml" +RS_OUT = CROSS_DIR / "rs_native.yaml" + + +def test_python_write_and_self_read(): + arch = arch_ser_yaml.read_arch(REFERENCE) + arch_ser_yaml.write_arch(arch, PY_OUT) + arch2 = arch_ser_yaml.read_arch(PY_OUT) + assert arch == arch2 + + +def test_python_reads_ruby(): + if not RB_OUT.exists(): + pytest.skip("rb_native.yaml not found") + arch = arch_ser_yaml.read_arch(RB_OUT) + arch_ser_yaml.write_arch(arch, CROSS_DIR / "py_from_rb.yaml") + arch2 = arch_ser_yaml.read_arch(CROSS_DIR / "py_from_rb.yaml") + assert arch == arch2 + + +def test_python_reads_rust(): + if not RS_OUT.exists(): + pytest.skip("rs_native.yaml not found") + arch = arch_ser_yaml.read_arch(RS_OUT) + arch_ser_yaml.write_arch(arch, CROSS_DIR / "py_from_rs.yaml") + arch2 = arch_ser_yaml.read_arch(CROSS_DIR / "py_from_rs.yaml") + assert arch == arch2 diff --git a/tests/cross_language/test_cross.rb b/tests/cross_language/test_cross.rb new file mode 100644 index 0000000..da42114 --- /dev/null +++ b/tests/cross_language/test_cross.rb @@ -0,0 +1,38 @@ +$LOAD_PATH.unshift(File.expand_path('../../ruby', __dir__)) +$LOAD_PATH.unshift(File.expand_path('../..', __dir__)) +require 'lira' +require 'minitest/autorun' + +include Lira + +CROSS_DIR = File.expand_path(__dir__) +REFERENCE = File.join(CROSS_DIR, '..', 'integration', 'reference.yaml') + +RB_OUT = File.join(CROSS_DIR, 'rb_native.yaml') +PY_OUT = File.join(CROSS_DIR, 'py_native.yaml') +RS_OUT = File.join(CROSS_DIR, 'rs_native.yaml') + +class TestCrossLanguage < Minitest::Test + def test_ruby_write_and_self_read + arch = ArchSerYaml.read_arch(REFERENCE) + ArchSerYaml.write_arch(arch, RB_OUT) + arch2 = ArchSerYaml.read_arch(RB_OUT) + assert_equal arch, arch2 + end + + def test_ruby_reads_python + skip('py_native.yaml not found') unless File.exist?(PY_OUT) + arch = ArchSerYaml.read_arch(PY_OUT) + ArchSerYaml.write_arch(arch, File.join(CROSS_DIR, 'rb_from_py.yaml')) + arch2 = ArchSerYaml.read_arch(File.join(CROSS_DIR, 'rb_from_py.yaml')) + assert_equal arch, arch2 + end + + def test_ruby_reads_rust + skip('rs_native.yaml not found') unless File.exist?(RS_OUT) + arch = ArchSerYaml.read_arch(RS_OUT) + ArchSerYaml.write_arch(arch, File.join(CROSS_DIR, 'rb_from_rs.yaml')) + arch2 = ArchSerYaml.read_arch(File.join(CROSS_DIR, 'rb_from_rs.yaml')) + assert_equal arch, arch2 + end +end diff --git a/tests/cross_language/test_cross.rs b/tests/cross_language/test_cross.rs new file mode 100644 index 0000000..39e4c88 --- /dev/null +++ b/tests/cross_language/test_cross.rs @@ -0,0 +1,58 @@ +use std::path::PathBuf; + +use lira::*; + +fn cross_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .parent() + .unwrap() + .join("tests") + .join("cross_language") +} + +fn reference() -> PathBuf { + cross_dir() + .parent() + .unwrap() + .join("integration") + .join("reference.yaml") +} + +#[test] +fn test_rust_write_and_self_read() { + let arch = Arch::read_yaml(&reference()).unwrap(); + let out = cross_dir().join("rs_native.yaml"); + arch.write_yaml(&out).unwrap(); + let arch2 = Arch::read_yaml(&out).unwrap(); + assert_eq!(arch, arch2); +} + +#[test] +fn test_rust_reads_python() { + let py_out = cross_dir().join("py_native.yaml"); + if !py_out.exists() { + return; + } + let arch = Arch::read_yaml(&py_out).unwrap(); + let tmp = cross_dir().join("rs_from_py.yaml"); + arch.write_yaml(&tmp).unwrap(); + let arch2 = Arch::read_yaml(&tmp).unwrap(); + assert_eq!(arch, arch2); + std::fs::remove_file(&tmp).ok(); +} + +#[test] +fn test_rust_reads_ruby() { + let rb_out = cross_dir().join("rb_native.yaml"); + if !rb_out.exists() { + return; + } + let arch = Arch::read_yaml(&rb_out).unwrap(); + let tmp = cross_dir().join("rs_from_rb.yaml"); + arch.write_yaml(&tmp).unwrap(); + let arch2 = Arch::read_yaml(&tmp).unwrap(); + assert_eq!(arch, arch2); + std::fs::remove_file(&tmp).ok(); +} diff --git a/tests/integration/reference.yaml b/tests/integration/reference.yaml new file mode 100644 index 0000000..cd13447 --- /dev/null +++ b/tests/integration/reference.yaml @@ -0,0 +1,859 @@ +name: TargetArch +attributes: [] +register_files: + - name: XRegs + attributes: [] + reg_size: + lanes_base: 32 + lanes_mult: + regs: + - name: x0 + attributes: + - zero + - name: x1 + attributes: [] + - name: x2 + attributes: [] + - name: x3 + attributes: [] + - name: x4 + attributes: [] + - name: x5 + attributes: [] + - name: x6 + attributes: [] + - name: x7 + attributes: [] + - name: x8 + attributes: [] + - name: SpecialRegs + attributes: [] + reg_size: + lanes_base: 32 + lanes_mult: + regs: + - name: pc + attributes: + - pc +system_registers: [] +environment_functions: + - name: sysCall + attributes: [] + inputs: [] + outputs: [] + - name: writeMem16 + attributes: [] + inputs: + - 32 + - 16 + outputs: [] + - name: readMem16 + attributes: [] + inputs: + - 32 + outputs: + - 16 + - name: getPC + attributes: [] + inputs: [] + outputs: + - 32 + - name: setPC + attributes: [] + inputs: + - 32 + outputs: [] +tables_int: [] +operations: + - name: add_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :add + semantic_func: + semantic_func_128: + semantic_table: + - name: lsr_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :lsr + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_5 + attributes: [] + inputs: + - 32 + outputs: + - 5 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_5_to_32 + attributes: [] + inputs: + - 5 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_1_to_32 + attributes: [] + inputs: + - 1 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: lsl_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :lsl + semantic_func: + semantic_func_128: + semantic_table: + - name: orr_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :orr + semantic_func: + semantic_func_128: + semantic_table: + - name: and_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 32 + semantic_base: :and + semantic_func: + semantic_func_128: + semantic_table: + - name: eq_32 + attributes: [] + inputs: + - 32 + - 32 + outputs: + - 1 + semantic_base: :eq + semantic_func: + semantic_func_128: + semantic_table: + - name: select_32 + attributes: [] + inputs: + - 1 + - 32 + - 32 + outputs: + - 32 + semantic_base: :select + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_1 + attributes: [] + inputs: + - 32 + outputs: + - 1 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_1_to_13 + attributes: [] + inputs: + - 1 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_13_to_32 + attributes: [] + inputs: + - 13 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_6 + attributes: [] + inputs: + - 32 + outputs: + - 6 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_6_to_13 + attributes: [] + inputs: + - 6 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_4 + attributes: [] + inputs: + - 32 + outputs: + - 4 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_4_to_13 + attributes: [] + inputs: + - 4 + outputs: + - 13 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_13 + attributes: [] + inputs: + - 32 + outputs: + - 13 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_13_to_32 + attributes: [] + inputs: + - 13 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_12 + attributes: [] + inputs: + - 32 + outputs: + - 12 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_12_to_32 + attributes: [] + inputs: + - 12 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_16 + attributes: [] + inputs: + - 32 + outputs: + - 16 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extract_low_32_to_7 + attributes: [] + inputs: + - 32 + outputs: + - 7 + semantic_base: :extract_low + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_7_to_12 + attributes: [] + inputs: + - 7 + outputs: + - 12 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_12_to_32 + attributes: [] + inputs: + - 12 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_5_to_12 + attributes: [] + inputs: + - 5 + outputs: + - 12 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_sign_16_to_32 + attributes: [] + inputs: + - 16 + outputs: + - 32 + semantic_base: :extend_sign + semantic_func: + semantic_func_128: + semantic_table: + - name: extend_zero_16_to_32 + attributes: [] + inputs: + - 16 + outputs: + - 32 + semantic_base: :extend_zero + semantic_func: + semantic_func_128: + semantic_table: +snippets: + - name: decode_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 20; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: decode_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 15; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: decode_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 7; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 5 _t5 = op extract_low_32_to_5 _t4; + 1 32 _t6 = op extend_zero_5_to_32 _t5; + 1 = output 0 _t6; + - name: encode_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 51; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 0; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 24; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 1 _t24 = const 0; + 1 32 _t25 = op extend_zero_1_to_32 _t24; + 1 32 _t26 = const 31; + 1 32 _t27 = op lsl_32 _t25 _t26; + 1 32 _t28 = op orr_32 _t23 _t27; + 1 = output 0 _t28; + - name: constraint_0 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 4261441663; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 51; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 31; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 1 _t5 = op extract_low_32_to_1 _t4; + 1 13 _t6 = op extend_zero_1_to_13 _t5; + 1 32 _t8 = const 12; + 1 32 _t9 = op extend_zero_13_to_32 _t6; + 1 32 _t10 = op lsl_32 _t9 _t8; + 1 32 _t12 = const 7; + 1 32 _t13 = op lsr_32 _t1 _t12; + 1 1 _t14 = op extract_low_32_to_1 _t13; + 1 13 _t15 = op extend_zero_1_to_13 _t14; + 1 32 _t17 = const 11; + 1 32 _t18 = op extend_zero_13_to_32 _t15; + 1 32 _t19 = op lsl_32 _t18 _t17; + 1 32 _t21 = op orr_32 _t10 _t19; + 1 32 _t23 = const 25; + 1 32 _t24 = op lsr_32 _t1 _t23; + 1 6 _t25 = op extract_low_32_to_6 _t24; + 1 13 _t26 = op extend_zero_6_to_13 _t25; + 1 32 _t28 = const 5; + 1 32 _t29 = op extend_zero_13_to_32 _t26; + 1 32 _t30 = op lsl_32 _t29 _t28; + 1 32 _t32 = op orr_32 _t21 _t30; + 1 32 _t34 = const 8; + 1 32 _t35 = op lsr_32 _t1 _t34; + 1 4 _t36 = op extract_low_32_to_4 _t35; + 1 13 _t37 = op extend_zero_4_to_13 _t36; + 1 32 _t39 = const 1; + 1 32 _t40 = op extend_zero_13_to_32 _t37; + 1 32 _t41 = op lsl_32 _t40 _t39; + 1 32 _t43 = op orr_32 _t32 _t41; + 1 13 _t45 = op extract_low_32_to_13 _t43; + 1 32 _t46 = op extend_sign_13_to_32 _t45; + 1 = output 0 _t46; + - name: encode_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 99; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 1 _t10 = const 0; + 1 32 _t11 = op extend_zero_1_to_32 _t10; + 1 32 _t12 = const 14; + 1 32 _t13 = op lsl_32 _t11 _t12; + 1 32 _t14 = op orr_32 _t9 _t13; + 1 32 _t15 = const 19; + 1 32 _t16 = op lsl_32 _t2 _t15; + 1 32 _t17 = op orr_32 _t14 _t16; + 1 32 _t18 = const 24; + 1 32 _t19 = op lsl_32 _t3 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 = output 0 _t20; + - name: constraint_1 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 99; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 20; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 12 _t5 = op extract_low_32_to_12 _t4; + 1 32 _t6 = op extend_sign_12_to_32 _t5; + 1 = output 0 _t6; + - name: encode_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 103; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 0; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_2 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 103; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: decode_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t3 = const 25; + 1 32 _t4 = op lsr_32 _t1 _t3; + 1 7 _t5 = op extract_low_32_to_7 _t4; + 1 12 _t6 = op extend_zero_7_to_12 _t5; + 1 32 _t8 = const 5; + 1 32 _t9 = op extend_zero_12_to_32 _t6; + 1 32 _t10 = op lsl_32 _t9 _t8; + 1 32 _t12 = const 7; + 1 32 _t13 = op lsr_32 _t1 _t12; + 1 5 _t14 = op extract_low_32_to_5 _t13; + 1 12 _t15 = op extend_zero_5_to_12 _t14; + 1 32 _t17 = op extend_zero_12_to_32 _t15; + 1 32 _t18 = op orr_32 _t10 _t17; + 1 12 _t20 = op extract_low_32_to_12 _t18; + 1 32 _t21 = op extend_sign_12_to_32 _t20; + 1 = output 0 _t21; + - name: encode_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 35; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 1 _t10 = const 1; + 1 32 _t11 = op extend_zero_1_to_32 _t10; + 1 32 _t12 = const 14; + 1 32 _t13 = op lsl_32 _t11 _t12; + 1 32 _t14 = op orr_32 _t9 _t13; + 1 32 _t15 = const 19; + 1 32 _t16 = op lsl_32 _t2 _t15; + 1 32 _t17 = op orr_32 _t14 _t16; + 1 32 _t18 = const 24; + 1 32 _t19 = op lsl_32 _t3 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 = output 0 _t20; + - name: constraint_3 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 4131; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: encode_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 3; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 1; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_4 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 4099; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: encode_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = input 1; + 1 32 _t3 = input 2; + 1 32 _t4 = const 0; + 1 1 _t5 = const 3; + 1 32 _t6 = op extend_zero_1_to_32 _t5; + 1 32 _t7 = const 6; + 1 32 _t8 = op lsl_32 _t6 _t7; + 1 32 _t9 = op orr_32 _t4 _t8; + 1 32 _t10 = const 11; + 1 32 _t11 = op lsl_32 _t3 _t10; + 1 32 _t12 = op orr_32 _t9 _t11; + 1 1 _t13 = const 5; + 1 32 _t14 = op extend_zero_1_to_32 _t13; + 1 32 _t15 = const 14; + 1 32 _t16 = op lsl_32 _t14 _t15; + 1 32 _t17 = op orr_32 _t12 _t16; + 1 32 _t18 = const 19; + 1 32 _t19 = op lsl_32 _t2 _t18; + 1 32 _t20 = op orr_32 _t17 _t19; + 1 32 _t21 = const 31; + 1 32 _t22 = op lsl_32 _t1 _t21; + 1 32 _t23 = op orr_32 _t20 _t22; + 1 = output 0 _t23; + - name: constraint_5 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 28799; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 20483; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; + - name: constraint_6 + seq: | + 1 32 _t1 = input 0; + 1 32 _t2 = const 4294967295; + 1 32 _t3 = op and_32 _t1 _t2; + 1 32 _t4 = const 115; + 1 1 _t5 = op eq_32 _t3 _t4; + 1 = output 0 _t5; +instructions: + - name: add + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - rs2 + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 51 + const_mask: 4261441663 + decode: + - decode_0 + - decode_1 + - decode_2 + encode: encode_0 + constraint_decode: constraint_0 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t7 = input 0; + 1 32 _t8 = read XRegs _t7; + 1 32 _t10 = op add_32 _t4 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: beq + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rs2 + encoding: + encoded_size: 32 + const_encoding_part: 99 + const_mask: 28799 + decode: + - decode_3 + - decode_1 + - decode_0 + encode: encode_1 + constraint_decode: constraint_1 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t6 = input 2; + 1 32 _t7 = read XRegs _t6; + 1 1 _t8 = op eq_32 _t4 _t7; + 1 32 _t10 = env getPC; + 1 32 _t11 = input 0; + 1 32 _t12 = op add_32 _t10 _t11; + 1 32 _t14 = const 4; + 1 32 _t15 = op add_32 _t10 _t14; + 1 32 _t17 = op select_32 _t8 _t12 _t15; + 1 = env setPC _t17; + - name: jalr + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 103 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_2 + constraint_decode: constraint_2 + constraint_encode: '' + semantic: | + 1 32 _t2 = env getPC; + 1 32 _t3 = const 4; + 1 32 _t4 = op add_32 _t2 _t3; + 1 32 _t8 = input 1; + 1 32 _t9 = read XRegs _t8; + 1 32 _t10 = input 0; + 1 32 _t11 = op add_32 _t9 _t10; + 1 32 _t13 = const -2; + 1 32 _t14 = op and_32 _t11 _t13; + 1 = env setPC _t14; + 1 32 _t15 = input 2; + 1 = write XRegs _t15 _t4; + - name: sh + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rs2 + encoding: + encoded_size: 32 + const_encoding_part: 4131 + const_mask: 28799 + decode: + - decode_5 + - decode_1 + - decode_0 + encode: encode_3 + constraint_decode: constraint_3 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 32 _t9 = input 2; + 1 32 _t10 = read XRegs _t9; + 1 32 _t11 = const 0; + 1 32 _t12 = op lsr_32 _t10 _t11; + 1 16 _t13 = op extract_low_32_to_16 _t12; + 1 = env writeMem16 _t6 _t13; + - name: lh + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 4099 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_4 + constraint_decode: constraint_4 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 16 _t8 = env readMem16 _t6; + 1 32 _t10 = op extend_sign_16_to_32 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: lhu + attributes: [] + operand_sizes: + - 32 + - 32 + - 32 + operand_names: + - imm + - rs1 + - rd + encoding: + encoded_size: 32 + const_encoding_part: 20483 + const_mask: 28799 + decode: + - decode_4 + - decode_1 + - decode_2 + encode: encode_5 + constraint_decode: constraint_5 + constraint_encode: '' + semantic: | + 1 32 _t3 = input 1; + 1 32 _t4 = read XRegs _t3; + 1 32 _t5 = input 0; + 1 32 _t6 = op add_32 _t4 _t5; + 1 16 _t8 = env readMem16 _t6; + 1 32 _t10 = op extend_zero_16_to_32 _t8; + 1 32 _t11 = input 2; + 1 = write XRegs _t11 _t10; + - name: ecall + attributes: [] + operand_sizes: [] + operand_names: [] + encoding: + encoded_size: 32 + const_encoding_part: 115 + const_mask: 4294967295 + decode: [] + encode: '' + constraint_decode: constraint_6 + constraint_encode: '' + semantic: | + 1 = env sysCall; diff --git a/tools/yaml_canonicalize.py b/tools/yaml_canonicalize.py new file mode 100755 index 0000000..8796537 --- /dev/null +++ b/tools/yaml_canonicalize.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import sys +from pathlib import Path +from ruamel.yaml import YAML +from ruamel.yaml.scalarstring import LiteralScalarString + +yaml = YAML() +yaml.default_flow_style = False +yaml.indent(mapping=2, sequence=4, offset=2) +yaml.preserve_quotes = True + + +def recursive_literalize(obj): + if isinstance(obj, dict): + return {k: recursive_literalize(v) for k, v in obj.items()} + if isinstance(obj, list): + return [recursive_literalize(v) for v in obj] + if isinstance(obj, str) and '\n' in obj: + return LiteralScalarString(obj) + return obj + + +def main(): + if len(sys.argv) < 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + + input_path = Path(sys.argv[1]) + output_path = Path(sys.argv[2]) + + data = yaml.load(input_path) + data = recursive_literalize(data) + yaml.dump(data, output_path) + + content = output_path.read_text() + content = content.replace('\"\"', "''") + import re + content = re.sub(r'":(\w+)"', r':\1', content) + output_path.write_text(content) + + +if __name__ == "__main__": + main()