diff --git a/python/lira/arch.py b/python/lira/arch.py index a36db61..18a860e 100644 --- a/python/lira/arch.py +++ b/python/lira/arch.py @@ -1,8 +1,10 @@ -from typing import Optional +from typing import ClassVar, Dict, Optional, TypeVar from dataclasses import dataclass, field from .ir import * +OperationT = TypeVar("OperationT", bound="Operation") + @dataclass class Component: name: str @@ -24,6 +26,22 @@ class Operation(Component): semantic_func_128: Optional[str] = None # Snippet semantic_table: Optional[str] = None # TableInt + typed_ops: ClassVar[Dict[str, type]] = {} + + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + sb = getattr(cls, "op_base", None) + if sb is not None: + Operation.typed_ops[sb] = cls + + def _restore_from(self: "OperationT", other: "Operation") -> "OperationT": + self.name = other.name + self.attributes = other.attributes + self.semantic_func = other.semantic_func + self.semantic_func_128 = other.semantic_func_128 + self.semantic_table = other.semantic_table + return self + def __eq__(self, other): if not isinstance(other, Operation): return NotImplemented @@ -95,6 +113,7 @@ class Instruction(Component): semantic: StatementSeq + @dataclass class Arch(Component): # Constant Architecture part diff --git a/python/lira/arch_ser_yaml.py b/python/lira/arch_ser_yaml.py index ff6d7a0..1b4be6c 100644 --- a/python/lira/arch_ser_yaml.py +++ b/python/lira/arch_ser_yaml.py @@ -7,6 +7,7 @@ from .arch import * from .ir import StatementSeq +from .ir_ops import from_operation from .ir_ser_txt import serialize_statement_seq, deserialize_statement_seq def to_serializable(obj: Any) -> Any: @@ -33,7 +34,10 @@ def from_serializable(cls: Type, data: Any) -> Any: kwargs[name] = deserialize_statement_seq(value) else: kwargs[name] = from_serializable(types[name], value) - return cls(**kwargs) + obj = cls(**kwargs) + if cls is Operation: + return from_operation(obj) + return obj if origin is list: item_cls = get_args(cls)[0] return [from_serializable(item_cls, item) for item in data] diff --git a/python/lira/ir_ops.py b/python/lira/ir_ops.py index 6d7fbfe..a38d79e 100644 --- a/python/lira/ir_ops.py +++ b/python/lira/ir_ops.py @@ -1,3 +1,6 @@ +from enum import Enum +from typing import ClassVar + from .arch import Operation @@ -5,7 +8,7 @@ class TypeCheckError(Exception): pass -class BaseOp: +class BaseOp(str, Enum): NOT = "not" NEG = "neg" ADD = "add" @@ -51,20 +54,22 @@ def check_bits(value: int, name: str): class UnaryOp(Operation): - def __init__(self, out_bits: int, semantic_base: str, name: str = ""): - if not name: - name = f"{semantic_base}_{out_bits}" + def __init__(self, out_bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{out_bits}", attributes=[], inputs=[out_bits], outputs=[out_bits], - semantic_base=semantic_base, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "UnaryOp": + return cls(op.inputs[0])._restore_from(op) + def _check_signature(self): check_bits(self.inputs[0], "input width") check_bits(self.outputs[0], "output width") @@ -75,20 +80,22 @@ def _check_signature(self): class BinaryOp(Operation): - def __init__(self, bits: int, semantic_base: str, name: str = ""): - if not name: - name = f"{semantic_base}_{bits}" + def __init__(self, bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{bits}", attributes=[], inputs=[bits, bits], outputs=[bits], - semantic_base=semantic_base, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "BinaryOp": + return cls(op.inputs[0])._restore_from(op) + def _check_signature(self): for i, inp in enumerate(self.inputs): check_bits(inp, f"input[{i}]") @@ -100,26 +107,25 @@ def _check_signature(self): 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}" + def __init__(self, bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{bits}", attributes=[], inputs=[bits, bits], - outputs=[out_bits], - semantic_base=semantic_base, + outputs=[1], + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "CmpOp": + return cls(op.inputs[0])._restore_from(op) + 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]}" @@ -127,20 +133,22 @@ def _check_signature(self): class TernaryOp(Operation): - def __init__(self, bits: int, semantic_base: str, name: str = ""): - if not name: - name = f"{semantic_base}_{bits}" + def __init__(self, bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{bits}", attributes=[], inputs=[bits, bits, bits], outputs=[bits], - semantic_base=semantic_base, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "TernaryOp": + return cls(op.inputs[0])._restore_from(op) + def _check_signature(self): for i, inp in enumerate(self.inputs): check_bits(inp, f"input[{i}]") @@ -152,20 +160,22 @@ def _check_signature(self): 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}" + def __init__(self, in_bits: int, out_bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{in_bits}_to_{out_bits}", attributes=[], inputs=[in_bits], outputs=[out_bits], - semantic_base=semantic_base, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "ExtendOp": + return cls(op.inputs[0], op.outputs[0])._restore_from(op) + def _check_signature(self): check_bits(self.inputs[0], "input") check_bits(self.outputs[0], "output") @@ -176,20 +186,22 @@ def _check_signature(self): 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}" + def __init__(self, in_bits: int, out_bits: int): super().__init__( - name=name, + name=f"{self.op_base.value}_{in_bits}_to_{out_bits}", attributes=[], inputs=[in_bits], outputs=[out_bits], - semantic_base=semantic_base, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) self._check_signature() + @classmethod + def from_operation(cls, op: Operation) -> "ExtractLowOp": + return cls(op.inputs[0], op.outputs[0])._restore_from(op) + def _check_signature(self): check_bits(self.inputs[0], "input") check_bits(self.outputs[0], "output") @@ -200,186 +212,260 @@ def _check_signature(self): class Not(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.NOT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.NOT) + super().__init__(bits) class Neg(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.NEG + def __init__(self, bits: int): - super().__init__(bits, BaseOp.NEG) + super().__init__(bits) class Add(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.ADD + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ADD) + super().__init__(bits) class Sub(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.SUB + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SUB) + super().__init__(bits) class Mul(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.MUL + def __init__(self, bits: int): - super().__init__(bits, BaseOp.MUL) + super().__init__(bits) class And(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.AND + def __init__(self, bits: int): - super().__init__(bits, BaseOp.AND) + super().__init__(bits) class Orr(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.ORR + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ORR) + super().__init__(bits) class Xor(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.XOR + def __init__(self, bits: int): - super().__init__(bits, BaseOp.XOR) + super().__init__(bits) class Lsl(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.LSL + def __init__(self, bits: int): - super().__init__(bits, BaseOp.LSL) + super().__init__(bits) class Lsr(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.LSR + def __init__(self, bits: int): - super().__init__(bits, BaseOp.LSR) + super().__init__(bits) class Asr(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.ASR + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ASR) + super().__init__(bits) class Eq(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.EQ + def __init__(self, bits: int): - super().__init__(bits, BaseOp.EQ) + super().__init__(bits) class Ne(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.NE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.NE) + super().__init__(bits) class Slt(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.SLT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SLT) + super().__init__(bits) class Sle(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.SLE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SLE) + super().__init__(bits) class Sgt(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.SGT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SGT) + super().__init__(bits) class Sge(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.SGE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SGE) + super().__init__(bits) class Ult(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.ULT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ULT) + super().__init__(bits) class Ule(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.ULE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ULE) + super().__init__(bits) class Ugt(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.UGT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.UGT) + super().__init__(bits) class Uge(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.UGE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.UGE) + super().__init__(bits) class ExtendSign(ExtendOp): + op_base: ClassVar[BaseOp] = BaseOp.EXTEND_SIGN + def __init__(self, in_bits: int, out_bits: int): - super().__init__(in_bits, out_bits, BaseOp.EXTEND_SIGN) + super().__init__(in_bits, out_bits) class ExtendZero(ExtendOp): + op_base: ClassVar[BaseOp] = BaseOp.EXTEND_ZERO + def __init__(self, in_bits: int, out_bits: int): - super().__init__(in_bits, out_bits, BaseOp.EXTEND_ZERO) + super().__init__(in_bits, out_bits) class ExtractLow(ExtractLowOp): + op_base: ClassVar[BaseOp] = BaseOp.EXTRACT_LOW + def __init__(self, in_bits: int, out_bits: int): - super().__init__(in_bits, out_bits, BaseOp.EXTRACT_LOW) + super().__init__(in_bits, out_bits) class Popcnt(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.POPCNT + def __init__(self, bits: int): - super().__init__(bits, BaseOp.POPCNT) + super().__init__(bits) class Ctz(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.CTZ + def __init__(self, bits: int): - super().__init__(bits, BaseOp.CTZ) + super().__init__(bits) class Clz(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.CLZ + def __init__(self, bits: int): - super().__init__(bits, BaseOp.CLZ) + super().__init__(bits) class Reverse(UnaryOp): + op_base: ClassVar[BaseOp] = BaseOp.REVERSE + def __init__(self, bits: int): - super().__init__(bits, BaseOp.REVERSE) + super().__init__(bits) class RemU(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.REM_U + def __init__(self, bits: int): - super().__init__(bits, BaseOp.REM_U) + super().__init__(bits) class RemS(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.REM_S + def __init__(self, bits: int): - super().__init__(bits, BaseOp.REM_S) + super().__init__(bits) class Ror(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.ROR + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ROR) + super().__init__(bits) class Rol(BinaryOp): + op_base: ClassVar[BaseOp] = BaseOp.ROL + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ROL) + super().__init__(bits) class AddOverflow(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.ADD_OVERFLOW + def __init__(self, bits: int): - super().__init__(bits, BaseOp.ADD_OVERFLOW, out_bits=1) + super().__init__(bits) class SubOverflow(CmpOp): + op_base: ClassVar[BaseOp] = BaseOp.SUB_OVERFLOW + def __init__(self, bits: int): - super().__init__(bits, BaseOp.SUB_OVERFLOW, out_bits=1) + super().__init__(bits) class DivU(TernaryOp): + op_base: ClassVar[BaseOp] = BaseOp.DIV_U + def __init__(self, bits: int): - super().__init__(bits, BaseOp.DIV_U) + super().__init__(bits) class DivS(TernaryOp): + op_base: ClassVar[BaseOp] = BaseOp.DIV_S + def __init__(self, bits: int): - super().__init__(bits, BaseOp.DIV_S) + super().__init__(bits) class Select(Operation): + op_base: ClassVar[BaseOp] = BaseOp.SELECT + def __init__(self, bits: int): name = f"select_{bits}" super().__init__( @@ -387,7 +473,7 @@ def __init__(self, bits: int): attributes=[], inputs=[1, bits, bits], outputs=[bits], - semantic_base=BaseOp.SELECT, + semantic_base=self.op_base.value, semantic_func=None, semantic_table=None, ) @@ -401,3 +487,20 @@ def _check_signature(self): raise TypeCheckError( "Select: mismatched widths of true/false branches and output" ) + + @classmethod + def from_operation(cls, op: Operation) -> "Select": + return cls(op.inputs[1])._restore_from(op) + + +def from_operation(op: Operation) -> Operation: + sb = op.semantic_base + + if sb is None: + return op + + cls = Operation.typed_ops.get(sb) + if cls is None: + assert False, f"Unexpected operation with semantic {sb}" + + return cls.from_operation(op) diff --git a/python/lira/ir_std.py b/python/lira/ir_std.py index 260908f..1c238b3 100644 --- a/python/lira/ir_std.py +++ b/python/lira/ir_std.py @@ -2,38 +2,60 @@ from .arch import * from dataclasses import dataclass +from enum import Enum +from typing import ClassVar + + +class StmtKind(str, Enum): + INPUT = "input" + OUTPUT = "output" + READ = "read" + WRITE = "write" + OP = "op" + ENV = "env" + COND_ENV = "cond_env" + CONST = "const" + DYN_CONST = "dyn_const" + @dataclass class StmtInput: + kind: ClassVar[StmtKind] = StmtKind.INPUT id_: int @dataclass class StmtOutput: + kind: ClassVar[StmtKind] = StmtKind.OUTPUT id_: int value: str @dataclass class StmtRead: + kind: ClassVar[StmtKind] = StmtKind.READ rf: RegisterFile rsi: str @dataclass class StmtWrite: + kind: ClassVar[StmtKind] = StmtKind.WRITE rf: RegisterFile rsi: str value: str @dataclass class StmtOp: + kind: ClassVar[StmtKind] = StmtKind.OP op: Operation args: list[str] @dataclass class StmtEnv: + kind: ClassVar[StmtKind] = StmtKind.ENV env: EnvironmentFunction args: list[str] class CondEnv: + kind: ClassVar[StmtKind] = StmtKind.COND_ENV env: EnvironmentFunction cond: str on_false: list[str] @@ -45,10 +67,12 @@ class StmtIndex: @dataclass class StmtConst: + kind: ClassVar[StmtKind] = StmtKind.CONST value: int @dataclass class StmtDynConst: + kind: ClassVar[StmtKind] = StmtKind.DYN_CONST name: str @dataclass diff --git a/python/tests/unit/test_arch_ser_yaml.py b/python/tests/unit/test_arch_ser_yaml.py index 2d5c95f..229a2d5 100644 --- a/python/tests/unit/test_arch_ser_yaml.py +++ b/python/tests/unit/test_arch_ser_yaml.py @@ -5,6 +5,7 @@ from python.lira.ir import Shape from python.lira.arch import ( + Operation, Register, RegisterFile, EnvironmentFunction, @@ -238,3 +239,73 @@ def test_rv32i_lite_roundtrip(self, rv32i_arch_lite, tmp_yaml): assert arch2.instructions[1].name == "lhu" assert arch2.instructions[2].name == "beq" assert arch2.instructions[3].name == "ecall" + + def test_typed_operation_reconstruction(self, rv32i_arch_lite, tmp_yaml): + arch_ser_yaml.write_arch(rv32i_arch_lite, tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + + ops = {op.name: op for op in arch2.operations} + assert isinstance(ops["lsr_32"], Lsr) + assert isinstance(ops["extract_low_32_to_5"], ExtractLow) + assert isinstance(ops["extend_sign_12_to_32"], ExtendSign) + assert isinstance(ops["extend_zero_5_to_32"], ExtendZero) + assert isinstance(ops["add_32"], Add) + assert ops["add_32"].semantic_base == "add" + assert ops["lsr_32"].semantic_base == "lsr" + + def test_typed_ops_registry(self): + from python.lira.arch import Operation + from python.lira.ir_ops import BaseOp + + assert Operation.typed_ops[BaseOp.ADD] is Add + assert Operation.typed_ops[BaseOp.LSR] is Lsr + assert Operation.typed_ops[BaseOp.SELECT] is Select + assert Operation.typed_ops[BaseOp.EXTEND_SIGN] is ExtendSign + # Every registered class declares the matching op_base ClassVar. + for op_base, cls in Operation.typed_ops.items(): + assert issubclass(cls, Operation) + assert getattr(cls, "op_base", None) == op_base + # Every BaseOp member is registered (and vice versa). + assert {member for member in BaseOp} == set(Operation.typed_ops) + + def test_from_operation_restores_fields(self): + op = Operation( + name="custom_add", + attributes=["volatile"], + inputs=[32, 32], + outputs=[32], + semantic_base="add", + semantic_func="add_func", + semantic_func_128="add_func_128", + semantic_table="add_table", + ) + typed = Add.from_operation(op) + assert isinstance(typed, Add) + assert typed.name == "custom_add" + assert typed.attributes == ["volatile"] + assert typed.semantic_base == "add" + assert typed.semantic_func == "add_func" + assert typed.semantic_func_128 == "add_func_128" + assert typed.semantic_table == "add_table" + + def test_typed_operation_roundtrip_preserves_fields(self, tmp_yaml): + op = Operation( + name="custom_add", + attributes=["volatile"], + inputs=[32, 32], + outputs=[32], + semantic_base="add", + semantic_func="add_func", + semantic_table="add_table", + ) + ab = ArchBuilder("fields", []) + ab.add_operation(op) + arch_ser_yaml.write_arch(ab.build(), tmp_yaml) + arch2 = arch_ser_yaml.read_arch(tmp_yaml) + + op2 = arch2.operations[0] + assert isinstance(op2, Add) + assert op2.name == "custom_add" + assert op2.attributes == ["volatile"] + assert op2.semantic_func == "add_func" + assert op2.semantic_table == "add_table" diff --git a/python/tests/unit/test_ir_builder.py b/python/tests/unit/test_ir_builder.py index 9043793..a7a9cc7 100644 --- a/python/tests/unit/test_ir_builder.py +++ b/python/tests/unit/test_ir_builder.py @@ -246,3 +246,31 @@ def test_add_env_operation_snippet(self): assert len(arch.environment_functions) == 1 assert len(arch.operations) == 1 assert len(arch.snippets) == 1 + + +def test_stmt_kinds(): + from python.lira.ir_std import ( + CondEnv, + StmtConst, + StmtDynConst, + StmtEnv, + StmtInput, + StmtOp, + StmtOutput, + StmtRead, + StmtWrite, + StmtKind, + ) + + assert StmtInput.kind is StmtKind.INPUT + assert StmtOutput.kind is StmtKind.OUTPUT + assert StmtConst.kind is StmtKind.CONST + assert StmtOp.kind is StmtKind.OP + assert StmtRead.kind is StmtKind.READ + assert StmtWrite.kind is StmtKind.WRITE + assert StmtEnv.kind is StmtKind.ENV + assert CondEnv.kind is StmtKind.COND_ENV + assert StmtDynConst.kind is StmtKind.DYN_CONST + + for member in StmtKind: + assert member == member.value diff --git a/ruby/lira/arch.rb b/ruby/lira/arch.rb index 8775d25..e8d85c4 100644 --- a/ruby/lira/arch.rb +++ b/ruby/lira/arch.rb @@ -48,6 +48,26 @@ def ==(other) class Operation < Component attr_accessor :inputs, :outputs, :semantic_base, :semantic_func, :semantic_func_128, :semantic_table + @typed_ops = {} + + class << self + attr_reader :typed_ops, :op_base + + def op_base=(value) + Operation.typed_ops[value] = self + @op_base = value + end + end + + def restore_from(other) + self.name = other.name + self.attributes = other.attributes + self.semantic_func = other.semantic_func + self.semantic_func_128 = other.semantic_func_128 + self.semantic_table = other.semantic_table + self + end + def initialize(name, attributes, inputs, outputs, semantic_base: nil, semantic_func: nil, semantic_func_128: nil, semantic_table: nil) super(name, attributes) @@ -84,11 +104,15 @@ def self.from_h(hash) 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) + op = new(name, attributes, inputs, outputs, + semantic_base: semantic_base, + semantic_func: semantic_func, + semantic_func_128: semantic_func_128, + semantic_table: semantic_table) + + return op unless Lira.respond_to?(:from_operation) + + Lira.from_operation(op) end def ==(other) diff --git a/ruby/lira/arch_ser_yaml.rb b/ruby/lira/arch_ser_yaml.rb index 6ab6b44..4e0e290 100644 --- a/ruby/lira/arch_ser_yaml.rb +++ b/ruby/lira/arch_ser_yaml.rb @@ -2,6 +2,7 @@ require 'yaml' require_relative 'ir' require_relative 'arch' +require_relative 'ir_ops' module Lira module ArchSerYaml diff --git a/ruby/lira/ir_ops.rb b/ruby/lira/ir_ops.rb index 1afc94e..8b1d1eb 100644 --- a/ruby/lira/ir_ops.rb +++ b/ruby/lira/ir_ops.rb @@ -5,53 +5,57 @@ 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 + 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], + def initialize(out_bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{out_bits}", [], [out_bits], [out_bits], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0]).restore_from(op) + 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 @@ -60,13 +64,17 @@ def check_signature end class BinaryOp < Operation - def initialize(bits, semantic_base, name: nil) - name ||= "#{semantic_base}_#{bits}" - super(name, [], [bits, bits], [bits], + def initialize(bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{bits}", [], [bits, bits], [bits], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0]).restore_from(op) + 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 @@ -78,29 +86,36 @@ def check_signature end class CmpOp < Operation - def initialize(bits, semantic_base, out_bits = 1, name: nil) - name ||= "#{semantic_base}_#{bits}" - super(name, [], [bits, bits], [out_bits], + def initialize(bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{bits}", [], [bits, bits], [1], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0]).restore_from(op) + 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], + def initialize(bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{bits}", [], [bits, bits, bits], [bits], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0]).restore_from(op) + 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 @@ -113,13 +128,17 @@ def check_signature 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], + def initialize(in_bits, out_bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{in_bits}_to_#{out_bits}", [], [in_bits], [out_bits], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0], op.outputs[0]).restore_from(op) + 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 @@ -128,13 +147,17 @@ def check_signature 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], + def initialize(in_bits, out_bits) + semantic_base = self.class.op_base + super("#{semantic_base}_#{in_bits}_to_#{out_bits}", [], [in_bits], [out_bits], semantic_base: semantic_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[0], op.outputs[0]).restore_from(op) + 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 @@ -143,159 +166,247 @@ def check_signature end class Not < UnaryOp - def initialize(bits); super(bits, BaseOp::NOT); end + self.op_base = BaseOp::NOT + + def initialize(bits); super(bits); end end class Neg < UnaryOp - def initialize(bits); super(bits, BaseOp::NEG); end + self.op_base = BaseOp::NEG + + def initialize(bits); super(bits); end end class Add < BinaryOp - def initialize(bits); super(bits, BaseOp::ADD); end + self.op_base = BaseOp::ADD + + def initialize(bits); super(bits); end end class Sub < BinaryOp - def initialize(bits); super(bits, BaseOp::SUB); end + self.op_base = BaseOp::SUB + + def initialize(bits); super(bits); end end class Mul < BinaryOp - def initialize(bits); super(bits, BaseOp::MUL); end + self.op_base = BaseOp::MUL + + def initialize(bits); super(bits); end end class And < BinaryOp - def initialize(bits); super(bits, BaseOp::AND); end + self.op_base = BaseOp::AND + + def initialize(bits); super(bits); end end class Orr < BinaryOp - def initialize(bits); super(bits, BaseOp::ORR); end + self.op_base = BaseOp::ORR + + def initialize(bits); super(bits); end end class Xor < BinaryOp - def initialize(bits); super(bits, BaseOp::XOR); end + self.op_base = BaseOp::XOR + + def initialize(bits); super(bits); end end class Lsl < BinaryOp - def initialize(bits); super(bits, BaseOp::LSL); end + self.op_base = BaseOp::LSL + + def initialize(bits); super(bits); end end class Lsr < BinaryOp - def initialize(bits); super(bits, BaseOp::LSR); end + self.op_base = BaseOp::LSR + + def initialize(bits); super(bits); end end class Asr < BinaryOp - def initialize(bits); super(bits, BaseOp::ASR); end + self.op_base = BaseOp::ASR + + def initialize(bits); super(bits); end end class Eq < CmpOp - def initialize(bits); super(bits, BaseOp::EQ); end + self.op_base = BaseOp::EQ + + def initialize(bits); super(bits); end end class Ne < CmpOp - def initialize(bits); super(bits, BaseOp::NE); end + self.op_base = BaseOp::NE + + def initialize(bits); super(bits); end end class Slt < CmpOp - def initialize(bits); super(bits, BaseOp::SLT); end + self.op_base = BaseOp::SLT + + def initialize(bits); super(bits); end end class Sle < CmpOp - def initialize(bits); super(bits, BaseOp::SLE); end + self.op_base = BaseOp::SLE + + def initialize(bits); super(bits); end end class Sgt < CmpOp - def initialize(bits); super(bits, BaseOp::SGT); end + self.op_base = BaseOp::SGT + + def initialize(bits); super(bits); end end class Sge < CmpOp - def initialize(bits); super(bits, BaseOp::SGE); end + self.op_base = BaseOp::SGE + + def initialize(bits); super(bits); end end class Ult < CmpOp - def initialize(bits); super(bits, BaseOp::ULT); end + self.op_base = BaseOp::ULT + + def initialize(bits); super(bits); end end class Ule < CmpOp - def initialize(bits); super(bits, BaseOp::ULE); end + self.op_base = BaseOp::ULE + + def initialize(bits); super(bits); end end class Ugt < CmpOp - def initialize(bits); super(bits, BaseOp::UGT); end + self.op_base = BaseOp::UGT + + def initialize(bits); super(bits); end end class Uge < CmpOp - def initialize(bits); super(bits, BaseOp::UGE); end + self.op_base = BaseOp::UGE + + def initialize(bits); super(bits); end end class ExtendSign < ExtendOp - def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTEND_SIGN); end + self.op_base = BaseOp::EXTEND_SIGN + + def initialize(in_bits, out_bits); super(in_bits, out_bits); end end class ExtendZero < ExtendOp - def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTEND_ZERO); end + self.op_base = BaseOp::EXTEND_ZERO + + def initialize(in_bits, out_bits); super(in_bits, out_bits); end end class ExtractLow < ExtractLowOp - def initialize(in_bits, out_bits); super(in_bits, out_bits, BaseOp::EXTRACT_LOW); end + self.op_base = BaseOp::EXTRACT_LOW + + def initialize(in_bits, out_bits); super(in_bits, out_bits); end end class Popcnt < UnaryOp - def initialize(bits); super(bits, BaseOp::POPCNT); end + self.op_base = BaseOp::POPCNT + + def initialize(bits); super(bits); end end class Ctz < UnaryOp - def initialize(bits); super(bits, BaseOp::CTZ); end + self.op_base = BaseOp::CTZ + + def initialize(bits); super(bits); end end class Clz < UnaryOp - def initialize(bits); super(bits, BaseOp::CLZ); end + self.op_base = BaseOp::CLZ + + def initialize(bits); super(bits); end end class Reverse < UnaryOp - def initialize(bits); super(bits, BaseOp::REVERSE); end + self.op_base = BaseOp::REVERSE + + def initialize(bits); super(bits); end end class RemU < BinaryOp - def initialize(bits); super(bits, BaseOp::REM_U); end + self.op_base = BaseOp::REM_U + + def initialize(bits); super(bits); end end class RemS < BinaryOp - def initialize(bits); super(bits, BaseOp::REM_S); end + self.op_base = BaseOp::REM_S + + def initialize(bits); super(bits); end end class Ror < BinaryOp - def initialize(bits); super(bits, BaseOp::ROR); end + self.op_base = BaseOp::ROR + + def initialize(bits); super(bits); end end class Rol < BinaryOp - def initialize(bits); super(bits, BaseOp::ROL); end + self.op_base = BaseOp::ROL + + def initialize(bits); super(bits); end end class AddOverflow < CmpOp - def initialize(bits); super(bits, BaseOp::ADD_OVERFLOW, out_bits: 1); end + self.op_base = BaseOp::ADD_OVERFLOW + + def initialize(bits); super(bits); end end class SubOverflow < CmpOp - def initialize(bits); super(bits, BaseOp::SUB_OVERFLOW, out_bits: 1); end + self.op_base = BaseOp::SUB_OVERFLOW + + def initialize(bits); super(bits); end end class DivU < TernaryOp - def initialize(bits); super(bits, BaseOp::DIV_U); end + self.op_base = BaseOp::DIV_U + + def initialize(bits); super(bits); end end class DivS < TernaryOp - def initialize(bits); super(bits, BaseOp::DIV_S); end + self.op_base = BaseOp::DIV_S + + def initialize(bits); super(bits); end end class Select < Operation + self.op_base = BaseOp::SELECT + def initialize(bits) name = "select_#{bits}" super(name, [], [1, bits, bits], [bits], - semantic_base: BaseOp::SELECT, semantic_func: nil, semantic_table: nil) + semantic_base: self.class.op_base, semantic_func: nil, semantic_table: nil) check_signature end + def self.from_operation(op) + new(op.inputs[1]).restore_from(op) + end + def check_signature raise TypeCheckError, 'true/false branches mismatch' unless inputs[1] == inputs[2] && inputs[1] == outputs[0] end end + + def self.from_operation(op) + sb = op.semantic_base + return op if sb.nil? + + cls = Operation.typed_ops[sb] + raise "Unexpected operation with semantic #{sb}" if cls.nil? + + cls.from_operation(op) + end end diff --git a/ruby/lira/ir_std.rb b/ruby/lira/ir_std.rb index 84961c1..0a581e0 100644 --- a/ruby/lira/ir_std.rb +++ b/ruby/lira/ir_std.rb @@ -2,9 +2,23 @@ require_relative 'ir' require_relative 'arch' +module StmtKind + INPUT = "input" + OUTPUT = "output" + READ = "read" + WRITE = "write" + OP = "op" + ENV = "env" + COND_ENV = "cond_env" + CONST = "const" + DYN_CONST = "dyn_const" +end + class StmtInput attr_accessor :id_ + def self.kind; StmtKind::INPUT; end + def initialize(id_) @id_ = id_ end @@ -13,6 +27,8 @@ def initialize(id_) class StmtOutput attr_accessor :id_, :value + def self.kind; StmtKind::OUTPUT; end + def initialize(id_, value) @id_ = id_ @value = value @@ -22,6 +38,8 @@ def initialize(id_, value) class StmtRead attr_accessor :rf, :rsi + def self.kind; StmtKind::READ; end + def initialize(rf, rsi) @rf = rf @rsi = rsi @@ -31,6 +49,8 @@ def initialize(rf, rsi) class StmtWrite attr_accessor :rf, :rsi, :value + def self.kind; StmtKind::WRITE; end + def initialize(rf, rsi, value) @rf = rf @rsi = rsi @@ -41,6 +61,8 @@ def initialize(rf, rsi, value) class StmtOp attr_accessor :op, :args + def self.kind; StmtKind::OP; end + def initialize(op, args) @op = op @args = args @@ -50,6 +72,8 @@ def initialize(op, args) class StmtEnv attr_accessor :env, :args + def self.kind; StmtKind::ENV; end + def initialize(env, args) @env = env @args = args @@ -59,6 +83,8 @@ def initialize(env, args) class CondEnv attr_accessor :env, :cond, :on_false, :inputs + def self.kind; StmtKind::COND_ENV; end + def initialize(env, cond, on_false, inputs) @env = env @cond = cond @@ -70,10 +96,16 @@ def initialize(env, cond, on_false, inputs) class StmtIndex; end class StmtConst attr_accessor :value + + def self.kind; StmtKind::CONST; end + def initialize(value); @value = value; end end class StmtDynConst attr_accessor :name + + def self.kind; StmtKind::DYN_CONST; end + def initialize(name); @name = name; end end class StmtGather diff --git a/ruby/tests/unit/test_arch_ser_yaml.rb b/ruby/tests/unit/test_arch_ser_yaml.rb index 182ca1f..0c00bce 100644 --- a/ruby/tests/unit/test_arch_ser_yaml.rb +++ b/ruby/tests/unit/test_arch_ser_yaml.rb @@ -140,4 +140,71 @@ def test_rv32i_lite_roundtrip arch2 = ArchSerYaml.read_arch(@tmp.path) assert_equal arch, arch2 end + + def test_typed_operation_reconstruction + ab = ArchBuilder.new('typed_ops', []) + ab.add_operation(Lsr.new(32)) + ab.add_operation(ExtractLow.new(32, 5)) + ab.add_operation(ExtendSign.new(12, 32)) + ab.add_operation(ExtendZero.new(5, 32)) + ab.add_operation(Add.new(32)) + arch = ab.build + ArchSerYaml.write_arch(arch, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + + ops = arch2.operations.to_h { |op| [op.name, op] } + assert_instance_of Lsr, ops['lsr_32'] + assert_instance_of ExtractLow, ops['extract_low_32_to_5'] + assert_instance_of ExtendSign, ops['extend_sign_12_to_32'] + assert_instance_of ExtendZero, ops['extend_zero_5_to_32'] + assert_instance_of Add, ops['add_32'] + assert_equal 'add', ops['add_32'].semantic_base + assert_equal 'lsr', ops['lsr_32'].semantic_base + end + + def test_typed_ops_registry + assert_same Add, Operation.typed_ops[BaseOp::ADD] + assert_same Lsr, Operation.typed_ops[BaseOp::LSR] + assert_same Select, Operation.typed_ops[BaseOp::SELECT] + assert_same ExtendSign, Operation.typed_ops[BaseOp::EXTEND_SIGN] + + Operation.typed_ops.each do |op_base, cls| + assert_operator cls, :<, Operation + assert_equal op_base, cls.op_base + end + + expected = BaseOp.constants.map { |c| BaseOp.const_get(c) }.sort + assert_equal expected, Operation.typed_ops.keys.sort + end + + def test_from_operation_restores_fields + op = Operation.new('custom_add', ['volatile'], [32, 32], [32], + semantic_base: 'add', semantic_func: 'add_func', + semantic_func_128: 'add_func_128', semantic_table: 'add_table') + typed = Add.from_operation(op) + assert_instance_of Add, typed + assert_equal 'custom_add', typed.name + assert_equal ['volatile'], typed.attributes + assert_equal 'add', typed.semantic_base + assert_equal 'add_func', typed.semantic_func + assert_equal 'add_func_128', typed.semantic_func_128 + assert_equal 'add_table', typed.semantic_table + end + + def test_typed_operation_roundtrip_preserves_fields + op = Operation.new('custom_add', ['volatile'], [32, 32], [32], + semantic_base: 'add', semantic_func: 'add_func', + semantic_table: 'add_table') + ab = ArchBuilder.new('fields', []) + ab.add_operation(op) + ArchSerYaml.write_arch(ab.build, @tmp.path) + arch2 = ArchSerYaml.read_arch(@tmp.path) + + op2 = arch2.operations[0] + assert_instance_of Add, op2 + assert_equal 'custom_add', op2.name + assert_equal ['volatile'], op2.attributes + assert_equal 'add_func', op2.semantic_func + assert_equal 'add_table', op2.semantic_table + end end diff --git a/ruby/tests/unit/test_ir_builder.rb b/ruby/tests/unit/test_ir_builder.rb index beabab6..a656c3f 100644 --- a/ruby/tests/unit/test_ir_builder.rb +++ b/ruby/tests/unit/test_ir_builder.rb @@ -1,5 +1,6 @@ $LOAD_PATH.unshift(File.expand_path('../..', __dir__)) require 'lira' +require 'lira/ir_std' require 'minitest/autorun' include Lira @@ -214,4 +215,20 @@ def test_add_env_operation_snippet assert_equal 1, arch.operations.length assert_equal 1, arch.snippets.length end + + def test_stmt_kinds + assert_equal StmtKind::INPUT, StmtInput.kind + assert_equal StmtKind::OUTPUT, StmtOutput.kind + assert_equal StmtKind::CONST, StmtConst.kind + assert_equal StmtKind::OP, StmtOp.kind + assert_equal StmtKind::READ, StmtRead.kind + assert_equal StmtKind::WRITE, StmtWrite.kind + assert_equal StmtKind::ENV, StmtEnv.kind + assert_equal StmtKind::COND_ENV, CondEnv.kind + assert_equal StmtKind::DYN_CONST, StmtDynConst.kind + + StmtKind.constants.each do |c| + assert_equal StmtKind.const_get(c), c.to_s.downcase + end + end end diff --git a/tests/integration/reference.yaml b/tests/integration/reference.yaml index cd13447..a9fb862 100644 --- a/tests/integration/reference.yaml +++ b/tests/integration/reference.yaml @@ -72,7 +72,7 @@ operations: - 32 outputs: - 32 - semantic_base: :add + semantic_base: add semantic_func: semantic_func_128: semantic_table: @@ -83,7 +83,7 @@ operations: - 32 outputs: - 32 - semantic_base: :lsr + semantic_base: lsr semantic_func: semantic_func_128: semantic_table: @@ -93,7 +93,7 @@ operations: - 32 outputs: - 5 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -103,7 +103,7 @@ operations: - 5 outputs: - 32 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -113,7 +113,7 @@ operations: - 1 outputs: - 32 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -124,7 +124,7 @@ operations: - 32 outputs: - 32 - semantic_base: :lsl + semantic_base: lsl semantic_func: semantic_func_128: semantic_table: @@ -135,7 +135,7 @@ operations: - 32 outputs: - 32 - semantic_base: :orr + semantic_base: orr semantic_func: semantic_func_128: semantic_table: @@ -146,7 +146,7 @@ operations: - 32 outputs: - 32 - semantic_base: :and + semantic_base: and semantic_func: semantic_func_128: semantic_table: @@ -157,7 +157,7 @@ operations: - 32 outputs: - 1 - semantic_base: :eq + semantic_base: eq semantic_func: semantic_func_128: semantic_table: @@ -169,7 +169,7 @@ operations: - 32 outputs: - 32 - semantic_base: :select + semantic_base: select semantic_func: semantic_func_128: semantic_table: @@ -179,7 +179,7 @@ operations: - 32 outputs: - 1 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -189,7 +189,7 @@ operations: - 1 outputs: - 13 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -199,7 +199,7 @@ operations: - 13 outputs: - 32 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -209,7 +209,7 @@ operations: - 32 outputs: - 6 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -219,7 +219,7 @@ operations: - 6 outputs: - 13 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -229,7 +229,7 @@ operations: - 32 outputs: - 4 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -239,7 +239,7 @@ operations: - 4 outputs: - 13 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -249,7 +249,7 @@ operations: - 32 outputs: - 13 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -259,7 +259,7 @@ operations: - 13 outputs: - 32 - semantic_base: :extend_sign + semantic_base: extend_sign semantic_func: semantic_func_128: semantic_table: @@ -269,7 +269,7 @@ operations: - 32 outputs: - 12 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -279,7 +279,7 @@ operations: - 12 outputs: - 32 - semantic_base: :extend_sign + semantic_base: extend_sign semantic_func: semantic_func_128: semantic_table: @@ -289,7 +289,7 @@ operations: - 32 outputs: - 16 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -299,7 +299,7 @@ operations: - 32 outputs: - 7 - semantic_base: :extract_low + semantic_base: extract_low semantic_func: semantic_func_128: semantic_table: @@ -309,7 +309,7 @@ operations: - 7 outputs: - 12 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -319,7 +319,7 @@ operations: - 12 outputs: - 32 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -329,7 +329,7 @@ operations: - 5 outputs: - 12 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: @@ -339,7 +339,7 @@ operations: - 16 outputs: - 32 - semantic_base: :extend_sign + semantic_base: extend_sign semantic_func: semantic_func_128: semantic_table: @@ -349,7 +349,7 @@ operations: - 16 outputs: - 32 - semantic_base: :extend_zero + semantic_base: extend_zero semantic_func: semantic_func_128: semantic_table: