예제 #1
0
    def test_cleanup_concrete_rtl_ireduce_bad(self):
        # type: () -> None
        x = Var('x')
        y = Var('y')
        x.set_typevar(TypeVar.singleton(i16.by(1)))
        r = Rtl(
                y << ireduce(x),
        )

        with self.assertRaises(AssertionError):
            r.cleanup_concrete_rtl()
예제 #2
0
 def setUp(self):
     # type: () -> None
     self.v0 = Var("v0")
     self.v1 = Var("v1")
     self.v2 = Var("v2")
     self.v3 = Var("v3")
     self.v4 = Var("v4")
     self.v5 = Var("v5")
     self.v6 = Var("v6")
     self.v7 = Var("v7")
     self.v8 = Var("v8")
     self.v9 = Var("v9")
     self.imm0 = Var("imm0")
     self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
                                  scalars=False, simd=True)
     self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
                        scalars=False, simd=True)
     self.b1 = TypeVar.singleton(b1)
예제 #3
0
RV32.legalize_type(
        default=narrow,
        i32=expand,
        f32=expand,
        f64=expand)

RV64.legalize_monomorphic(expand)
RV64.legalize_type(
        default=narrow,
        i32=expand,
        i64=expand,
        f32=expand,
        f64=expand)

# Dummies for instruction predicates.
x = Var('x')
y = Var('y')
dest = Var('dest')
args = Var('args')

# Basic arithmetic binary instructions are encoded in an R-type instruction.
for inst,           inst_imm,      f3,    f7 in [
        (base.iadd, base.iadd_imm, 0b000, 0b0000000),
        (base.isub, None,          0b000, 0b0100000),
        (base.bxor, base.bxor_imm, 0b100, 0b0000000),
        (base.bor,  base.bor_imm,  0b110, 0b0000000),
        (base.band, base.band_imm, 0b111, 0b0000000)
        ]:
    RV32.enc(inst.i32, R, OP(f3, f7))
    RV64.enc(inst.i64, R, OP(f3, f7))
예제 #4
0
expand.custom_legalize(insts.global_addr, 'expand_global_addr')
expand.custom_legalize(insts.heap_addr, 'expand_heap_addr')

# Custom expansions that need to change the CFG.
# TODO: Add sufficient XForm syntax that we don't need to hand-code these.
expand.custom_legalize(insts.trapz, 'expand_cond_trap')
expand.custom_legalize(insts.trapnz, 'expand_cond_trap')
expand.custom_legalize(insts.br_table, 'expand_br_table')
expand.custom_legalize(insts.select, 'expand_select')

# Custom expansions for floating point constants.
# These expansions require bit-casting or creating constant pool entries.
expand.custom_legalize(insts.f32const, 'expand_fconst')
expand.custom_legalize(insts.f64const, 'expand_fconst')

x = Var('x')
y = Var('y')
a = Var('a')
a1 = Var('a1')
a2 = Var('a2')
b = Var('b')
b1 = Var('b1')
b2 = Var('b2')
b_in = Var('b_in')
b_int = Var('b_int')
c = Var('c')
c1 = Var('c1')
c2 = Var('c2')
c_in = Var('c_in')
c_int = Var('c_int')
xl = Var('xl')
예제 #5
0
class TestRuntimeChecks(TestCase):

    def setUp(self):
        # type: () -> None
        self.v0 = Var("v0")
        self.v1 = Var("v1")
        self.v2 = Var("v2")
        self.v3 = Var("v3")
        self.v4 = Var("v4")
        self.v5 = Var("v5")
        self.v6 = Var("v6")
        self.v7 = Var("v7")
        self.v8 = Var("v8")
        self.v9 = Var("v9")
        self.imm0 = Var("imm0")
        self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
                                     scalars=False, simd=True)
        self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
                           scalars=False, simd=True)
        self.b1 = TypeVar.singleton(b1)

    def check_yo_check(self, xform, expected_f):
        # type: (XForm, CheckProducer) -> None
        fmt = Formatter()
        type_sets = UniqueTable()
        for check in get_runtime_typechecks(xform):
            emit_runtime_typecheck(check, fmt, type_sets)

        # Remove comments
        got = "".join([l for l in fmt.lines if not l.strip().startswith("//")])
        expected = expected_f(type_sets)
        self.assertEqual(got, expected)

    def test_width_check(self):
        # type: () -> None
        x = XForm(Rtl(self.v0 << copy(self.v1)),
                  Rtl((self.v2, self.v3) << isplit(self.v1),
                      self.v0 << iconcat(self.v2, self.v3)))

        WideInt = TypeSet(lanes=(1, 256), ints=(16, 64))
        self.check_yo_check(x, typeset_check(self.v1, WideInt))

    def test_lanes_check(self):
        # type: () -> None
        x = XForm(Rtl(self.v0 << copy(self.v1)),
                  Rtl((self.v2, self.v3) << vsplit(self.v1),
                      self.v0 << vconcat(self.v2, self.v3)))

        WideVec = TypeSet(lanes=(2, 256), ints=(8, 64), floats=(32, 64),
                          bools=(1, 64))
        self.check_yo_check(x, typeset_check(self.v1, WideVec))

    def test_vselect_imm(self):
        # type: () -> None
        ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64))
        r = Rtl(
                self.v0 << iconst(self.imm0),
                self.v1 << icmp(intcc.eq, self.v2, self.v0),
                self.v5 << vselect(self.v1, self.v3, self.v4),
        )
        x = XForm(r, r)
        tv2_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\
            .format(self.v2.get_typevar().name)
        tv3_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\
            .format(self.v3.get_typevar().name)

        self.check_yo_check(
            x, sequence(typeset_check(self.v3, ts),
                        equiv_check(tv2_exp, tv3_exp)))

    def test_reduce_extend(self):
        # type: () -> None
        r = Rtl(
            self.v1 << uextend(self.v0),
            self.v2 << ireduce(self.v1),
            self.v3 << sextend(self.v2),
        )
        x = XForm(r, r)

        tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
        tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
        tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
        tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)

        self.check_yo_check(
            x, sequence(wider_check(tv1_exp, tv0_exp),
                        wider_check(tv1_exp, tv2_exp),
                        wider_check(tv3_exp, tv2_exp)))

    def test_demote_promote(self):
        # type: () -> None
        r = Rtl(
            self.v1 << fpromote(self.v0),
            self.v2 << fdemote(self.v1),
            self.v3 << fpromote(self.v2),
        )
        x = XForm(r, r)

        tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
        tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
        tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
        tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)

        self.check_yo_check(
            x, sequence(wider_check(tv1_exp, tv0_exp),
                        wider_check(tv1_exp, tv2_exp),
                        wider_check(tv3_exp, tv2_exp)))
the primitives.
"""
from __future__ import absolute_import
from cdsl.operands import Operand
from cdsl.typevar import TypeVar
from cdsl.instructions import Instruction, InstructionGroup
from base.types import b1
from base.immediates import imm64
from cdsl.ast import Var
from cdsl.xform import Rtl
from semantics.primitives import bv_from_imm64, bvite
import base.formats  # noqa

GROUP = InstructionGroup("primitive_macros", "Semantic macros instruction set")
AnyBV = TypeVar('AnyBV', bitvecs=True, doc="")
x = Var('x')
y = Var('y')
imm = Var('imm')
a = Var('a')

#
# Bool-to-bv1
#
BV1 = TypeVar("BV1", bitvecs=(1, 1), doc="")
bv1_op = Operand('bv1_op', BV1, doc="")
cond_op = Operand("cond", b1, doc="")
bool2bv = Instruction('bool2bv',
                      r"""Convert a b1 value to a 1-bit BV""",
                      ins=cond_op,
                      outs=bv1_op)
예제 #7
0
# TODO: Add sufficient XForm syntax that we don't need to hand-code these.
expand.custom_legalize(insts.trapz, 'expand_cond_trap')
expand.custom_legalize(insts.trapnz, 'expand_cond_trap')
expand.custom_legalize(insts.br_table, 'expand_br_table')
expand.custom_legalize(insts.select, 'expand_select')

# Custom expansions for floating point constants.
# These expansions require bit-casting or creating constant pool entries.
expand.custom_legalize(insts.f32const, 'expand_fconst')
expand.custom_legalize(insts.f64const, 'expand_fconst')

# Custom expansions for stack memory accesses.
expand.custom_legalize(insts.stack_load, 'expand_stack_load')
expand.custom_legalize(insts.stack_store, 'expand_stack_store')

x = Var('x')
y = Var('y')
a = Var('a')
a1 = Var('a1')
a2 = Var('a2')
b = Var('b')
b1 = Var('b1')
b2 = Var('b2')
b_in = Var('b_in')
b_int = Var('b_int')
c = Var('c')
c1 = Var('c1')
c2 = Var('c2')
c_in = Var('c_in')
c_int = Var('c_int')
d = Var('d')
from base.immediates import imm64, intcc, floatcc
from base import legalize as shared
from base import instructions as insts
from . import instructions as x86
from .defs import ISA

x86_expand = XFormGroup('x86_expand',
                        """
        Legalize instructions by expansion.

        Use x86-specific instructions if needed.
        """,
                        isa=ISA,
                        chain=shared.expand_flags)

a = Var('a')
dead = Var('dead')
x = Var('x')
xhi = Var('xhi')
y = Var('y')
a1 = Var('a1')
a2 = Var('a2')

#
# Division and remainder.
#
# The srem expansion requires custom code because srem INT_MIN, -1 is not
# allowed to trap. The other ops need to check avoid_div_traps.
x86_expand.custom_legalize(insts.sdiv, 'expand_sdivrem')
x86_expand.custom_legalize(insts.srem, 'expand_sdivrem')
x86_expand.custom_legalize(insts.udiv, 'expand_udivrem')
예제 #9
0
 def setUp(self):
     # type: () -> None
     self.v0 = Var("v0")
     self.v1 = Var("v1")
     self.v2 = Var("v2")
     self.v3 = Var("v3")
     self.v4 = Var("v4")
     self.v5 = Var("v5")
     self.v6 = Var("v6")
     self.v7 = Var("v7")
     self.v8 = Var("v8")
     self.v9 = Var("v9")
     self.imm0 = Var("imm0")
     self.IxN_nonscalar = TypeVar("IxN_nonscalar",
                                  "",
                                  ints=True,
                                  scalars=False,
                                  simd=True)
     self.TxN = TypeVar("TxN",
                        "",
                        ints=True,
                        bools=True,
                        floats=True,
                        scalars=False,
                        simd=True)
     self.b1 = TypeVar.singleton(b1)
예제 #10
0
class TestRuntimeChecks(TestCase):
    def setUp(self):
        # type: () -> None
        self.v0 = Var("v0")
        self.v1 = Var("v1")
        self.v2 = Var("v2")
        self.v3 = Var("v3")
        self.v4 = Var("v4")
        self.v5 = Var("v5")
        self.v6 = Var("v6")
        self.v7 = Var("v7")
        self.v8 = Var("v8")
        self.v9 = Var("v9")
        self.imm0 = Var("imm0")
        self.IxN_nonscalar = TypeVar("IxN_nonscalar",
                                     "",
                                     ints=True,
                                     scalars=False,
                                     simd=True)
        self.TxN = TypeVar("TxN",
                           "",
                           ints=True,
                           bools=True,
                           floats=True,
                           scalars=False,
                           simd=True)
        self.b1 = TypeVar.singleton(b1)

    def check_yo_check(self, xform, expected_f):
        # type: (XForm, CheckProducer) -> None
        fmt = Formatter()
        type_sets = UniqueTable()
        for check in get_runtime_typechecks(xform):
            emit_runtime_typecheck(check, fmt, type_sets)

        # Remove comments
        got = "".join([l for l in fmt.lines if not l.strip().startswith("//")])
        expected = expected_f(type_sets)
        self.assertEqual(got, expected)

    def test_width_check(self):
        # type: () -> None
        x = XForm(
            Rtl(self.v0 << copy(self.v1)),
            Rtl((self.v2, self.v3) << isplit(self.v1),
                self.v0 << iconcat(self.v2, self.v3)))

        WideInt = TypeSet(lanes=(1, 256), ints=(16, 64))
        self.check_yo_check(x, typeset_check(self.v1, WideInt))

    def test_lanes_check(self):
        # type: () -> None
        x = XForm(
            Rtl(self.v0 << copy(self.v1)),
            Rtl((self.v2, self.v3) << vsplit(self.v1),
                self.v0 << vconcat(self.v2, self.v3)))

        WideVec = TypeSet(lanes=(2, 256),
                          ints=(8, 64),
                          floats=(32, 64),
                          bools=(1, 64))
        self.check_yo_check(x, typeset_check(self.v1, WideVec))

    def test_vselect_imm(self):
        # type: () -> None
        ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64))
        r = Rtl(
            self.v0 << iconst(self.imm0),
            self.v1 << icmp(intcc.eq, self.v2, self.v0),
            self.v5 << vselect(self.v1, self.v3, self.v4),
        )
        x = XForm(r, r)
        tv2_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
            .format(self.v2.get_typevar().name)
        tv3_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
            .format(self.v3.get_typevar().name)

        self.check_yo_check(
            x,
            sequence(typeset_check(self.v3, ts), equiv_check(tv2_exp,
                                                             tv3_exp)))

    def test_reduce_extend(self):
        # type: () -> None
        r = Rtl(
            self.v1 << uextend(self.v0),
            self.v2 << ireduce(self.v1),
            self.v3 << sextend(self.v2),
        )
        x = XForm(r, r)

        tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
        tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
        tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
        tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)

        self.check_yo_check(
            x,
            sequence(wider_check(tv1_exp, tv0_exp),
                     wider_check(tv1_exp, tv2_exp),
                     wider_check(tv3_exp, tv2_exp)))

    def test_demote_promote(self):
        # type: () -> None
        r = Rtl(
            self.v1 << fpromote(self.v0),
            self.v2 << fdemote(self.v1),
            self.v3 << fpromote(self.v2),
        )
        x = XForm(r, r)

        tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
        tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
        tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
        tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)

        self.check_yo_check(
            x,
            sequence(wider_check(tv1_exp, tv0_exp),
                     wider_check(tv1_exp, tv2_exp),
                     wider_check(tv3_exp, tv2_exp)))
예제 #11
0
def equivalent(r1, r2, inp_m, out_m):
    # type: (Rtl, Rtl, VarAtomMap, VarAtomMap) -> List[ExprRef]
    """
    Given:
        - concrete source Rtl r1
        - concrete dest Rtl r2
        - VarAtomMap inp_m mapping r1's non-bitvector inputs to r2
        - VarAtomMap out_m mapping r1's non-bitvector outputs to r2

    Build a query checking whether r1 and r2 are semantically equivalent.
    If the returned query is unsatisfiable, then r1 and r2 are equivalent.
    Otherwise, the satisfying example for the query gives us values
    for which the two Rtls disagree.
    """
    # Sanity - inp_m is a bijection from the set of inputs of r1 to the set of
    # inputs of r2
    assert set(r1.free_vars()) == set(inp_m.keys())
    assert set(r2.free_vars()) == set(inp_m.values())

    # Note that the same rule is not expected to hold for out_m due to
    # temporaries/intermediates. out_m specified which values are enough for
    # equivalence.

    # Rename the vars in r1 and r2 with unique suffixes to avoid conflicts
    src_m = {v: Var(v.name + ".a", v.get_typevar())
             for v in r1.vars()}  # type: VarAtomMap # noqa
    dst_m = {v: Var(v.name + ".b", v.get_typevar())
             for v in r2.vars()}  # type: VarAtomMap # noqa
    r1 = r1.copy(src_m)
    r2 = r2.copy(dst_m)

    def _translate(m, k_m, v_m):
        # type: (VarAtomMap, VarAtomMap, VarAtomMap) -> VarAtomMap
        """Obtain a new map from m, by mapping m's keys with k_m and m's values
        with v_m"""
        res = {}  # type: VarAtomMap
        for (k, v) in m1.items():
            new_k = k_m[k]
            new_v = v_m[v]
            assert isinstance(new_k, Var)
            res[new_k] = new_v

        return res

    # Convert inp_m, out_m in terms of variables with the .a/.b suffixes
    inp_m = _translate(inp_m, src_m, dst_m)
    out_m = _translate(out_m, src_m, dst_m)

    # Encode r1 and r2 as SMT queries
    (q1, m1) = to_smt(r1)
    (q2, m2) = to_smt(r2)

    # Build an expression for the equality of real Cranelift inputs of
    # r1 and r2
    args_eq_exp = []  # type: List[ExprRef]

    for (v1, v2) in inp_m.items():
        assert isinstance(v2, Var)
        args_eq_exp.append(mk_eq(m1[v1], m2[v2]))

    # Build an expression for the equality of real Cranelift outputs of
    # r1 and r2
    results_eq_exp = []  # type: List[ExprRef]
    for (v1, v2) in out_m.items():
        assert isinstance(v2, Var)
        results_eq_exp.append(mk_eq(m1[v1], m2[v2]))

    # Put the whole query toghether
    return q1 + q2 + args_eq_exp + [Not(And(*results_eq_exp))]
예제 #12
0
# TODO: Add sufficient XForm syntax that we don't need to hand-code these.
expand.custom_legalize(insts.trapz, 'expand_cond_trap')
expand.custom_legalize(insts.trapnz, 'expand_cond_trap')
expand.custom_legalize(insts.br_table, 'expand_br_table')
expand.custom_legalize(insts.select, 'expand_select')

# Custom expansions for floating point constants.
# These expansions require bit-casting or creating constant pool entries.
expand.custom_legalize(insts.f32const, 'expand_fconst')
expand.custom_legalize(insts.f64const, 'expand_fconst')

# Custom expansions for stack memory accesses.
expand.custom_legalize(insts.stack_load, 'expand_stack_load')
expand.custom_legalize(insts.stack_store, 'expand_stack_store')

x = Var('x')
y = Var('y')
z = Var('z')
a = Var('a')
a1 = Var('a1')
a2 = Var('a2')
a3 = Var('a3')
a4 = Var('a4')
b = Var('b')
b1 = Var('b1')
b2 = Var('b2')
b3 = Var('b3')
b4 = Var('b4')
b_in = Var('b_in')
b_int = Var('b_int')
c = Var('c')
예제 #13
0
 def test_elaborate_iadd_cout_simple(self):
     # type: () -> None
     x = Var('x')
     y = Var('y')
     a = Var('a')
     c_out = Var('c_out')
     bvc_out = Var('bvc_out')
     bc_out = Var('bc_out')
     bvx = Var('bvx')
     bvy = Var('bvy')
     bva = Var('bva')
     bvone = Var('bvone')
     bvzero = Var('bvzero')
     r = Rtl(
             (a, c_out) << iadd_cout.i32(x, y),
     )
     r.cleanup_concrete_rtl()
     sem = elaborate(r)
     exp = Rtl(
         bvx << prim_to_bv.i32(x),
         bvy << prim_to_bv.i32(y),
         bva << bvadd.bv32(bvx, bvy),
         bc_out << bvult.bv32(bva, bvx),
         bvone << bv_from_imm64(imm64(1)),
         bvzero << bv_from_imm64(imm64(0)),
         bvc_out << bvite(bc_out, bvone, bvzero),
         a << prim_from_bv.i32(bva),
         c_out << prim_from_bv.b1(bvc_out)
     )
     exp.cleanup_concrete_rtl()
     assert concrete_rtls_eq(sem, exp)
예제 #14
0
    def test_elaborate_iadd_elaborate_2(self):
        # type: () -> None
        i8.by(4)  # Make sure i32x2 exists.
        r = Rtl(
                self.v0 << iadd.i8x4(self.v1, self.v2),
        )
        r.cleanup_concrete_rtl()

        sem = elaborate(r)
        x = Var('x')
        y = Var('y')
        a = Var('a')
        bvx_1 = Var('bvx_1')
        bvx_2 = Var('bvx_2')
        bvx_5 = Var('bvx_5')
        bvx_10 = Var('bvx_10')
        bvx_15 = Var('bvx_15')

        bvlo_1 = Var('bvlo_1')
        bvlo_2 = Var('bvlo_2')
        bvlo_6 = Var('bvlo_6')
        bvlo_7 = Var('bvlo_7')
        bvlo_11 = Var('bvlo_11')
        bvlo_12 = Var('bvlo_12')

        bvhi_1 = Var('bvhi_1')
        bvhi_2 = Var('bvhi_2')
        bvhi_6 = Var('bvhi_6')
        bvhi_7 = Var('bvhi_7')
        bvhi_11 = Var('bvhi_11')
        bvhi_12 = Var('bvhi_12')

        bva_8 = Var('bva_8')
        bva_9 = Var('bva_9')
        bva_13 = Var('bva_13')
        bva_14 = Var('bva_14')

        exp = Rtl(
            bvx_1 << prim_to_bv.i8x4(x),
            (bvlo_1, bvhi_1) << bvsplit.bv32(bvx_1),
            bvx_2 << prim_to_bv.i8x4(y),
            (bvlo_2, bvhi_2) << bvsplit.bv32(bvx_2),
            (bvlo_6, bvhi_6) << bvsplit.bv16(bvlo_1),
            (bvlo_7, bvhi_7) << bvsplit.bv16(bvlo_2),
            bva_8 << bvadd.bv8(bvlo_6, bvlo_7),
            bva_9 << bvadd.bv8(bvhi_6, bvhi_7),
            bvx_10 << bvconcat.bv8(bva_8, bva_9),
            (bvlo_11, bvhi_11) << bvsplit.bv16(bvhi_1),
            (bvlo_12, bvhi_12) << bvsplit.bv16(bvhi_2),
            bva_13 << bvadd.bv8(bvlo_11, bvlo_12),
            bva_14 << bvadd.bv8(bvhi_11, bvhi_12),
            bvx_15 << bvconcat.bv8(bva_13, bva_14),
            bvx_5 << bvconcat.bv16(bvx_10, bvx_15),
            a << prim_from_bv.i8x4(bvx_5)
        )
        exp.cleanup_concrete_rtl()
        assert concrete_rtls_eq(sem, exp)
예제 #15
0
    def test_elaborate_iadd_elaborate_1(self):
        # type: () -> None
        i32.by(2)  # Make sure i32x2 exists.
        r = Rtl(
                self.v0 << iadd.i32x2(self.v1, self.v2),
        )
        r.cleanup_concrete_rtl()
        sem = elaborate(r)
        x = Var('x')
        y = Var('y')
        a = Var('a')
        bvx_1 = Var('bvx_1')
        bvx_2 = Var('bvx_2')
        bvx_5 = Var('bvx_5')
        bvlo_1 = Var('bvlo_1')
        bvlo_2 = Var('bvlo_2')
        bvhi_1 = Var('bvhi_1')
        bvhi_2 = Var('bvhi_2')

        bva_3 = Var('bva_3')
        bva_4 = Var('bva_4')

        exp = Rtl(
            bvx_1 << prim_to_bv.i32x2(x),
            (bvlo_1, bvhi_1) << bvsplit.bv64(bvx_1),
            bvx_2 << prim_to_bv.i32x2(y),
            (bvlo_2, bvhi_2) << bvsplit.bv64(bvx_2),
            bva_3 << bvadd.bv32(bvlo_1, bvlo_2),
            bva_4 << bvadd.bv32(bvhi_1, bvhi_2),
            bvx_5 << bvconcat.bv32(bva_3, bva_4),
            a << prim_from_bv.i32x2(bvx_5)
        )
        exp.cleanup_concrete_rtl()

        assert concrete_rtls_eq(sem, exp)