From 40ad1eee8241b2b716f74c6d6d5f2d05ae0ad2b8 Mon Sep 17 00:00:00 2001 From: AryanBhirud Date: Fri, 20 Mar 2026 18:55:23 +0530 Subject: [PATCH 1/2] Feat(#250): Implement bitwise operators (&,|,^,~,<<,>>) --- src/irx/builders/llvmliteir.py | 40 ++++++++ tests/test_bitwise_op.py | 182 +++++++++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 tests/test_bitwise_op.py diff --git a/src/irx/builders/llvmliteir.py b/src/irx/builders/llvmliteir.py index 80b3c3a2..9cae2e0c 100644 --- a/src/irx/builders/llvmliteir.py +++ b/src/irx/builders/llvmliteir.py @@ -825,6 +825,25 @@ def visit(self, node: astx.UnaryOp) -> None: self.result_stack.append(result) return + elif node.op_code == "~": + self.visit(node.operand) + operand_val = safe_pop(self.result_stack) + # Bitwise NOT: xor with all bits set (-1) + result = self._llvm.ir_builder.xor( + operand_val, ir.Constant(operand_val.type, -1), "bitnottmp" + ) + if isinstance(node.operand, astx.Identifier): + if node.operand.name in self.const_vars: + raise Exception( + f"Cannot mutate '{node.operand.name}':" + "declared as constant" + ) + addr = self.named_values.get(node.operand.name) + if addr: + self._llvm.ir_builder.store(result, addr) + self.result_stack.append(result) + return + raise Exception(f"Unary operator {node.op_code} not implemented yet.") @dispatch # type: ignore[no-redef] @@ -1033,6 +1052,27 @@ def visit(self, node: astx.BinaryOp) -> None: result = self._llvm.ir_builder.or_(llvm_lhs, llvm_rhs, "ortmp") self.result_stack.append(result) return + + if node.op_code == "&": + result = self._llvm.ir_builder.and_(llvm_lhs, llvm_rhs, "bitandtmp") + self.result_stack.append(result) + return + elif node.op_code == "|": + result = self._llvm.ir_builder.or_(llvm_lhs, llvm_rhs, "bitortmp") + self.result_stack.append(result) + return + elif node.op_code == "^": + result = self._llvm.ir_builder.xor(llvm_lhs, llvm_rhs, "bitxortmp") + self.result_stack.append(result) + return + elif node.op_code == "<<": + result = self._llvm.ir_builder.shl(llvm_lhs, llvm_rhs, "shltmp") + self.result_stack.append(result) + return + elif node.op_code == ">>": + result = self._llvm.ir_builder.ashr(llvm_lhs, llvm_rhs, "shrtmp") + self.result_stack.append(result) + return if node.op_code == "+": # note: it should be according the datatype, diff --git a/tests/test_bitwise_op.py b/tests/test_bitwise_op.py new file mode 100644 index 00000000..f1e2a7c8 --- /dev/null +++ b/tests/test_bitwise_op.py @@ -0,0 +1,182 @@ +""" +title: Tests for Bitwise AND (&) operator. +""" + +import astx +import pytest + +from irx.builders.base import Builder +from irx.builders.llvmliteir import LLVMLiteIR +from irx.system import PrintExpr +from .conftest import check_result + +@pytest.mark.parametrize( + "int_type, literal_type", + [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), + ], +) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_bitwise_and(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + """ + title: Test bitwise AND (&) operation on integer literals. + parameters: + builder_class: + type: type[Builder] + int_type: + type: type + literal_type: + type: type + """ + builder = builder_class() + module = builder.module() + + expr = astx.BinaryOp("&", literal_type(6), literal_type(3)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + + check_result("build", builder, module, expected_output="2") + + +@pytest.mark.parametrize("int_type, literal_type", [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), +]) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_bitwise_or(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + builder = builder_class() + module = builder.module() + expr = astx.BinaryOp("|", literal_type(6), literal_type(3)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + check_result("build", builder, module, expected_output="7") + + +@pytest.mark.parametrize("int_type, literal_type", [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), +]) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_bitwise_xor(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + builder = builder_class() + module = builder.module() + expr = astx.BinaryOp("^", literal_type(6), literal_type(3)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + check_result("build", builder, module, expected_output="5") + + +@pytest.mark.parametrize("int_type, literal_type", [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), +]) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_left_shift(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + builder = builder_class() + module = builder.module() + expr = astx.BinaryOp("<<", literal_type(3), literal_type(2)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + check_result("build", builder, module, expected_output="12") + + +@pytest.mark.parametrize("int_type, literal_type", [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), +]) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_right_shift(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + builder = builder_class() + module = builder.module() + expr = astx.BinaryOp(">>", literal_type(12), literal_type(2)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + check_result("build", builder, module, expected_output="3") + + +@pytest.mark.parametrize("int_type, literal_type", [ + (astx.Int32, astx.LiteralInt32), + (astx.Int16, astx.LiteralInt16), + (astx.Int8, astx.LiteralInt8), + (astx.Int64, astx.LiteralInt64), +]) +@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) +def test_bitwise_not(builder_class: type[Builder], int_type: type, literal_type: type) -> None: + builder = builder_class() + module = builder.module() + expr = astx.UnaryOp("~", literal_type(15)) + decl = astx.VariableDeclaration( + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + ) + main_proto = astx.FunctionPrototype( + name="main", args=astx.Arguments(), return_type=int_type() + ) + main_block = astx.Block() + main_block.append(decl) + main_block.append(PrintExpr(astx.Identifier("result"))) + main_block.append(astx.FunctionReturn(literal_type(0))) + main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) + module.block.append(main_fn) + check_result("build", builder, module, expected_output=str(~15)) From a93500817c5b3e7833ab64d26251e88a09f6527c Mon Sep 17 00:00:00 2001 From: AryanBhirud Date: Sat, 21 Mar 2026 02:50:38 +0530 Subject: [PATCH 2/2] Parameterize operators for test_bitwise_op.py --- tests/test_bitwise_op.py | 189 ++++++++++++--------------------------- 1 file changed, 56 insertions(+), 133 deletions(-) diff --git a/tests/test_bitwise_op.py b/tests/test_bitwise_op.py index f1e2a7c8..39920d57 100644 --- a/tests/test_bitwise_op.py +++ b/tests/test_bitwise_op.py @@ -11,7 +11,17 @@ from .conftest import check_result @pytest.mark.parametrize( - "int_type, literal_type", + "op,lhs,rhs,expected", + [ + ("&", 6, 3, lambda a, b: str(a & b)), + ("|", 6, 3, lambda a, b: str(a | b)), + ("^", 6, 3, lambda a, b: str(a ^ b)), + ("<<", 3, 2, lambda a, b: str(a << b)), + (">>", 12, 2, lambda a, b: str(a >> b)), + ], +) +@pytest.mark.parametrize( + "int_type,literal_type", [ (astx.Int32, astx.LiteralInt32), (astx.Int16, astx.LiteralInt16), @@ -20,25 +30,23 @@ ], ) @pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_bitwise_and(builder_class: type[Builder], int_type: type, literal_type: type) -> None: - """ - title: Test bitwise AND (&) operation on integer literals. - parameters: - builder_class: - type: type[Builder] - int_type: - type: type - literal_type: - type: type - """ +def test_bitwise_binary_ops( + builder_class: type[Builder], + int_type: type, + literal_type: type, + op: str, + lhs: int, + rhs: int, + expected, +) -> None: builder = builder_class() module = builder.module() - - expr = astx.BinaryOp("&", literal_type(6), literal_type(3)) + left = literal_type(lhs) + right = literal_type(rhs) + expr = astx.BinaryOp(op, left, right) decl = astx.VariableDeclaration( name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable ) - main_proto = astx.FunctionPrototype( name="main", args=astx.Arguments(), return_type=int_type() ) @@ -48,130 +56,37 @@ def test_bitwise_and(builder_class: type[Builder], int_type: type, literal_type: main_block.append(astx.FunctionReturn(literal_type(0))) main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) module.block.append(main_fn) + check_result("build", builder, module, expected_output=expected(lhs, rhs)) - check_result("build", builder, module, expected_output="2") - - -@pytest.mark.parametrize("int_type, literal_type", [ - (astx.Int32, astx.LiteralInt32), - (astx.Int16, astx.LiteralInt16), - (astx.Int8, astx.LiteralInt8), - (astx.Int64, astx.LiteralInt64), -]) -@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_bitwise_or(builder_class: type[Builder], int_type: type, literal_type: type) -> None: - builder = builder_class() - module = builder.module() - expr = astx.BinaryOp("|", literal_type(6), literal_type(3)) - decl = astx.VariableDeclaration( - name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable - ) - main_proto = astx.FunctionPrototype( - name="main", args=astx.Arguments(), return_type=int_type() - ) - main_block = astx.Block() - main_block.append(decl) - main_block.append(PrintExpr(astx.Identifier("result"))) - main_block.append(astx.FunctionReturn(literal_type(0))) - main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) - module.block.append(main_fn) - check_result("build", builder, module, expected_output="7") - - -@pytest.mark.parametrize("int_type, literal_type", [ - (astx.Int32, astx.LiteralInt32), - (astx.Int16, astx.LiteralInt16), - (astx.Int8, astx.LiteralInt8), - (astx.Int64, astx.LiteralInt64), -]) -@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_bitwise_xor(builder_class: type[Builder], int_type: type, literal_type: type) -> None: - builder = builder_class() - module = builder.module() - expr = astx.BinaryOp("^", literal_type(6), literal_type(3)) - decl = astx.VariableDeclaration( - name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable - ) - main_proto = astx.FunctionPrototype( - name="main", args=astx.Arguments(), return_type=int_type() - ) - main_block = astx.Block() - main_block.append(decl) - main_block.append(PrintExpr(astx.Identifier("result"))) - main_block.append(astx.FunctionReturn(literal_type(0))) - main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) - module.block.append(main_fn) - check_result("build", builder, module, expected_output="5") - - -@pytest.mark.parametrize("int_type, literal_type", [ - (astx.Int32, astx.LiteralInt32), - (astx.Int16, astx.LiteralInt16), - (astx.Int8, astx.LiteralInt8), - (astx.Int64, astx.LiteralInt64), -]) -@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_left_shift(builder_class: type[Builder], int_type: type, literal_type: type) -> None: - builder = builder_class() - module = builder.module() - expr = astx.BinaryOp("<<", literal_type(3), literal_type(2)) - decl = astx.VariableDeclaration( - name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable - ) - main_proto = astx.FunctionPrototype( - name="main", args=astx.Arguments(), return_type=int_type() - ) - main_block = astx.Block() - main_block.append(decl) - main_block.append(PrintExpr(astx.Identifier("result"))) - main_block.append(astx.FunctionReturn(literal_type(0))) - main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) - module.block.append(main_fn) - check_result("build", builder, module, expected_output="12") - - -@pytest.mark.parametrize("int_type, literal_type", [ - (astx.Int32, astx.LiteralInt32), - (astx.Int16, astx.LiteralInt16), - (astx.Int8, astx.LiteralInt8), - (astx.Int64, astx.LiteralInt64), -]) -@pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_right_shift(builder_class: type[Builder], int_type: type, literal_type: type) -> None: - builder = builder_class() - module = builder.module() - expr = astx.BinaryOp(">>", literal_type(12), literal_type(2)) - decl = astx.VariableDeclaration( - name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable - ) - main_proto = astx.FunctionPrototype( - name="main", args=astx.Arguments(), return_type=int_type() - ) - main_block = astx.Block() - main_block.append(decl) - main_block.append(PrintExpr(astx.Identifier("result"))) - main_block.append(astx.FunctionReturn(literal_type(0))) - main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) - module.block.append(main_fn) - check_result("build", builder, module, expected_output="3") - - -@pytest.mark.parametrize("int_type, literal_type", [ - (astx.Int32, astx.LiteralInt32), - (astx.Int16, astx.LiteralInt16), - (astx.Int8, astx.LiteralInt8), - (astx.Int64, astx.LiteralInt64), -]) +@pytest.mark.parametrize( + "val", + [15, 0, 1, 255], +) +@pytest.mark.parametrize( + "int_type,literal_type,bitwidth", + [ + (astx.Int8, astx.LiteralInt8, 8), + (astx.Int16, astx.LiteralInt16, 16), + (astx.Int32, astx.LiteralInt32, 32), + (astx.Int64, astx.LiteralInt64, 64), + ], +) @pytest.mark.parametrize("builder_class", [LLVMLiteIR]) -def test_bitwise_not(builder_class: type[Builder], int_type: type, literal_type: type) -> None: +def test_bitwise_not( + builder_class: type[Builder], + int_type: type, + literal_type: type, + bitwidth: int, + val: int, +) -> None: builder = builder_class() module = builder.module() - expr = astx.UnaryOp("~", literal_type(15)) + expr = astx.UnaryOp("~", literal_type(val)) decl = astx.VariableDeclaration( - name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable + name="result", type_=int_type(), value=expr, mutability=astx.MutabilityKind.mutable ) main_proto = astx.FunctionPrototype( - name="main", args=astx.Arguments(), return_type=int_type() + name="main", args=astx.Arguments(), return_type=int_type() ) main_block = astx.Block() main_block.append(decl) @@ -179,4 +94,12 @@ def test_bitwise_not(builder_class: type[Builder], int_type: type, literal_type: main_block.append(astx.FunctionReturn(literal_type(0))) main_fn = astx.FunctionDef(prototype=main_proto, body=main_block) module.block.append(main_fn) - check_result("build", builder, module, expected_output=str(~15)) + # Compute the expected result as a signed integer of the correct width + mask = (1 << bitwidth) - 1 + unsigned = (~val) & mask + sign_bit = 1 << (bitwidth - 1) + if unsigned & sign_bit: + expected = str(unsigned - (1 << bitwidth)) + else: + expected = str(unsigned) + check_result("build", builder, module, expected_output=expected)