├── docs ├── source │ ├── ir.png │ ├── llvm2py.ir.enum.rst │ ├── llvm2py.ir.type.rst │ ├── llvm2py.ir.block.rst │ ├── llvm2py.ir.value.rst │ ├── llvm2py.ir.module.rst │ ├── llvm2py.ir.function.rst │ ├── llvm2py.ir.support.rst │ ├── llvm2py.ir.global_object.rst │ ├── llvm2py.ir.global_variable.rst │ ├── index.rst │ ├── llvm2py.ir.rst │ ├── conf.py │ ├── ir_structure.rst │ ├── installation.rst │ ├── about_attributes.rst │ ├── llvm2py.ir.instruction.rst │ └── example.rst ├── Makefile └── make.bat ├── changes.txt ├── llvm2py ├── __init__.py └── ir │ ├── __init__.py │ ├── module.py │ ├── block.py │ ├── global_variable.py │ ├── support.py │ ├── global_object.py │ ├── function.py │ ├── value.py │ ├── enum.py │ └── type.py ├── src ├── bind.cpp ├── parser.cpp └── module.cpp ├── todo.txt ├── include ├── parser.h ├── module.h └── tools.h ├── test_files ├── factorial.c ├── factorial.ll └── test.ll ├── pyproject.toml ├── .github └── workflows │ ├── docs.yml │ └── main.yml ├── examples ├── cfg.py ├── cfg.svg └── interpreter.py ├── CHANGELOG.rst ├── CMakeLists.txt ├── generate_instructions_prototype.py ├── .gitignore ├── README.rst ├── setup.py └── tests └── test_lib.py /docs/source/ir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Papr1ka/llvm2py/HEAD/docs/source/ir.png -------------------------------------------------------------------------------- /changes.txt: -------------------------------------------------------------------------------- 1 | 1. Added support of global variables 2 | 2. Common attributes for global variables and functions moved to new class GlobalObject 3 | 3. Deleted ListMixin and ParentMixin 4 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.enum.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.enum module 2 | ====================== 3 | 4 | .. automodule:: llvm2py.ir.enum 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.type.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.type module 2 | ====================== 3 | 4 | .. automodule:: llvm2py.ir.type 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.block.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.block module 2 | ======================= 3 | 4 | .. automodule:: llvm2py.ir.block 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.value.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.value module 2 | ======================= 3 | 4 | .. automodule:: llvm2py.ir.value 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.module.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.module module 2 | ======================== 3 | 4 | .. automodule:: llvm2py.ir.module 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.function.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.function module 2 | ========================== 3 | 4 | .. automodule:: llvm2py.ir.function 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.support.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.support module 2 | ========================= 3 | 4 | .. automodule:: llvm2py.ir.support 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /llvm2py/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | from . import ir 4 | from ._llvm2py import parse_assembly 5 | 6 | 7 | parse_assembly: Callable[[str], ir.Module] 8 | 9 | __version__ = "0.2.0b" 10 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.global_object.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.global\_object module 2 | ================================ 3 | 4 | .. automodule:: llvm2py.ir.global_object 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.global_variable.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.global\_variable module 2 | ================================== 3 | 4 | .. automodule:: llvm2py.ir.global_variable 5 | :members: 6 | :show-inheritance: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../README.rst 2 | 3 | .. toctree:: 4 | :maxdepth: 2 5 | :caption: Contents: 6 | 7 | installation 8 | ir_structure 9 | about_attributes 10 | example 11 | llvm2py.ir 12 | 13 | 14 | 15 | Indices and tables 16 | ================== 17 | 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /src/bind.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/parser.h" 3 | #include "../include/module.h" 4 | 5 | namespace py = pybind11; 6 | 7 | namespace llvm2py { 8 | 9 | PYBIND11_MODULE(_llvm2py, m) { 10 | m.doc() = "Python & LLVM IR parser, an early beginning..."; 11 | m.def("parse_assembly", &llvm2py::createModule); 12 | } 13 | } -------------------------------------------------------------------------------- /llvm2py/ir/__init__.py: -------------------------------------------------------------------------------- 1 | from .module import Module 2 | from .function import Function 3 | from .block import Block 4 | from .type import * 5 | from .value import Value 6 | from .instruction import * 7 | from .instruction import _create_instruction 8 | from .global_variable import GlobalVariable 9 | from .global_object import GlobalObject, ThreadLocal 10 | from . import support 11 | from . import enum 12 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | [ ] Complete migration to separate classes for unique instructions like "phi", "call", "alloca", "load", "store"... with useful functions (methods or separate) 2 | [ ] Move classes to general repr function from tools 3 | [ ] Add automatic creation of instrinsics python module from tablegen 4 | [ ] Rewrite test_library.py 5 | [ ] Check example on correctness 6 | [ ] Handle global aliases for variables and functions? 7 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | llvm2py.ir.block 11 | llvm2py.ir.enum 12 | llvm2py.ir.function 13 | llvm2py.ir.global_object 14 | llvm2py.ir.global_variable 15 | llvm2py.ir.instruction 16 | llvm2py.ir.module 17 | llvm2py.ir.support 18 | llvm2py.ir.type 19 | llvm2py.ir.value 20 | -------------------------------------------------------------------------------- /include/parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by joe on 24.03.24. 3 | // 4 | 5 | #ifndef LLVM2PY_PARSER_H 6 | #define LLVM2PY_PARSER_H 7 | 8 | #include "llvm/IRReader/IRReader.h" 9 | #include "llvm/Support/MemoryBuffer.h" 10 | #include "llvm/Support/SourceMgr.h" 11 | #include "llvm/IR/Module.h" 12 | 13 | using namespace llvm; 14 | 15 | namespace llvm2py 16 | { 17 | std::unique_ptr parse_module(const std::string& irPresentation); 18 | } 19 | 20 | #endif //LLVM2PY_PARSER_H 21 | -------------------------------------------------------------------------------- /test_files/factorial.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | 4 | int factorial_cycle(int n) 5 | { 6 | int result = 1; 7 | while (n > 0) 8 | { 9 | result *= n; 10 | n -= 1; 11 | } 12 | return result; 13 | } 14 | 15 | int factorial_req(int n) 16 | { 17 | if (n == 1) 18 | { 19 | return 1; 20 | } 21 | return factorial_req(n - 1) * n; 22 | } 23 | 24 | int main() 25 | { 26 | int factorial_cycle_result = factorial_cycle(10); 27 | int factorial_req_result = factorial_req(10); 28 | printf("%d\n", factorial_cycle_result); 29 | printf("%d\n", factorial_req_result); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.cibuildwheel] 6 | skip = ["*-win32", "*-manylinux_i686", "pp*", "*-musllinux_*"] 7 | 8 | test-command = "python {package}/tests/test_lib.py" 9 | 10 | manylinux-x86_64-image = "quay.io/pypa/manylinux_2_28_x86_64:latest" 11 | 12 | [tool.cibuildwheel.linux] 13 | before-all = [ 14 | "dnf upgrade -y", 15 | "dnf install cmake -y", 16 | "dnf install ninja-build -y", 17 | "dnf install clang -y", 18 | "export CC=/usr/bin/clang", 19 | "export CXX=/usr/bin/clang++", 20 | "dnf install llvm -y", 21 | "dnf install llvm-devel -y", 22 | "dnf install libffi-devel -y", 23 | ] -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /src/parser.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/parser.h" 2 | 3 | namespace llvm2py 4 | { 5 | 6 | static LLVMContext ctx; 7 | std::unique_ptr parse_module(const std::string& irPresentation) 8 | { 9 | SMDiagnostic Err; 10 | StringRef moduleIR = irPresentation; 11 | std::unique_ptr moduleIRBuffer = MemoryBuffer::getMemBuffer(moduleIR); 12 | std::unique_ptr M = parseIR(*moduleIRBuffer, Err, ctx); 13 | if (!M) 14 | { 15 | std::string rw = "Error reading IR file\n"; 16 | llvm::raw_string_ostream OS(rw); 17 | Err.print(irPresentation.c_str(), OS); 18 | throw std::invalid_argument(OS.str()); 19 | } 20 | return M; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /llvm2py/ir/module.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .global_variable import GlobalVariable 3 | 4 | from .function import Function 5 | 6 | 7 | @dataclass 8 | class Module: 9 | """ 10 | Module class coresponds to the llvm module class. 11 | """ 12 | 13 | funcs: dict[str, Function] 14 | """ 15 | A dictionary that maps function names to their objects. 16 | """ 17 | 18 | global_vars: dict[str, GlobalVariable] 19 | """ 20 | A dictionary that maps global variable names to their objects. 21 | """ 22 | 23 | def __init__(self, funcs: list[Function], global_vars: tuple[GlobalVariable]): 24 | self.funcs = {func.value.val: func for func in funcs} 25 | self.global_vars = {var.value.val: var for var in global_vars} 26 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: [push, workflow_dispatch] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-python@v5 14 | - name: Install dependencies 15 | run: | 16 | pip install sphinx furo 17 | - name: Sphinx build 18 | run: | 19 | sphinx-build docs/source _build 20 | - name: Deploy to GitHub Pages 21 | uses: peaceiris/actions-gh-pages@v3 22 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 23 | with: 24 | publish_branch: gh-pages 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: _build/ 27 | force_orphan: true 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /include/module.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by joe on 24.03.24. 3 | // 4 | 5 | #ifndef LLVM2PY_MODULE_H 6 | #define LLVM2PY_MODULE_H 7 | 8 | #include 9 | #include 10 | #include "llvm/IR/Module.h" 11 | #include "llvm/IR/Function.h" 12 | #include "llvm/IR/CFG.h" 13 | #include "llvm/IR/Instructions.h" 14 | #include "llvm/IR/InstrTypes.h" 15 | #include "llvm/IR/Constants.h" 16 | #include "llvm/IR/GlobalVariable.h" 17 | #include "llvm/Support/Alignment.h" 18 | #include "llvm/IR/TypedPointerType.h" 19 | #include "llvm/IR/FMF.h" 20 | 21 | namespace py = pybind11; 22 | using namespace llvm; 23 | 24 | namespace llvm2py { 25 | py::object createModule(const std::string& IR); 26 | 27 | struct PythonTypes; 28 | py::object handleInstruction(const Instruction &instruction, const PythonTypes &PT); 29 | 30 | py::object handleAttributeList(AttributeList attributes, const PythonTypes &PT); 31 | } 32 | 33 | #endif //LLVM2PY_MODULE_H 34 | -------------------------------------------------------------------------------- /examples/cfg.py: -------------------------------------------------------------------------------- 1 | from llvm2py import parse_assembly 2 | from llvm2py.ir import * 3 | from graphviz import Digraph 4 | 5 | 6 | with open("../test_files/factorial.ll") as file: 7 | source = file.read() 8 | 9 | mod = parse_assembly(source) 10 | 11 | g = Digraph() 12 | 13 | node_attributes = {"shape": "record", "style": "filled"} 14 | 15 | 16 | def name_label(label): 17 | return label.replace("%", "\\%") 18 | 19 | 20 | def name_block(block): 21 | name = block.value.val 22 | return name_label(name) 23 | 24 | 25 | for block in mod.funcs["factorial_req"].blocks.values(): 26 | color = "#f59c7d70" # Default block color 27 | last_instruction = block.instrs[-1] 28 | 29 | match last_instruction: 30 | case Br(None, Value(label)): 31 | g.edge(name_block(block), name_label(label)) 32 | case Br(_, Value(label_false), Value(label_true)): 33 | g.edge(name_block(block), name_label(label_true), label="True") 34 | g.edge(name_block(block), name_label(label_false), label="False") 35 | 36 | if len(block.pred_blocks) >= 2: 37 | color = "#b70d2870" # merge-type block 38 | 39 | g.node(name_block(block), **node_attributes, color=color) 40 | 41 | g.save("cfg.dot") 42 | -------------------------------------------------------------------------------- /llvm2py/ir/block.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from .instruction import CallBr, Instruction, Call, Invoke 3 | from .value import Value 4 | 5 | 6 | class Block(NamedTuple): 7 | """ 8 | Basic block class. 9 | 10 | :param value: The block as value. 11 | :type value: Value 12 | :param instrs: A list of block instructions. 13 | :type instrs: list[Instruction] 14 | :param pred_blocks: A list of predecessor block names. 15 | :type pred_blocks: list[str] 16 | """ 17 | 18 | value: Value 19 | 20 | instrs: list[Instruction] 21 | 22 | pred_blocks: list[str] 23 | 24 | def has_no_calls(self): 25 | """ 26 | Returns true if the block does not contain calls to other functions. 27 | 28 | Intrinsics are not considered functions. 29 | """ 30 | for instr in self.instrs: 31 | match instr: 32 | case ( 33 | Call(_, Value(str(callee))) 34 | | CallBr(_, Value(str(callee))) 35 | | Invoke(_, Value(str(callee))) 36 | ): 37 | """ 38 | Intrinsic function names must all start with an “llvm.” prefix. 39 | https://llvm.org/docs/LangRef.html#intrinsic-functions 40 | """ 41 | if not callee.startswith("llvm."): 42 | return False 43 | return True 44 | -------------------------------------------------------------------------------- /include/tools.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by joe on 26.03.24. 3 | // 4 | 5 | #ifndef LLVM2PY_TOOLS_H 6 | #define LLVM2PY_TOOLS_H 7 | 8 | #include "llvm/Support/raw_ostream.h" 9 | #include 10 | #include 11 | #include 12 | 13 | namespace llvm2py 14 | { 15 | std::string to_string(llvm::Module* object) 16 | { 17 | std::string buffer; 18 | llvm::raw_string_ostream OS(buffer); 19 | object->print(OS, nullptr); 20 | return buffer; 21 | } 22 | 23 | template 24 | std::string to_string(T* object) 25 | { 26 | std::string buffer; 27 | llvm::raw_string_ostream OS(buffer); 28 | object->print(OS); 29 | return buffer; 30 | } 31 | 32 | std::string getNameOrAsOperand(const llvm::Value* object) 33 | { 34 | if (!object->getName().empty()) 35 | return std::string(object->getName()); 36 | 37 | std::string BBName; 38 | raw_string_ostream OS(BBName); 39 | object->printAsOperand(OS, false); 40 | return OS.str(); 41 | } 42 | 43 | std::string getNameOrAsOperand(const llvm::Value& object) 44 | { 45 | if (!object.getName().empty()) 46 | return std::string(object.getName()); 47 | 48 | std::string BBName; 49 | raw_string_ostream OS(BBName); 50 | object.printAsOperand(OS, false); 51 | return OS.str(); 52 | } 53 | } 54 | 55 | #endif //LLVM2PY_TOOLS_H 56 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | import os 10 | import sys 11 | from collections import _tuplegetter 12 | 13 | sys.path.insert(0, os.path.abspath("../../")) 14 | 15 | project = "llvm2py" 16 | copyright = "2025, Papr1ka" 17 | author = "Papr1ka" 18 | release = "0.2.0" 19 | 20 | # -- General configuration --------------------------------------------------- 21 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 22 | 23 | extensions = [ 24 | "sphinx.ext.autodoc", 25 | ] 26 | 27 | add_module_names = False 28 | 29 | templates_path = ["_templates"] 30 | exclude_patterns = [] 31 | 32 | 33 | # -- Options for HTML output ------------------------------------------------- 34 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 35 | 36 | html_theme = "furo" 37 | html_static_path = ["_static"] 38 | 39 | 40 | def skip_properties(app, what, name, obj, skip, options): 41 | # Skip NamedTuple properties with non-informative __doc__ string 42 | return skip or isinstance(obj, _tuplegetter) 43 | 44 | 45 | def setup(app): 46 | app.connect("autodoc-skip-member", skip_properties) 47 | -------------------------------------------------------------------------------- /llvm2py/ir/global_variable.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .global_object import GlobalObject 4 | from .enum import Attrs 5 | from .support import attrs_to_dict 6 | from .value import Value 7 | 8 | 9 | @dataclass 10 | class GlobalVariable: 11 | """ 12 | `GlobalVariable class `_. 13 | """ 14 | 15 | value: Value 16 | """ 17 | Global variable as value 18 | """ 19 | 20 | initializer: Value 21 | """ 22 | A constant value that the global variable takes on during initialization. 23 | """ 24 | 25 | is_const: bool 26 | """ 27 | If True, varialbe is marked as a constant. 28 | """ 29 | 30 | attrs: Attrs 31 | """ 32 | Global variable attributes. 33 | """ 34 | 35 | global_object: GlobalObject 36 | """ 37 | Global variable as global object. 38 | """ 39 | 40 | is_externally_initialized: bool 41 | """ 42 | If True, variable is marked as ExternallyInitialized. 43 | """ 44 | 45 | def __init__( 46 | self, 47 | value, 48 | initializer, 49 | is_const, 50 | attributes, 51 | global_object, 52 | is_externally_initialized, 53 | ): 54 | self.value = value 55 | self.initializer = initializer 56 | self.is_const = is_const 57 | self.attrs = attrs_to_dict(attributes) 58 | self.global_object = global_object 59 | self.is_externally_initialized = is_externally_initialized 60 | -------------------------------------------------------------------------------- /llvm2py/ir/support.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from .enum import Attrs 4 | 5 | 6 | def function_attributes(attrs: list): 7 | """ 8 | Returns the attributes of the function. 9 | """ 10 | return attrs[0] 11 | 12 | 13 | def ret_attributes(attrs: list): 14 | """ 15 | Returns the attributes of the returned value. 16 | """ 17 | return attrs[1] 18 | 19 | 20 | def arguments_attributes(attrs: list): 21 | """ 22 | Returns the attributes of the arguments. 23 | """ 24 | return attrs[2:] 25 | 26 | 27 | def argument_attributes(attrs: list, arg_no: int): 28 | """ 29 | Returns the argument attributes. 30 | """ 31 | if arg_no + 3 > len(attrs): 32 | return None 33 | else: 34 | return attrs[2 + arg_no] 35 | 36 | 37 | def attrs_to_dict(attrs: list[tuple]) -> Attrs: 38 | """Transforms attributes from tuple form to dict form. 39 | Conversation:: 40 | 41 | [("nsw",), ("fmf", []), ("vscale_range", 1, 2)] 42 | -> 43 | { 44 | "nsw": (), 45 | "fmf": [], 46 | "vscale_range": (1, 2) 47 | } 48 | """ 49 | new_attrs = {} 50 | if not attrs: # empty 51 | return new_attrs 52 | for name, *values in attrs: 53 | if len(values) == 1: 54 | new_attrs[name] = values[0] 55 | else: 56 | new_attrs[name] = tuple(values) 57 | return new_attrs 58 | 59 | 60 | def attrs_list_to_dict(attrs_list: list[list[tuple]]): 61 | return [attrs_to_dict(attrs) for attrs in attrs_list] 62 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | .. _changelog: 2 | 3 | Change Log 4 | ========== 5 | 6 | All notable changes to this project will be documented in this file. 7 | 8 | [0.2.0b] - 2025-03-09 9 | ##################### 10 | 11 | Changes 12 | ------- 13 | 14 | 1. New classes for instruction. 15 | 16 | 2. Instructions now have more useful attributes. 17 | 18 | 3. Deleted x86mmx type. 19 | 20 | 4. Parameter attributes, function attributes, call site attributes, global attributes are now handled and stored in dictionaries. 21 | 22 | 5. All enum-types are placed in the enum.py file. 23 | 24 | 6. Documentation added 25 | 26 | 27 | [0.1.0b] - 2025-02-18 28 | ######################## 29 | 30 | Changes 31 | ------- 32 | 33 | 1. Added support for global variables. 34 | 35 | 2. A different class is created for each type of instruction. 36 | 37 | 3. Added support for attributes. 38 | 39 | 4. Common attributes for global variables and functions have been moved to the new GlobalObject class. 40 | 41 | 5. ListMixin, ParentMixin, CodeMixin removed. 42 | 43 | 6. Classes have been simplified, though still complex. 44 | 45 | 7. Classes with no inheritance and no special initialization method now inherit from NamedTuple, otherwise it is dataclass. 46 | 47 | 8. Instead of inheriting from Value, classes now store it as an attribute. 48 | 49 | 9. The Value class is now used to store constants up to ConstantExpression (val type will be Instruction type). 50 | 51 | 10. Simplified methods are implemented for classes (do not handle attributes). 52 | 53 | 11. The minimum supported version of python is now **3.10**. 54 | 55 | 12. New type hierarchy. 56 | -------------------------------------------------------------------------------- /llvm2py/ir/global_object.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from .enum import LinkageType, VisibilityTypes, UnnamedAddr, ThreadLocal 3 | 4 | 5 | @dataclass 6 | class GlobalObject: 7 | """ 8 | Class, representing GlobalObject and GlobalValue fields. 9 | """ 10 | 11 | addr_space: int 12 | """ 13 | Address space of the object. 14 | """ 15 | 16 | align: int 17 | """ 18 | Alignment of the object. 19 | """ 20 | 21 | linkage: LinkageType 22 | """ 23 | Object linkage type. 24 | """ 25 | 26 | visibility: VisibilityTypes 27 | """ 28 | Object visibility. 29 | """ 30 | 31 | unnamed_addr: UnnamedAddr 32 | """ 33 | Object UnnamedAddr. 34 | If the local_unnamed_addr attribute is given, 35 | the address is known to not be significant within the module. 36 | """ 37 | 38 | thread_local: ThreadLocal 39 | """ 40 | Object ThreadLocal model. 41 | """ 42 | 43 | section: str | None 44 | """ 45 | Object section. 46 | """ 47 | 48 | def __init__( 49 | self, 50 | addr_space: int, 51 | align: int, 52 | linkage: int, 53 | unnamed_addr: int, 54 | visibility: int, 55 | thread_local: int, 56 | section: str, 57 | ): 58 | self.addr_space = addr_space 59 | self.align = align 60 | self.linkage = LinkageType(linkage) 61 | self.unnamed_addr = UnnamedAddr(unnamed_addr) 62 | self.visibility = VisibilityTypes(visibility) 63 | self.thread_local = ThreadLocal(thread_local) 64 | self.section = section if len(section) > 0 else None 65 | -------------------------------------------------------------------------------- /docs/source/ir_structure.rst: -------------------------------------------------------------------------------- 1 | IR Structure 2 | ============ 3 | 4 | To work with this library it is advisable to be familiar with LLVM IR, but if you are not, here is a brief introduction. 5 | 6 | The LLVM implementation contains a huge number of classes, llvm2py does not implement them all, generalizing many things. 7 | 8 | .. image:: ./ir.png 9 | 10 | A module is a translation unit of the input programs and consists of functions and global variables 11 | 12 | At this time, metadata, aliases, ifuncs and comdats are not supported. 13 | 14 | A function definition contains a list of basic blocks, forming the CFG (Control Flow Graph) for the function. 15 | 16 | Basic block contains a list of instructions that are executed linearly, and ends with a terminator instruction. 17 | 18 | The instruction contains a list of operands (not always), which are values. 19 | 20 | In an LLVM implementation, both the instruction, basic blocks and functions are values (inheritance relation). 21 | 22 | This fact is realized through the value attribute in each of the classes described, so that classes have a reference to a value. 23 | 24 | Global variables, as well as functions in the LLVM implementation, inherit from GlobalObject, and the latter from GlobalValue, accumulating many attributes from them. Many such attributes are collected in the GlobalObject class. 25 | 26 | Each value has a type. Composite types are recursive. 27 | 28 | A more detailed description of each class can be found in their documentation. 29 | 30 | It's also worth looking at the handling of attributes :doc:`about_attributes`. 31 | 32 | And you can learn about LLVM IR from here https://llvm.org/docs/LangRef.html. 33 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ############ 3 | 4 | Preferred way 5 | ============= 6 | 7 | .. code-block:: bash 8 | 9 | pip install llvm2py 10 | 11 | Supported versions 12 | 13 | CPython3.10 - CPython3.13 14 | 15 | On Windows 64bit and manylinux x86_64 platforms. 16 | 17 | Manual installation 18 | =================== 19 | 20 | 1. Dependencies 21 | 22 | * For correct build you need **CMake >= 3.27** 23 | 24 | * C and C++ compilers. 25 | 26 | * The system must also have libraries **libffi** и **libtinfo**. 27 | 28 | .. note:: 29 | 30 | If the libraries are not found, you can add them manually, example [CMakeLists.txt](./CMakeLists.txt), lines 12-15. 31 | 32 | * Preferred build system **Ninja** 33 | 34 | 2. Cloning llvm2py 35 | 36 | .. code-block:: bash 37 | 38 | git clone git@github.com:Papr1ka/llvm2py.git 39 | cd llvm2py 40 | 41 | 3. LLVM setup 42 | 43 | You must have the static library **LLVM** version >= 16 installed on the system. 44 | 45 | This can be done via the distribution's package manager. 46 | -------------------------------------------------------- 47 | 48 | Example: 49 | 50 | .. code-block:: bash 51 | 52 | sudo dnf install llvm 53 | sudo dnf install llvm-devel 54 | 55 | Or you can build LLVM manually and specify the path to the cmake folder, line 21 56 | -------------------------------------------------------------------------------- 57 | .. code-block:: bash 58 | 59 | LLVM_DIR = "/usr/lib/llvm-18/cmake" # Path to cmake folder of llvm library 60 | 61 | Or you can download compiled libraries 62 | -------------------------------------- 63 | 64 | * Deb packages https://apt.llvm.org/ 65 | * Windows https://packages.msys2.org/package/mingw-w64-x86_64-llvm 66 | 67 | 4. Run setup.py 68 | 69 | in the directory with setup.py: 70 | 71 | .. code-block:: bash 72 | 73 | python -m pip install . 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | project(llvm2py LANGUAGES C CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17 CACHE STRING "") 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_VERBOSE_MAKEFILE ON) 8 | 9 | list(APPEND CMAKE_PREFIX_PATH "${LLVM_INSTALL_DIR}/lib/cmake/llvm/") 10 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 11 | 12 | if(UNIX) 13 | # Custom path to Terminfo in manylinux28 container 14 | find_library(Terminfo_LIBRARIES libtinfo.so.6 PATHS /usr/lib64/) 15 | endif(UNIX) 16 | 17 | find_package(LLVM REQUIRED CONFIG) 18 | 19 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 20 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 21 | 22 | include_directories(${LLVM_INCLUDE_DIRS}) 23 | link_directories(${LLVM_LIBRARY_DIRS}) 24 | add_definitions(${LLVM_DEFINITIONS}) 25 | 26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall\ 27 | -fdiagnostics-color=always") 28 | 29 | if (WIN32) 30 | message(STATUS "UNIX SYSTEM DETECTED") 31 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static -static-libgcc -static-libstdc++") 32 | endif (WIN32) 33 | 34 | include(GNUInstallDirs) 35 | include(FetchContent) 36 | 37 | FetchContent_Declare( 38 | pybind11 39 | URL https://github.com/pybind/pybind11/archive/refs/tags/v2.10.4.tar.gz 40 | URL_HASH SHA256=832e2f309c57da9c1e6d4542dedd34b24e4192ecb4d62f6f4866a737454c9970 41 | ) 42 | FetchContent_MakeAvailable(pybind11) 43 | 44 | set(python_module_name _llvm2py) 45 | 46 | pybind11_add_module(${python_module_name} 47 | src/module.cpp 48 | src/parser.cpp 49 | src/bind.cpp 50 | ) 51 | 52 | target_include_directories(${python_module_name} PRIVATE include) 53 | 54 | target_link_libraries(${python_module_name} PRIVATE 55 | LLVMCore 56 | LLVMIRReader 57 | LLVMSupport 58 | ) 59 | 60 | install(TARGETS ${python_module_name} DESTINATION .) 61 | -------------------------------------------------------------------------------- /docs/source/about_attributes.rst: -------------------------------------------------------------------------------- 1 | About attributes 2 | ################ 3 | 4 | In LLVM, there are various kinds of attributes. 5 | 6 | All attributes related to instructions are accessible by the corresponding class attributes. 7 | 8 | However, there are attributes such as 9 | `parameter attributes `_, 10 | `function attributes `_, 11 | `call site attributes `_ and 12 | `global attributes `_. 13 | 14 | Attributes can be categorized into groups: 15 | 16 | 1. Attributes without arguments (zeroext, inreg, noalias...). 17 | 18 | 2. Attributes with a number argument (align(n), alignstack(n), ...). 19 | 20 | 3. Attributes with an type argument (byref(ty), preallocated(ty), ...). 21 | 22 | 4. Other attributes. 23 | 24 | There is attributes with two params (vscale_range(min, max)). 25 | 26 | All such attributes are stored in a dictionary, 27 | where the key is the attribute name and the value is the attribute argument tuple. 28 | 29 | .. code-block:: python 30 | 31 | attrs = { 32 | "zeroext": (), 33 | "align": n, 34 | "alignstack": n, 35 | "byref": ty, 36 | "preallocated": ty, 37 | "vscale": (min, max) 38 | } 39 | 40 | You can find these attributes in the classes: 41 | :class:`llvm2py.ir.function.Function`, 42 | :class:`llvm2py.ir.global_variable.GlobalVariable`, 43 | :class:`llvm2py.ir.instruction.Invoke`, 44 | :class:`llvm2py.ir.instruction.CallBr`, 45 | :class:`llvm2py.ir.instruction.Call`. 46 | 47 | `Fast math flags `_ are represented 48 | by a set of strings, where the possible strings are bounded by the set: 49 | {"nnan", "ninf", "nsz", "arcp", "contract", "afn", "reassoc"}. 50 | 51 | One of the classes: :class:`llvm2py.ir.instruction.BinOp`. 52 | -------------------------------------------------------------------------------- /llvm2py/ir/function.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .support import attrs_list_to_dict 4 | 5 | from .global_object import GlobalObject 6 | 7 | from .block import Block 8 | from .value import Value 9 | from .enum import CallingConv, Attrs 10 | 11 | 12 | @dataclass 13 | class Function: 14 | """ 15 | Function class 16 | """ 17 | 18 | value: Value 19 | """ 20 | Function as value. 21 | """ 22 | 23 | args: list[Value] 24 | """ 25 | List of function arguments. 26 | """ 27 | 28 | # function basic blocks 29 | blocks: dict[str, Block] 30 | """ 31 | A dictionary that maps block names to their objects. 32 | """ 33 | 34 | attrs: list[Attrs] 35 | """ 36 | A list of function attributes. 37 | Each element references either the function itself, the return value, or one of the arguments. 38 | 39 | For convenience, you can use functions from the support module to extract attributes. 40 | """ 41 | 42 | calling_convention: CallingConv 43 | """ 44 | Function calling convention. 45 | """ 46 | 47 | is_vararg: bool 48 | """ 49 | If True, the function has a variable number of arguments. 50 | For example - @printf(ptr noundef, ...). 51 | """ 52 | 53 | global_object: GlobalObject 54 | """ 55 | Function as global object. 56 | """ 57 | 58 | def __init__( 59 | self, 60 | value: Value, 61 | args: list[Value], 62 | blocks: list[Block], 63 | attributes: list[list[tuple]], 64 | calling_convention: int, 65 | is_vararg: bool, 66 | global_object: GlobalObject, 67 | ): 68 | self.value = value 69 | self.args = args 70 | self.blocks = {block.value.val: block for block in blocks} 71 | self.attrs = attrs_list_to_dict(attributes) 72 | self.calling_convention = CallingConv(calling_convention) 73 | self.is_vararg = is_vararg 74 | self.global_object = global_object 75 | 76 | def has_no_body(self): 77 | """ 78 | Returns true if the function is only declared but not defined. 79 | """ 80 | return len(self.blocks) == 0 81 | -------------------------------------------------------------------------------- /llvm2py/ir/value.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | from .type import Type 3 | 4 | 5 | class Instruction: ... 6 | 7 | 8 | class Value(NamedTuple): 9 | """ 10 | A base class for many IR entities. 11 | This class can represent a name, a constant, or a constant expression. 12 | Regardless of the meaning, the value is stored in the val field. 13 | 14 | +-------------------------------------------+-----------------+ 15 | | val kind | val type | 16 | +===========================================+=================+ 17 | | Name | str | 18 | +-------------------------------------------+-----------------+ 19 | | poison | str("poison") | 20 | +-------------------------------------------+-----------------+ 21 | | undef | str("undef) | 22 | +-------------------------------------------+-----------------+ 23 | | Integer constant | int | 24 | +-------------------------------------------+-----------------+ 25 | | Float constant | float | 26 | +-------------------------------------------+-----------------+ 27 | | Array | Vector of integer constants | list[int] | 28 | +-------------------------------------------+-----------------+ 29 | | Array | Vector of float constants | list[float] | 30 | +-------------------------------------------+-----------------+ 31 | | Null pointer constant | None | 32 | +-------------------------------------------+-----------------+ 33 | | Array of int8 | bytes | 34 | +-------------------------------------------+-----------------+ 35 | | Block address (Function name, Block name) | tuple[str, str] | 36 | +-------------------------------------------+-----------------+ 37 | | Constant expression | Instruction | 38 | +-------------------------------------------+-----------------+ 39 | 40 | :param val: Value. 41 | :type val: str | int | float | list[int] | list[float] | list["Value"] | None | bytes | tuple[str, str] | Instruction) 42 | :param ty: Value type. 43 | :type ty: Type 44 | """ 45 | 46 | val: ( 47 | str 48 | | int 49 | | float 50 | | list[int] 51 | | list[float] 52 | | list["Value"] 53 | | None 54 | | bytes 55 | | tuple[str, str] 56 | | Instruction 57 | ) 58 | ty: Type 59 | 60 | def __str__(self): 61 | return f"{self.ty} {self.val}" 62 | -------------------------------------------------------------------------------- /docs/source/llvm2py.ir.instruction.rst: -------------------------------------------------------------------------------- 1 | llvm2py.ir.instruction module 2 | ============================= 3 | 4 | Terminator instructions 5 | ----------------------- 6 | 7 | Every basic block in a program ends with a “Terminator” instruction, 8 | which indicates which block should be executed after the current block is finished. 9 | 10 | .. autoclass:: llvm2py.ir.instruction.Ret 11 | .. autoclass:: llvm2py.ir.instruction.Br 12 | .. autoclass:: llvm2py.ir.instruction.Switch 13 | .. autoclass:: llvm2py.ir.instruction.IndirectBr 14 | .. autoclass:: llvm2py.ir.instruction.Invoke 15 | .. autoclass:: llvm2py.ir.instruction.CallBr 16 | .. autoclass:: llvm2py.ir.instruction.Resume 17 | .. autoclass:: llvm2py.ir.instruction.CatchSwitch 18 | .. autoclass:: llvm2py.ir.instruction.CatchRet 19 | .. autoclass:: llvm2py.ir.instruction.CleanupRet 20 | .. autoclass:: llvm2py.ir.instruction.Unreacheble 21 | 22 | Unary operations 23 | ---------------- 24 | 25 | .. autoclass:: llvm2py.ir.instruction.UnaryOp 26 | 27 | Binary operations 28 | ----------------- 29 | 30 | .. autoclass:: llvm2py.ir.instruction.BinOp 31 | 32 | Vector operations 33 | ----------------- 34 | 35 | Instructions representing vector operations in a target-independent manner. 36 | 37 | .. autoclass:: llvm2py.ir.instruction.ExtractElement 38 | .. autoclass:: llvm2py.ir.instruction.InsertElement 39 | .. autoclass:: llvm2py.ir.instruction.ShuffleVector 40 | 41 | Aggregate operations 42 | -------------------- 43 | 44 | Instructions for working with aggregate values. 45 | 46 | .. autoclass:: llvm2py.ir.instruction.ExtractValue 47 | .. autoclass:: llvm2py.ir.instruction.InsertValue 48 | 49 | Memory access and addressing operations 50 | --------------------------------------- 51 | 52 | A key design point of an SSA-based representation is how it represents memory. 53 | In LLVM IR, no memory locations are in SSA form, which makes things very simple. 54 | This section describes how to read, write, and allocate memory in LLVM IR. 55 | 56 | .. autoclass:: llvm2py.ir.instruction.Alloca 57 | .. autoclass:: llvm2py.ir.instruction.Load 58 | .. autoclass:: llvm2py.ir.instruction.Store 59 | .. autoclass:: llvm2py.ir.instruction.CmpXchg 60 | .. autoclass:: llvm2py.ir.instruction.AtomicRmw 61 | .. autoclass:: llvm2py.ir.instruction.GetElementPtr 62 | 63 | Conversion operations 64 | --------------------- 65 | 66 | .. autoclass:: llvm2py.ir.instruction.Conversion 67 | 68 | Other operations 69 | ---------------- 70 | 71 | .. autoclass:: llvm2py.ir.instruction.ICmp 72 | .. autoclass:: llvm2py.ir.instruction.FCmp 73 | .. autoclass:: llvm2py.ir.instruction.Phi 74 | .. autoclass:: llvm2py.ir.instruction.Select 75 | .. autoclass:: llvm2py.ir.instruction.Freeze 76 | .. autoclass:: llvm2py.ir.instruction.Call 77 | .. autoclass:: llvm2py.ir.instruction.VaArg 78 | .. autoclass:: llvm2py.ir.instruction.LandingPad 79 | .. autoclass:: llvm2py.ir.instruction.CatchPad 80 | .. autoclass:: llvm2py.ir.instruction.CleanupPad 81 | -------------------------------------------------------------------------------- /generate_instructions_prototype.py: -------------------------------------------------------------------------------- 1 | from ast import * 2 | import graphlib 3 | 4 | 5 | src = """ 6 | from typing import NamedTuple 7 | 8 | 9 | class Instruction(NamedTuple): 10 | # instruction attributes 11 | attrs: Attrs 12 | 13 | # instruction as value 14 | result: Value 15 | 16 | # instruction opcode 17 | opcode: str 18 | 19 | 20 | class CallInstr(Instruction): 21 | call_attributes: list[Attrs] 22 | 23 | 24 | class Invoke(CallInstr): 25 | func: Value 26 | args: list[Value] 27 | label_ok: Value 28 | label_err: Value 29 | 30 | def __str__(self): 31 | return ( 32 | f"{self.result} = invoke {self.func}({', '.join(map(str, self.args))}) " 33 | + (f"to {self.label_ok} unwind {self.label_err}") 34 | ) 35 | 36 | """ 37 | 38 | tree = parse(src) 39 | print(dump(tree, indent=4)) 40 | 41 | 42 | def construct_final(class_def: ClassDef, names: dict[str, ClassDef]): 43 | match class_def: 44 | case ClassDef( 45 | name, [Name("NamedTuple")], keywords, body, decorator_list, type_params 46 | ): 47 | return class_def 48 | case ClassDef(name, bases, keywords, body, decorator_list, type_params): 49 | for base in bases: 50 | base_class = construct_final(names[base.id], names) 51 | match base_class: 52 | case ClassDef( 53 | _, 54 | _, 55 | base_keywords, 56 | base_body, 57 | base_decorator_list, 58 | base_type_params, 59 | ): 60 | body = base_body + body 61 | decorator_list = base_decorator_list + decorator_list 62 | type_params = base_type_params + type_params 63 | keywords = base_keywords + keywords 64 | return ClassDef( 65 | name, [Name("NamedTuple")], keywords, body, decorator_list, type_params 66 | ) 67 | 68 | 69 | # def build_graph(mod: Module): 70 | # match mod: 71 | # case Module(body): 72 | # for s in body: 73 | # match s: 74 | # case ClassDef(name): 75 | # names[name] = s 76 | # new_class = construct_final(s, names) 77 | # new_body.append(new_class) 78 | 79 | 80 | match tree: 81 | case Module(body): 82 | new_body = [] 83 | names = {} 84 | for s in body: 85 | match s: 86 | case ClassDef(name): 87 | names[name] = s 88 | new_class = construct_final(s, names) 89 | new_body.append(new_class) 90 | case _: 91 | new_body.append(s) 92 | new_mod = Module(new_body, []) 93 | print(dump(new_mod, indent=4)) 94 | print(unparse(new_mod)) 95 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build_wheels 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+.* 6 | workflow_dispatch: 7 | 8 | 9 | jobs: 10 | build_wheels_windows: 11 | runs-on: windows-latest 12 | strategy: 13 | fail-fast: false 14 | defaults: 15 | run: 16 | shell: msys2 {0} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: msys2/setup-msys2@v2 20 | with: 21 | msystem: MINGW64 22 | platform-check-severity: warn 23 | update: true 24 | install: >- 25 | mingw-w64-x86_64-gcc 26 | mingw-w64-x86_64-llvm 27 | mingw-w64-x86_64-cmake 28 | - name: try to update PATH 29 | run: | 30 | echo "$(cygpath -w /mingw64/bin)" 31 | echo "$(cygpath -w /mingw64/bin)" >> $GITHUB_PATH 32 | echo "$GITHUB_WORKSPACE/msys64/mingw64/bin" >> $GITHUB_PATH 33 | echo "$HOME/msys64/mingw64/bin" >> $GITHUB_PATH 34 | echo "{path}" >> $GITHUB_PATH 35 | cd "$(cygpath -w /mingw64/bin)" 36 | - name: Build wheels 37 | uses: pypa/cibuildwheel@v2.22.0 38 | - uses: actions/upload-artifact@v4 39 | with: 40 | name: cibw-wheels-windows-latest-${{ strategy.job-index }} 41 | path: ./wheelhouse/*.whl 42 | 43 | build_sdist: 44 | name: Build source distributions 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - name: Build sdist 50 | run: pipx run build --sdist 51 | 52 | - uses: actions/upload-artifact@v4 53 | with: 54 | name: cibw-sdist 55 | path: dist/*.tar.gz 56 | 57 | build_wheels_linux: 58 | name: Build wheels on linux 59 | strategy: 60 | fail-fast: false 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v4 64 | - name: Build wheels 65 | uses: pypa/cibuildwheel@v2.22.0 66 | - uses: actions/upload-artifact@v4 67 | with: 68 | name: cibw-wheels-linux-latest-${{ strategy.job-index }} 69 | path: ./wheelhouse/*.whl 70 | 71 | upload_pypi: 72 | needs: [build_wheels_windows, build_wheels_linux, build_sdist] 73 | runs-on: ubuntu-latest 74 | environment: pypi 75 | permissions: 76 | id-token: write 77 | steps: 78 | - uses: actions/download-artifact@v4 79 | with: 80 | pattern: cibw-* 81 | path: dist 82 | merge-multiple: true 83 | 84 | - uses: pypa/gh-action-pypi-publish@release/v1 85 | with: 86 | skip-existing: true 87 | release: 88 | needs: [build_wheels_windows, build_wheels_linux, build_sdist] 89 | name: Create Release 90 | runs-on: ubuntu-latest 91 | permissions: 92 | contents: write 93 | steps: 94 | - name: Download artifact 95 | uses: actions/download-artifact@v4 96 | with: 97 | pattern: cibw-* 98 | path: dist 99 | merge-multiple: true 100 | 101 | - name: Create release 102 | uses: softprops/action-gh-release@v2 103 | if: startsWith(github.ref, 'refs/tags/') 104 | with: 105 | files: dist/* 106 | -------------------------------------------------------------------------------- /examples/cfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %1 10 | 11 | 12 | 13 | 14 | 15 | %6 16 | 17 | 18 | 19 | 20 | 21 | 22 | True 23 | 24 | 25 | 26 | 27 | 28 | %7 29 | 30 | 31 | 32 | 33 | 34 | 35 | False 36 | 37 | 38 | 39 | 40 | 41 | %13 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test_files/factorial.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'factorial.c' 2 | source_filename = "factorial.c" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-redhat-linux-gnu" 5 | 6 | @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 7 | 8 | ; Function Attrs: noinline nounwind optnone uwtable 9 | define dso_local i32 @factorial_cycle(i32 noundef %0) #0 { 10 | %2 = alloca i32, align 4 11 | %3 = alloca i32, align 4 12 | store i32 %0, ptr %2, align 4 13 | store i32 1, ptr %3, align 4 14 | br label %4 15 | 16 | 4: ; preds = %7, %1 17 | %5 = load i32, ptr %2, align 4 18 | %6 = icmp sgt i32 %5, 0 19 | br i1 %6, label %7, label %13 20 | 21 | 7: ; preds = %4 22 | %8 = load i32, ptr %2, align 4 23 | %9 = load i32, ptr %3, align 4 24 | %10 = mul nsw i32 %9, %8 25 | store i32 %10, ptr %3, align 4 26 | %11 = load i32, ptr %2, align 4 27 | %12 = sub nsw i32 %11, 1 28 | store i32 %12, ptr %2, align 4 29 | br label %4, !llvm.loop !4 30 | 31 | 13: ; preds = %4 32 | %14 = load i32, ptr %3, align 4 33 | ret i32 %14 34 | } 35 | 36 | ; Function Attrs: noinline nounwind optnone uwtable 37 | define dso_local i32 @factorial_req(i32 noundef %0) #0 { 38 | %2 = alloca i32, align 4 39 | %3 = alloca i32, align 4 40 | store i32 %0, ptr %3, align 4 41 | %4 = load i32, ptr %3, align 4 42 | %5 = icmp eq i32 %4, 1 43 | br i1 %5, label %6, label %7 44 | 45 | 6: ; preds = %1 46 | store i32 1, ptr %2, align 4 47 | br label %13 48 | 49 | 7: ; preds = %1 50 | %8 = load i32, ptr %3, align 4 51 | %9 = sub nsw i32 %8, 1 52 | %10 = call i32 @factorial_req(i32 noundef %9) 53 | %11 = load i32, ptr %3, align 4 54 | %12 = mul nsw i32 %10, %11 55 | store i32 %12, ptr %2, align 4 56 | br label %13 57 | 58 | 13: ; preds = %7, %6 59 | %14 = load i32, ptr %2, align 4 60 | ret i32 %14 61 | } 62 | 63 | ; Function Attrs: noinline nounwind optnone uwtable 64 | define dso_local i32 @main() #0 { 65 | %1 = alloca i32, align 4 66 | %2 = alloca i32, align 4 67 | %3 = alloca i32, align 4 68 | store i32 0, ptr %1, align 4 69 | %4 = call i32 @factorial_cycle(i32 noundef 10) 70 | store i32 %4, ptr %2, align 4 71 | %5 = call i32 @factorial_req(i32 noundef 10) 72 | store i32 %5, ptr %3, align 4 73 | %6 = load i32, ptr %2, align 4 74 | %7 = call i32 (ptr, ...) @printf(ptr noundef @.str, i32 noundef %6) 75 | %8 = load i32, ptr %3, align 4 76 | %9 = call i32 (ptr, ...) @printf(ptr noundef @.str, i32 noundef %8) 77 | ret i32 0 78 | } 79 | 80 | declare dso_local i32 @printf(ptr noundef %0, ...) #1 81 | 82 | attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 83 | attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 84 | 85 | !llvm.module.flags = !{!0, !1, !2} 86 | !llvm.ident = !{!3} 87 | 88 | !0 = !{i32 1, !"wchar_size", i32 4} 89 | !1 = !{i32 7, !"uwtable", i32 2} 90 | !2 = !{i32 7, !"frame-pointer", i32 2} 91 | !3 = !{!"clang version 16.0.6 (Fedora 16.0.6-4.fc38)"} 92 | !4 = distinct !{!4, !5} 93 | !5 = !{!"llvm.loop.mustprogress"} 94 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | helper.sh 10 | compile.sh 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | .idea/ 164 | 165 | cmake-build-debug/ 166 | .vscode/ 167 | pybind11/ 168 | -------------------------------------------------------------------------------- /llvm2py/ir/enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import Any, Union 3 | 4 | 5 | Attrs = dict[str, Union[tuple, tuple[Any], tuple[Any, Any]]] 6 | 7 | 8 | class CallingConv(Enum): 9 | """ 10 | `Calling conventions `_. 11 | """ 12 | 13 | C = 0 14 | Fast = 8 15 | Cold = 9 16 | GHC = 10 17 | HiPE = 11 18 | WebKit_JS = 12 19 | AnyReg = 13 20 | PreserveMost = 14 21 | PreserveAll = 15 22 | Swift = 16 23 | CXX_FAST_TLS = 17 24 | Tail = 18 25 | CFGuard_Check = 19 26 | SwiftTail = 20 27 | FirstTargetCC = 64 28 | X86_StdCall = 64 29 | X86_FastCall = 65 30 | ARM_APCS = 66 31 | ARM_AAPCS = 67 32 | ARM_AAPCS_VFP = 68 33 | MSP430_INTR = 69 34 | X86_ThisCall = 70 35 | PTX_Kernel = 71 36 | PTX_Device = 72 37 | SPIR_FUNC = 75 38 | SPIR_KERNEL = 76 39 | Intel_OCL_BI = 77 40 | X86_64_SysV = 78 41 | Win64 = 79 42 | X86_VectorCall = 80 43 | HHVM = 81 44 | HHVM_C = 82 45 | X86_INTR = 83 46 | AVR_INTR = 84 47 | AVR_SIGNAL = 85 48 | AVR_BUILTIN = 86 49 | AMDGPU_VS = 87 50 | AMDGPU_GS = 88 51 | AMDGPU_PS = 89 52 | AMDGPU_CS = 90 53 | AMDGPU_KERNEL = 91 54 | X86_RegCall = 92 55 | AMDGPU_HS = 93 56 | MSP430_BUILTIN = 94 57 | AMDGPU_LS = 95 58 | AMDGPU_ES = 96 59 | AArch64_VectorCall = 97 60 | AArch64_SVE_VectorCall = 98 61 | WASM_EmscriptenInvoke = 99 62 | AMDGPU_Gfx = 100 63 | M68k_INTR = 101 64 | AArch64_SME_ABI_Support_Routines_PreserveMost_From_X0 = 102 65 | AArch64_SME_ABI_Support_Routines_PreserveMost_From_X2 = 103 66 | MaxID = 1023 67 | 68 | 69 | class LinkageType(Enum): 70 | """ 71 | `Linkage types `_. 72 | """ 73 | 74 | ExternalLinkage = 0 # < Externally visible function 75 | AvailableExternallyLinkage = 1 # < Available for inspection, not emission. 76 | LinkOnceAnyLinkage = 2 # < Keep one copy of function when linking (inline) 77 | LinkOnceODRLinkage = 3 # < Same, but only replaced by something equivalent. 78 | WeakAnyLinkage = 4 # < Keep one copy of named function when linking (weak) 79 | WeakODRLinkage = 5 # < Same, but only replaced by something equivalent. 80 | AppendingLinkage = 6 # < Special purpose, only applies to global arrays 81 | InternalLinkage = 7 # < Rename collisions when linking (static functions). 82 | PrivateLinkage = 8 # < Like Internal, but omit from symbol table. 83 | ExternalWeakLinkage = 9 # < ExternalWeak linkage description. 84 | CommonLinkage = 10 # < Tentative definitions. 85 | 86 | 87 | class VisibilityTypes(Enum): 88 | """ 89 | `Visibility types `_. 90 | """ 91 | 92 | DefaultVisibility = 0 # < The GV is visible 93 | HiddenVisibility = 1 # < The GV is hidden 94 | ProtectedVisibility = 2 # < The GV is protected 95 | 96 | 97 | class UnnamedAddr(Enum): 98 | """ 99 | Unnamed addresses 100 | """ 101 | 102 | Default = 0 103 | """ 104 | None 105 | """ 106 | Local = 1 107 | """ 108 | local_unnamed_addr 109 | """ 110 | Global = 2 111 | """ 112 | unnamed_addr 113 | """ 114 | 115 | 116 | class ThreadLocal(Enum): 117 | """ 118 | `Thread local storage models `_. 119 | """ 120 | 121 | NotThreadLocal = 0 122 | GeneralDynamicTLSModel = 1 123 | LocalDynamicTLSModel = 2 124 | InitialExecTLSModel = 3 125 | LocalExecTLSModel = 4 126 | 127 | 128 | class Ordering(Enum): 129 | """ 130 | `Atomic memory ordering constraints `_. 131 | """ 132 | 133 | NotAtomic = 0 134 | Unordered = 1 135 | Monotonic = 2 136 | # Consume = 3 137 | Acquire = 4 138 | Release = 5 139 | AcquireRelease = 6 140 | SequentiallyConsistent = 7 141 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |PyPI| |Actions Status| |Release| 2 | 3 | llvm2py 4 | ####### 5 | 6 | `Documentation is available here `_ 7 | 8 | A fairly large proportion of programs are written in C/C++. 9 | 10 | Let's imagine that you need to analyze programs in a given language, to search for vulnerabilities, to detect patterns or for optimization purposes. 11 | To solve such a problem, it is necessary to have the code in a slightly more convenient form than the source code of the program - an intermediate representation. 12 | 13 | You might come up with the idea of building your own compiler, which is quite difficult, or you might decide to use intermediate representations of GCC or LLVM, but in that case you have to deal with the C/C++ API, which is something you don't want when you have elegant solutions in Python. 14 | 15 | **llvm2py** allows you to analyze C/C++ programs in the LLVM IR representation in Python. 16 | 17 | .. note:: 18 | 19 | The library is in beta, so in case of problems, feel free to create issues. 20 | 21 | 22 | Usage example 23 | ------------- 24 | 25 | The following example will build a control flow graph for the following function. 26 | 27 | .. code-block:: cpp 28 | 29 | int factorial_req(int n) 30 | { 31 | if (n == 1) 32 | { 33 | return 1; 34 | } 35 | return factorial_req(n - 1) * n; 36 | } 37 | 38 | .. code-block:: llvm 39 | 40 | define dso_local i32 @factorial_req(i32 noundef %0) { 41 | %2 = alloca i32, align 4 42 | %3 = alloca i32, align 4 43 | store i32 %0, ptr %3, align 4 44 | %4 = load i32, ptr %3, align 4 45 | %5 = icmp eq i32 %4, 1 46 | br i1 %5, label %6, label %7 47 | 48 | 6: ; preds = %1 49 | store i32 1, ptr %2, align 4 50 | br label %13 51 | 52 | 7: ; preds = %1 53 | %8 = load i32, ptr %3, align 4 54 | %9 = sub nsw i32 %8, 1 55 | %10 = call i32 @factorial_req(i32 noundef %9) 56 | %11 = load i32, ptr %3, align 4 57 | %12 = mul nsw i32 %10, %11 58 | store i32 %12, ptr %2, align 4 59 | br label %13 60 | 61 | 13: ; preds = %7, %6 62 | %14 = load i32, ptr %2, align 4 63 | ret i32 %14 64 | } 65 | 66 | .. code-block:: python 67 | 68 | from llvm2py import parse_assembly 69 | from llvm2py.ir import * 70 | from graphviz import Digraph 71 | 72 | 73 | with open("../test_files/factorial.ll") as file: 74 | source = file.read() 75 | 76 | mod = parse_assembly(source) 77 | 78 | g = Digraph() 79 | 80 | node_attributes = {"shape": "record", "style": "filled"} 81 | 82 | 83 | def name_label(label): 84 | return label.replace("%", "\\%") 85 | 86 | 87 | def name_block(block): 88 | name = block.value.val 89 | return name_label(name) 90 | 91 | 92 | for block in mod.funcs["factorial_req"].blocks.values(): 93 | color = "#f59c7d70" # Default block color 94 | last_instruction = block.instrs[-1] 95 | 96 | match last_instruction: 97 | case Br(None, Value(label)): 98 | g.edge(name_block(block), name_label(label)) 99 | case Br(_, Value(label_false), Value(label_true)): 100 | g.edge(name_block(block), name_label(label_true), label="True") 101 | g.edge(name_block(block), name_label(label_false), label="False") 102 | 103 | if len(block.pred_blocks) >= 2: 104 | color = "#b70d2870" # merge-type block 105 | 106 | g.node(name_block(block), **node_attributes, color=color) 107 | 108 | g.save("cfg.dot") 109 | 110 | .. |PyPI| image:: https://img.shields.io/pypi/v/llvm2py.svg 111 | :target: https://pypi.python.org/pypi/llvm2py 112 | 113 | .. |Actions status| image:: https://github.com/Papr1ka/llvm2py/actions/workflows/main.yml/badge.svg?branch=main 114 | :target: https://github.com/Papr1ka/llvm2py/actions/workflows/main.yml 115 | 116 | .. |Release| image:: https://img.shields.io/github/v/release/Papr1ka/llvm2py.svg?label=release 117 | :target: https://github.com/Papr1ka/llvm2py/releases 118 | -------------------------------------------------------------------------------- /llvm2py/ir/type.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains all types that exist in the LLVM IR representation. 3 | For detailed documentation, please refer to the source https://llvm.org/docs/LangRef.html#type-system 4 | """ 5 | 6 | from __future__ import annotations 7 | from typing import NamedTuple 8 | 9 | 10 | class VoidType(NamedTuple): 11 | def __str__(self): 12 | return "void" 13 | 14 | 15 | class FunctionType(NamedTuple): 16 | """ 17 | :param param_tys: List of function argument types in the appropriate order. 18 | :type param_tys: list[Type] 19 | :param ret_ty: Type of value returned by function. 20 | :type ret_ty: Type 21 | """ 22 | 23 | param_tys: list[Type] 24 | ret_ty: Type 25 | 26 | def __str__(self): 27 | return f"{self.ret_ty} ({', '.join(map(str, self.param_tys))})" 28 | 29 | 30 | class IntegerType(NamedTuple): 31 | """ 32 | :param num_bits: Bit width. 33 | :type num_bits: int 34 | """ 35 | 36 | num_bits: int 37 | 38 | def __str__(self): 39 | return f"i{self.num_bits}" 40 | 41 | 42 | class FPType(NamedTuple): 43 | """ 44 | :param kind: One of {'half', 'bfloat', 'float', 'double', 'fp128', 'x86_fp80', 'ppc_fp128'} 45 | :type kind: str 46 | """ 47 | 48 | kind: str 49 | 50 | def __str__(self): 51 | return self.kind 52 | 53 | 54 | class X86_amxType(NamedTuple): 55 | def __str__(self): 56 | return "x86_amx" 57 | 58 | 59 | class PtrType(NamedTuple): 60 | """ 61 | Almost always, pointers are opaque (untyped). 62 | 63 | :param addr_space: Numbered address space where the pointed-to object resides. 64 | :type addr_space: int 65 | :param ty: Pointer type (may be used by some gpu targets). 66 | :type ty: Type | None 67 | """ 68 | 69 | addr_space: int 70 | ty: Type | None = None 71 | 72 | def __str__(self): 73 | if self.ty is not None: 74 | return f"ptr: {self.ty}" 75 | else: 76 | return "ptr" 77 | 78 | 79 | class TargetExtensionType(NamedTuple): 80 | """ 81 | :param name: Type name. 82 | :type name: str 83 | :param params: List of type parameters. 84 | :type params: list[int | Type] 85 | """ 86 | 87 | name: str 88 | params: list[int | Type] 89 | 90 | def __str__(self): 91 | name = ['"' + self.name + '"'] 92 | return f"target({', '.join(map(str, name + self.params))})" 93 | 94 | 95 | class VectorType(NamedTuple): 96 | """ 97 | :param elem_count: Vector element count. If the vector is scalable, it is the minimum number of elements in this vector. 98 | The actual number of elements in the vector is an integer multiple of this value. 99 | :type elem_count: int 100 | :param elem_ty: Vector element type. 101 | :type elem_ty: Type 102 | :param is_scalable: If True, the vector is scalable (vscale). 103 | :type is_scalable: bool 104 | """ 105 | 106 | elem_count: int 107 | elem_ty: Type 108 | is_scalable: bool = False 109 | 110 | def __str__(self): 111 | if self.is_scalable: 112 | return f"" 113 | else: 114 | return f"<{self.elem_count} x {self.elem_ty}>" 115 | 116 | 117 | class LabelType(NamedTuple): 118 | def __str__(self): 119 | return "label" 120 | 121 | 122 | class TokenType(NamedTuple): 123 | def __str__(self): 124 | return "token" 125 | 126 | 127 | class MetadataType(NamedTuple): 128 | def __str__(self): 129 | return "metadata" 130 | 131 | 132 | class ArrayType(NamedTuple): 133 | """ 134 | :param elem_count: The number of elements in the array. 135 | :type elem_count: int 136 | :param elem_ty: The array element type. 137 | :type elem_ty: Type 138 | """ 139 | 140 | elem_count: int 141 | elem_ty: Type 142 | 143 | def __str__(self): 144 | return f"[{self.elem_count} x {self.elem_ty}]" 145 | 146 | 147 | class StructureType(NamedTuple): 148 | """ 149 | :param name: Structure name, if it is a named structure. 150 | :type name: str | None 151 | :type elem_tys: List of structure types. 152 | :type elem_tys: list[Type] 153 | :type is_packed: If True, the structure is packed, 154 | which indicate that the alignment of the struct is one byte and 155 | there is no padding between the elements. 156 | :type is_packed: bool 157 | """ 158 | 159 | name: str | None 160 | elem_tys: list[Type] 161 | is_packed: bool = False 162 | 163 | def __str__(self): 164 | if self.is_packed: 165 | return f"<{{{', '.join(map(str, self.elem_tys))}}}>" 166 | else: 167 | return f"{{{', '.join(map(str, self.elem_tys))}}}" 168 | 169 | 170 | class OpaqueType(NamedTuple): 171 | def __str__(self): 172 | return "opaque" 173 | 174 | 175 | Type = ( 176 | VoidType 177 | | FunctionType 178 | | IntegerType 179 | | FPType 180 | | X86_amxType 181 | | PtrType 182 | | TargetExtensionType 183 | | VectorType 184 | | LabelType 185 | | TokenType 186 | | MetadataType 187 | | ArrayType 188 | | StructureType 189 | | OpaqueType 190 | ) 191 | -------------------------------------------------------------------------------- /examples/interpreter.py: -------------------------------------------------------------------------------- 1 | from llvm2py import parse_assembly 2 | from llvm2py.ir import * 3 | 4 | 5 | def first_value_in_dict(mapping: dict): 6 | return next(iter(mapping.values())) 7 | 8 | 9 | class Mem: 10 | def __init__(self): 11 | self.stack_frames = [] 12 | 13 | def alloca(self): 14 | addr = len(self.stack_frames[-1]) 15 | self.stack_frames[-1].append(None) 16 | return addr 17 | 18 | def store(self, value, addr): 19 | self.stack_frames[-1][addr] = value 20 | 21 | def load(self, addr): 22 | return self.stack_frames[-1][addr] 23 | 24 | def new_frame(self): 25 | self.stack_frames.append([]) 26 | 27 | def pop_frame(self): 28 | return self.stack_frames.pop() 29 | 30 | 31 | class State(NamedTuple): 32 | globals: dict 33 | vars: dict 34 | mem: Mem 35 | 36 | 37 | def interp_value(value: Value, state): 38 | globals, vars, _ = state 39 | match value.val: 40 | case str(name): 41 | if name in vars: 42 | new_value = vars[name] 43 | else: 44 | new_value = globals[name] 45 | match new_value: 46 | case GlobalVariable(_, initializer): 47 | return interp_value(initializer, state) 48 | case _: 49 | return new_value 50 | case int(val): 51 | return val 52 | case bytes(val): 53 | return str(val, "utf-8") 54 | case _: 55 | raise NotImplementedError() 56 | 57 | 58 | def interp_instr(instr: Instruction, state: State): 59 | _, vars, mem = state 60 | match instr: 61 | case Alloca(Value(addr)): 62 | vars[addr] = mem.alloca() 63 | case Store(value, Value(addr)): 64 | val = interp_value(value, state) 65 | mem.store(val, vars[addr]) 66 | case Load(Value(res), Value(addr)): 67 | vars[res] = mem.load(vars[addr]) 68 | case ICmp(cond, Value(res), fst, snd): 69 | arg0 = interp_value(fst, state) 70 | arg1 = interp_value(snd, state) 71 | match cond: 72 | case "sgt": 73 | vars[res] = arg0 > arg1 74 | case "eq": 75 | vars[res] = arg0 == arg1 76 | case _: 77 | raise NotImplementedError() 78 | 79 | case BinOp("mul", Value(res), fst, snd): 80 | arg0 = interp_value(fst, state) 81 | arg1 = interp_value(snd, state) 82 | vars[res] = arg0 * arg1 83 | case BinOp("sub", Value(res), fst, snd): 84 | arg0 = interp_value(fst, state) 85 | arg1 = interp_value(snd, state) 86 | vars[res] = arg0 - arg1 87 | case Call(Value(res), Value(func), args): 88 | func = mod.funcs[func] 89 | arg_vals = [interp_value(arg, state) for arg in args] 90 | vars[res] = interp_function(func, arg_vals, state) 91 | case _: 92 | raise NotImplementedError() 93 | 94 | 95 | def interp_tail(instr: Instruction, state: State): 96 | match instr: 97 | case Br(None, Value(label)): 98 | return (label, None) 99 | case Br(cond, Value(label_false), Value(label_true)): 100 | cond_val = interp_value(cond, state) 101 | if cond_val: 102 | return (label_true, None) 103 | else: 104 | return (label_false, None) 105 | case Ret(value): 106 | val = interp_value(value, state) 107 | return (None, val) 108 | 109 | 110 | def interp_block(block: Block, state: State): 111 | *instrs, tail = block.instrs 112 | for instr in instrs: 113 | interp_instr(instr, state) 114 | return interp_tail(tail, state) 115 | 116 | 117 | external_functions = {"printf": lambda fmt, *args: print(fmt % tuple(args), end="")} 118 | 119 | 120 | def interp_external_function(func: Function, args): 121 | func_name = func.value.val 122 | if func_name in external_functions: 123 | return external_functions[func_name](*args) 124 | else: 125 | raise NotImplementedError() 126 | 127 | 128 | def interp_function(func: Function, args, state): 129 | globals, _, mem = state 130 | vars = {} 131 | new_state = State(globals, vars, mem) 132 | 133 | for param, arg in zip(func.args, args): 134 | vars[param.val] = arg 135 | 136 | if func.has_no_body(): 137 | return interp_external_function(func, args) 138 | 139 | mem.new_frame() 140 | blocks = func.blocks 141 | fst_block = first_value_in_dict(blocks) 142 | label, value = interp_block(fst_block, new_state) 143 | while label is not None: 144 | label, value = interp_block(blocks[label], new_state) 145 | 146 | mem.pop_frame() 147 | return value 148 | 149 | 150 | def interp_module(mod: Module): 151 | state = State(mod.global_vars, {}, Mem()) 152 | main = mod.funcs["main"] 153 | return interp_function(main, [], state) 154 | 155 | 156 | if __name__ == "__main__": 157 | with open("../test_files/factorial.ll") as file: 158 | source = file.read() 159 | 160 | mod = parse_assembly(source) 161 | interp_module(mod) 162 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import subprocess 4 | import sys 5 | from pathlib import Path 6 | import platform 7 | from setuptools import Extension, setup 8 | from setuptools.command.build_ext import build_ext 9 | 10 | 11 | sources = [ 12 | "src/bind.cpp", 13 | "src/module.cpp", 14 | "src/parser.cpp", 15 | "include/module.h", 16 | "include/parser.h", 17 | "include/tools.h", 18 | "CMakeLists.txt", 19 | ] 20 | 21 | LLVM_DIR = "/usr/lib/llvm-18/cmake" # Path to cmake folder of llvm library 22 | 23 | 24 | class CMakeExtension(Extension): 25 | def __init__(self, name: str, sourcedir: str = "") -> None: 26 | super().__init__(name, sources=sources) 27 | self.sourcedir = os.fspath(Path(sourcedir).resolve()) 28 | 29 | 30 | class CMakeBuild(build_ext): 31 | def build_extension(self, ext: CMakeExtension) -> None: 32 | # if self.compiler.compiler_type == 'mingw32': 33 | # for e in self.extensions: 34 | # e.extra_link_args = link_args 35 | # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ 36 | ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) 37 | extdir = ext_fullpath.parent.resolve() 38 | 39 | # Using this requires trailing slash for auto-detection & inclusion of 40 | # auxiliary "native" libs 41 | 42 | debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug 43 | cfg = "Debug" if debug else "Release" 44 | 45 | # CMake lets you override the generator - we need to check this. 46 | # Can be set with Conda-Build, for example. 47 | cmake_generator = os.environ.get("CMAKE_GENERATOR", "Ninja") 48 | 49 | # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON 50 | # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code 51 | # from Python. 52 | p = platform.system() 53 | 54 | cmake_args = [ 55 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", 56 | f"-DPYTHON_EXECUTABLE={sys.executable}", 57 | f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm 58 | ] 59 | if p == "Linux" and LLVM_DIR is not None: 60 | cmake_args.append(f"-DLLVM_DIR={LLVM_DIR}") 61 | 62 | build_args = [] 63 | # Adding CMake arguments set as environment variable 64 | # (needed e.g. to build for ARM OSx on conda-forge) 65 | if "CMAKE_ARGS" in os.environ: 66 | cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] 67 | 68 | # In this example, we pass in the version to C++. You might not need to. 69 | cmake_args += [f"-DEXAMPLE_VERSION_INFO={self.distribution.get_version()}"] 70 | 71 | if self.compiler.compiler_type != "msvc": 72 | # Using Ninja-build since it a) is available as a wheel and b) 73 | # multithreads automatically. MSVC would require all variables be 74 | # exported for Ninja to pick it up, which is a little tricky to do. 75 | # Users can override the generator with CMAKE_GENERATOR in CMake 76 | # 3.15+. 77 | if not cmake_generator or cmake_generator == "Ninja": 78 | try: 79 | import ninja 80 | 81 | ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" 82 | cmake_args += [ 83 | "-GNinja", 84 | f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", 85 | ] 86 | except ImportError: 87 | pass 88 | 89 | else: 90 | # Single config generators are handled "normally" 91 | single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) 92 | 93 | # CMake allows an arch-in-generator style for backward compatibility 94 | contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) 95 | 96 | # Specify the arch if using MSVC generator, but only if it doesn't 97 | # contain a backward-compatibility arch spec already in the 98 | # generator name. 99 | # if not single_config and not contains_arch: 100 | # cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] 101 | 102 | # Multi-config generators have a different way to specify configs 103 | if not single_config: 104 | cmake_args += [ 105 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" 106 | ] 107 | build_args += ["--config", cfg] 108 | 109 | if sys.platform.startswith("darwin"): 110 | # Cross-compile support for macOS - respect ARCHFLAGS if set 111 | archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) 112 | if archs: 113 | cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] 114 | 115 | # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level 116 | # across all generators. 117 | if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: 118 | # self.parallel is a Python 3 only way to set parallel jobs by hand 119 | # using -j in the build_ext call, not supported by pip or PyPA-build. 120 | if hasattr(self, "parallel") and self.parallel: 121 | # CMake 3.12+ only. 122 | build_args += [f"-j{self.parallel}"] 123 | 124 | build_temp = Path(self.build_temp) / ext.name 125 | if not build_temp.exists(): 126 | build_temp.mkdir(parents=True) 127 | 128 | subprocess.run( 129 | ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True 130 | ) 131 | subprocess.run( 132 | ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True 133 | ) 134 | 135 | 136 | # The information here can also be placed in setup.cfg - better separation of 137 | # logic and declaration, and simpler if you include description/version in a file. 138 | 139 | with open("./README.rst", encoding="utf-8") as file: 140 | long_description = file.read() 141 | 142 | setup( 143 | name="llvm2py", 144 | version="0.2.0b", 145 | author="Papr1ka", 146 | url="https://github.com/Papr1ka/llvm2py", 147 | author_email="kirillpavlov4214@gmail.com", 148 | description="A library for analyzing LLVM IR in Python", 149 | long_description=long_description, 150 | long_description_content_type="text/markdown", 151 | packages=["llvm2py", "llvm2py/ir"], 152 | ext_modules=[CMakeExtension("llvm2py._llvm2py")], 153 | cmdclass={"build_ext": CMakeBuild}, 154 | zip_safe=False, 155 | python_requires=">=3.10", 156 | classifiers=[ 157 | "Development Status :: 4 - Beta", 158 | "Operating System :: Microsoft :: Windows", 159 | "Operating System :: POSIX", 160 | "Intended Audience :: Developers", 161 | "Intended Audience :: Education", 162 | "Intended Audience :: Science/Research", 163 | "Topic :: Scientific/Engineering", 164 | "Topic :: Software Development :: Libraries", 165 | "License :: OSI Approved :: MIT License", 166 | "Natural Language :: English", 167 | "Programming Language :: Python :: 3", 168 | "Programming Language :: Python :: 3.10", 169 | "Programming Language :: Python :: 3.11", 170 | "Programming Language :: Python :: 3.12", 171 | "Programming Language :: Python :: 3.13", 172 | ], 173 | keywords=[ 174 | "llvm", 175 | "analysis", 176 | "ir", 177 | "intermediate", 178 | "presentation", 179 | "representation", 180 | "compiler", 181 | "parser", 182 | ], 183 | license="MIT", 184 | ) 185 | -------------------------------------------------------------------------------- /test_files/test.ll: -------------------------------------------------------------------------------- 1 | ; RUN: llvm-as < %s | llvm-dis > %t.orig 2 | ; RUN: llvm-as < %s | llvm-c-test --echo > %t.echo 3 | ; RUN: diff -w %t.orig %t.echo 4 | 5 | source_filename = "/test/Bindings/echo.ll" 6 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 7 | target triple = "x86_64-apple-macosx10.11.0" 8 | 9 | module asm "classical GAS" 10 | 11 | %S = type { i64, %S* } 12 | 13 | @var = global i32 42 14 | @ext = external global i32* 15 | @cst = constant %S { i64 1, %S* @cst } 16 | @tl = thread_local global { i64, %S* } { i64 1, %S* @cst } 17 | @arr = linkonce_odr global [5 x i8] [ i8 2, i8 3, i8 5, i8 7, i8 11 ] 18 | @str = private unnamed_addr constant [13 x i8] c"hello world\0A\00" 19 | @locStr = private local_unnamed_addr constant [13 x i8] c"hello world\0A\00" 20 | @hidden = hidden global i32 7 21 | @protected = protected global i32 23 22 | @section = global i32 27, section ".custom" 23 | @align = global i32 31, align 4 24 | @nullptr = global i32* null 25 | 26 | @const_gep = global i32* getelementptr (i32, i32* @var, i64 2) 27 | @const_inbounds_gep = global i32* getelementptr inbounds (i32, i32* @var, i64 1) 28 | 29 | @aliased1 = alias i32, i32* @var 30 | @aliased2 = internal alias i32, i32* @var 31 | @aliased3 = external alias i32, i32* @var 32 | @aliased4 = weak alias i32, i32* @var 33 | @aliased5 = weak_odr alias i32, i32* @var 34 | 35 | @ifunc = ifunc i32 (i32), i32 (i32)* ()* @ifunc_resolver 36 | 37 | define i32 (i32)* @ifunc_resolver() { 38 | entry: 39 | ret i32 (i32)* null 40 | } 41 | 42 | define { i64, %S* } @unpackrepack(%S %s) { 43 | %1 = extractvalue %S %s, 0 44 | %2 = extractvalue %S %s, 1 45 | %3 = insertvalue { i64, %S* } undef, %S* %2, 1 46 | %4 = insertvalue { i64, %S* } %3, i64 %1, 0 47 | ret { i64, %S* } %4 48 | } 49 | 50 | declare void @decl() 51 | 52 | ; TODO: label and metadata types 53 | define void @types() { 54 | %1 = alloca half, align 2 55 | %2 = alloca float, align 4 56 | %3 = alloca double, align 8 57 | %4 = alloca x86_fp80, align 16 58 | %5 = alloca fp128, align 16 59 | %6 = alloca ppc_fp128, align 16 60 | %7 = alloca inalloca i7, align 1 61 | %8 = alloca void (i1)*, align 8 62 | %9 = alloca [3 x i22], align 4 63 | %10 = alloca i328 addrspace(5)*, align 8 64 | %11 = alloca <5 x i23*>, align 64 65 | ret void 66 | } 67 | 68 | define double @fops(double %a, double %b) { 69 | %1 = fneg nnan double %a 70 | %2 = fadd ninf double %a, %b 71 | %3 = fsub nsz double %a, %b 72 | %4 = fmul arcp double %a, %b 73 | %5 = fdiv contract afn reassoc double %a, %b 74 | %6 = frem contract afn reassoc double %a, %b 75 | ret double %6 76 | } 77 | 78 | define i32 @iops(i32 %a, i32 %b) { 79 | %1 = add i32 %a, %b 80 | %2 = mul nsw i32 %a, %1 81 | %3 = sub nuw nsw i32 %2, %1 82 | %4 = udiv exact i32 %3, %b 83 | %5 = sdiv i32 %2, %4 84 | %6 = urem i32 %3, %5 85 | %7 = srem i32 %2, %6 86 | %8 = shl nuw i32 %1, %b 87 | %9 = lshr exact i32 %a, %7 88 | %10 = ashr i32 %b, %8 89 | %11 = and i32 %9, %10 90 | %12 = or disjoint i32 %2, %11 91 | %13 = xor i32 %12, %4 92 | ret i32 %13 93 | } 94 | 95 | define i32 @call() { 96 | %1 = call i32 @iops(i32 23, i32 19) 97 | %2 = call fast double @fops(double immarg 3.0, double 71.0) 98 | ret i32 %1 99 | } 100 | 101 | define i32 @cond(i32 %a, i32 %b) { 102 | br label %br 103 | unreachable: 104 | unreachable 105 | br: 106 | %1 = icmp eq i32 %a, %b 107 | br i1 %1, label %next0, label %unreachable 108 | next0: 109 | %2 = icmp ne i32 %a, %b 110 | br i1 %2, label %next1, label %unreachable 111 | next1: 112 | %3 = icmp ugt i32 %a, %b 113 | br i1 %3, label %next2, label %unreachable 114 | next2: 115 | %4 = icmp uge i32 %a, %b 116 | br i1 %4, label %next3, label %unreachable 117 | next3: 118 | %5 = icmp ult i32 %a, %b 119 | br i1 %5, label %next4, label %unreachable 120 | next4: 121 | %6 = icmp ule i32 %a, %b 122 | br i1 %6, label %next5, label %unreachable 123 | next5: 124 | %7 = icmp sgt i32 %a, %b 125 | br i1 %7, label %next6, label %unreachable 126 | next6: 127 | %8 = icmp sge i32 %a, %b 128 | br i1 %8, label %next7, label %unreachable 129 | next7: 130 | %9 = icmp slt i32 %a, %b 131 | br i1 %9, label %next8, label %unreachable 132 | next8: 133 | %10 = fcmp fast olt double 1.0, 2.0 134 | br i1 %10, label %next9, label %unreachable 135 | next9: 136 | ret i32 0 137 | } 138 | 139 | define i32 @loop(i32 %i) { 140 | br label %cond 141 | cond: 142 | %c = phi i32 [ %i, %0 ], [ %j, %do ] 143 | %p = phi i32 [ %r, %do ], [ 789, %0 ] 144 | %1 = icmp eq i32 %c, 0 145 | br i1 %1, label %do, label %done 146 | do: 147 | %2 = sub i32 %p, 23 148 | %j = sub i32 %i, 1 149 | %r = mul i32 %2, 3 150 | br label %cond 151 | done: 152 | ret i32 %p 153 | } 154 | 155 | define void @memops(i8* %ptr) { 156 | %a = load i8, i8* %ptr 157 | %b = load volatile i8, i8* %ptr 158 | %c = load i8, i8* %ptr, align 8 159 | %d = load atomic i8, i8* %ptr acquire, align 32 160 | store i8 0, i8* %ptr 161 | store volatile i8 0, i8* %ptr 162 | store i8 0, i8* %ptr, align 8 163 | store atomic i8 0, i8* %ptr release, align 32 164 | %e = atomicrmw add i8* %ptr, i8 0 monotonic, align 1 165 | %f = atomicrmw volatile xchg i8* %ptr, i8 0 acq_rel, align 8 166 | %g = cmpxchg i8* %ptr, i8 1, i8 2 seq_cst acquire, align 1 167 | %h = cmpxchg weak i8* %ptr, i8 1, i8 2 seq_cst acquire, align 8 168 | %i = cmpxchg volatile i8* %ptr, i8 1, i8 2 monotonic monotonic, align 16 169 | ret void 170 | } 171 | 172 | define i32 @vectorops(i32, i32) { 173 | %a = insertelement <4 x i32> undef, i32 %0, i32 0 174 | %b = insertelement <4 x i32> %a, i32 %1, i32 2 175 | %c = shufflevector <4 x i32> %b, <4 x i32> undef, <4 x i32> zeroinitializer 176 | %d = shufflevector <4 x i32> %c, <4 x i32> %b, <4 x i32> 177 | %e = add <4 x i32> %d, %a 178 | %f = mul <4 x i32> %e, %b 179 | %g = xor <4 x i32> %f, %d 180 | %h = or <4 x i32> %f, %e 181 | %i = lshr <4 x i32> %h, 182 | %j = shl <4 x i32> %i, 183 | %k = shufflevector <4 x i32> %j, <4 x i32> %i, <4 x i32> 184 | %m = shufflevector <4 x i32> %k, <4 x i32> undef, <1 x i32> 185 | %n = shufflevector <4 x i32> %j, <4 x i32> undef, <8 x i32> 186 | %p = extractelement <8 x i32> %n, i32 5 187 | ret i32 %p 188 | } 189 | 190 | define i32 @scalablevectorops(i32, ) { 191 | %a = insertelement undef, i32 %0, i32 0 192 | %b = insertelement %a, i32 %0, i32 2 193 | %c = shufflevector %b, undef, zeroinitializer 194 | %e = add %a, %1 195 | %f = mul %e, %b 196 | %g = xor %f, %e 197 | %h = or %g, %e 198 | %i = lshr %h, undef 199 | %j = extractelement %i, i32 3 200 | ret i32 %j 201 | } 202 | 203 | declare void @personalityFn() 204 | 205 | define void @exn() personality void ()* @personalityFn { 206 | entry: 207 | invoke void @decl() 208 | to label %via.cleanup unwind label %exn.dispatch 209 | via.cleanup: 210 | invoke void @decl() 211 | to label %via.catchswitch unwind label %cleanup.inner 212 | cleanup.inner: 213 | %cp.inner = cleanuppad within none [] 214 | cleanupret from %cp.inner unwind label %exn.dispatch 215 | via.catchswitch: 216 | invoke void @decl() 217 | to label %exit unwind label %dispatch.inner 218 | dispatch.inner: 219 | %cs.inner = catchswitch within none [label %pad.inner] unwind label %exn.dispatch 220 | pad.inner: 221 | %catch.inner = catchpad within %cs.inner [i32 0] 222 | catchret from %catch.inner to label %exit 223 | exn.dispatch: 224 | %cs = catchswitch within none [label %pad1, label %pad2] unwind label %cleanup 225 | pad1: 226 | catchpad within %cs [i32 1] 227 | unreachable 228 | pad2: 229 | catchpad within %cs [i32 2] 230 | unreachable 231 | cleanup: 232 | %cp = cleanuppad within none [] 233 | cleanupret from %cp unwind to caller 234 | exit: 235 | ret void 236 | } 237 | 238 | declare i8* @llvm.stacksave() 239 | declare void @llvm.stackrestore(i8*) 240 | declare void @llvm.lifetime.start.p0i8(i64, i8*) 241 | declare void @llvm.lifetime.end.p0i8(i64, i8*) 242 | 243 | define void @test_intrinsics() { 244 | entry: 245 | %sp = call i8* @llvm.stacksave() 246 | %x = alloca i32, align 4 247 | %0 = bitcast i32* %x to i8* 248 | call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) 249 | call void @llvm.lifetime.end.p0i8(i64 4, i8* %0) 250 | call void @llvm.stackrestore(i8* %sp) 251 | ret void 252 | } 253 | -------------------------------------------------------------------------------- /docs/source/example.rst: -------------------------------------------------------------------------------- 1 | Straightforward simple interpreter 2 | ################################## 3 | 4 | Let's create a simple interpreter of the llvm ir subset to compute the value of the factorial. 5 | 6 | .. _source_program: 7 | 8 | Source program 9 | ============== 10 | 11 | Here is the code of the single test program, whick execution we want to support. 12 | 13 | .. code-block:: c 14 | 15 | #include "stdio.h" 16 | 17 | 18 | int factorial_cycle(int n) 19 | { 20 | int result = 1; 21 | while (n > 0) 22 | { 23 | result *= n; 24 | n -= 1; 25 | } 26 | return result; 27 | } 28 | 29 | int factorial_req(int n) 30 | { 31 | if (n == 1) 32 | { 33 | return 1; 34 | } 35 | return factorial_req(n - 1) * n; 36 | } 37 | 38 | int main() 39 | { 40 | int factorial_cycle_result = factorial_cycle(10); 41 | int factorial_req_result = factorial_req(10); 42 | printf("%d\n", factorial_cycle_result); 43 | printf("%d\n", factorial_req_result); 44 | return 0; 45 | } 46 | 47 | .. _compiled program: 48 | 49 | Compiled program 50 | ================ 51 | 52 | Now let's look at the compiled version without optimizations 53 | 54 | .. code-block:: llvm 55 | 56 | @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 57 | 58 | 59 | define dso_local i32 @factorial_cycle(i32 noundef %0) { 60 | %2 = alloca i32, align 4 61 | %3 = alloca i32, align 4 62 | store i32 %0, ptr %2, align 4 63 | store i32 1, ptr %3, align 4 64 | br label %4 65 | 66 | 4: ; preds = %7, %1 67 | %5 = load i32, ptr %2, align 4 68 | %6 = icmp sgt i32 %5, 0 69 | br i1 %6, label %7, label %13 70 | 71 | 7: ; preds = %4 72 | %8 = load i32, ptr %2, align 4 73 | %9 = load i32, ptr %3, align 4 74 | %10 = mul nsw i32 %9, %8 75 | store i32 %10, ptr %3, align 4 76 | %11 = load i32, ptr %2, align 4 77 | %12 = sub nsw i32 %11, 1 78 | store i32 %12, ptr %2, align 4 79 | br label %4 80 | 81 | 13: ; preds = %4 82 | %14 = load i32, ptr %3, align 4 83 | ret i32 %14 84 | } 85 | 86 | define dso_local i32 @factorial_req(i32 noundef %0) { 87 | %2 = alloca i32, align 4 88 | %3 = alloca i32, align 4 89 | store i32 %0, ptr %3, align 4 90 | %4 = load i32, ptr %3, align 4 91 | %5 = icmp eq i32 %4, 1 92 | br i1 %5, label %6, label %7 93 | 94 | 6: ; preds = %1 95 | store i32 1, ptr %2, align 4 96 | br label %13 97 | 98 | 7: ; preds = %1 99 | %8 = load i32, ptr %3, align 4 100 | %9 = sub nsw i32 %8, 1 101 | %10 = call i32 @factorial_req(i32 noundef %9) 102 | %11 = load i32, ptr %3, align 4 103 | %12 = mul nsw i32 %10, %11 104 | store i32 %12, ptr %2, align 4 105 | br label %13 106 | 107 | 13: ; preds = %7, %6 108 | %14 = load i32, ptr %2, align 4 109 | ret i32 %14 110 | } 111 | 112 | define dso_local i32 @main() #0 { 113 | %1 = alloca i32, align 4 114 | %2 = alloca i32, align 4 115 | %3 = alloca i32, align 4 116 | store i32 0, ptr %1, align 4 117 | %4 = call i32 @factorial_cycle(i32 noundef 10) 118 | store i32 %4, ptr %2, align 4 119 | %5 = call i32 @factorial_req(i32 noundef 10) 120 | store i32 %5, ptr %3, align 4 121 | %6 = load i32, ptr %2, align 4 122 | %7 = call i32 (ptr, ...) @printf(ptr noundef @.str, i32 noundef %6) 123 | %8 = load i32, ptr %3, align 4 124 | %9 = call i32 (ptr, ...) @printf(ptr noundef @.str, i32 noundef %8) 125 | ret i32 0 126 | } 127 | 128 | declare dso_local i32 @printf(ptr noundef, ...) 129 | 130 | .. _full_implementation: 131 | 132 | Full implementation 133 | =================== 134 | 135 | direct realization may look as follows: 136 | 137 | .. code-block:: python 138 | 139 | from llvm2py import parse_assembly 140 | from llvm2py.ir import * 141 | 142 | 143 | def first_value_in_dict(mapping: dict): 144 | return next(iter(mapping.values())) 145 | 146 | 147 | class Mem: 148 | def __init__(self): 149 | self.stack_frames = [] 150 | 151 | def alloca(self): 152 | addr = len(self.stack_frames[-1]) 153 | self.stack_frames[-1].append(None) 154 | return addr 155 | 156 | def store(self, value, addr): 157 | self.stack_frames[-1][addr] = value 158 | 159 | def load(self, addr): 160 | return self.stack_frames[-1][addr] 161 | 162 | def new_frame(self): 163 | self.stack_frames.append([]) 164 | 165 | def pop_frame(self): 166 | return self.stack_frames.pop() 167 | 168 | 169 | class State(NamedTuple): 170 | globals: dict 171 | vars: dict 172 | mem: Mem 173 | 174 | 175 | def interp_value(value: Value, state): 176 | globals, vars, _ = state 177 | match value.val: 178 | case str(name): 179 | if name in vars: 180 | new_value = vars[name] 181 | else: 182 | new_value = globals[name] 183 | match new_value: 184 | case GlobalVariable(_, initializer): 185 | return interp_value(initializer, state) 186 | case Value(): 187 | return interp_value(new_value, state) 188 | case _: 189 | return new_value 190 | case int(val): 191 | return val 192 | case bytes(val): 193 | return str(val, "utf-8") 194 | case _: 195 | raise NotImplementedError() 196 | 197 | 198 | def interp_instr(instr: Instruction, state: State): 199 | _, vars, mem = state 200 | match instr: 201 | case Alloca(Value(addr)): 202 | vars[addr] = mem.alloca() 203 | case Store(value, Value(addr)): 204 | val = interp_value(value, state) 205 | mem.store(val, vars[addr]) 206 | case Load(Value(res), Value(addr)): 207 | vars[res] = mem.load(vars[addr]) 208 | case ICmp(cond, Value(res), fst, snd): 209 | arg0 = interp_value(fst, state) 210 | arg1 = interp_value(snd, state) 211 | match cond: 212 | case "sgt": 213 | vars[res] = arg0 > arg1 214 | case "eq": 215 | vars[res] = arg0 == arg1 216 | case _: 217 | raise NotImplementedError() 218 | 219 | case BinOp("mul", Value(res), fst, snd): 220 | arg0 = interp_value(fst, state) 221 | arg1 = interp_value(snd, state) 222 | vars[res] = arg0 * arg1 223 | case BinOp("sub", Value(res), fst, snd): 224 | arg0 = interp_value(fst, state) 225 | arg1 = interp_value(snd, state) 226 | vars[res] = arg0 - arg1 227 | case Call(Value(res), Value(func), args): 228 | func = mod.funcs[func] 229 | arg_vals = [interp_value(arg, state) for arg in args] 230 | vars[res] = interp_function(func, arg_vals, state) 231 | case _: 232 | raise NotImplementedError() 233 | 234 | 235 | def interp_tail(instr: Instruction, state: State): 236 | match instr: 237 | case Br(None, Value(label)): 238 | return (label, None) 239 | case Br(cond, Value(label_false), Value(label_true)): 240 | cond_val = interp_value(cond, state) 241 | if cond_val: 242 | return (label_true, None) 243 | else: 244 | return (label_false, None) 245 | case Ret(value): 246 | val = interp_value(value, state) 247 | return (None, val) 248 | 249 | 250 | def interp_block(block: Block, state: State): 251 | *instrs, tail = block.instrs 252 | for instr in instrs: 253 | interp_instr(instr, state) 254 | return interp_tail(tail, state) 255 | 256 | 257 | external_functions = {"printf": lambda fmt, *args: print(fmt % tuple(args), end="")} 258 | 259 | 260 | def interp_external_function(func: Function, args): 261 | func_name = func.value.val 262 | if func_name in external_functions: 263 | return external_functions[func_name](*args) 264 | else: 265 | raise NotImplementedError() 266 | 267 | 268 | def interp_function(func: Function, args, state): 269 | globals, _, mem = state 270 | vars = {} 271 | new_state = State(globals, vars, mem) 272 | 273 | for param, arg in zip(func.args, args): 274 | vars[param.val] = arg 275 | 276 | if func.has_no_body(): 277 | return interp_external_function(func, args) 278 | 279 | mem.new_frame() 280 | blocks = func.blocks 281 | fst_block = first_value_in_dict(blocks) 282 | label, value = interp_block(fst_block, new_state) 283 | while label is not None: 284 | label, value = interp_block(blocks[label], new_state) 285 | 286 | mem.pop_frame() 287 | return value 288 | 289 | 290 | def interp_module(mod: Module): 291 | state = State(mod.global_vars, {}, Mem()) 292 | main = mod.funcs["main"] 293 | return interp_function(main, [], state) 294 | 295 | 296 | if __name__ == "__main__": 297 | with open("../test_files/factorial.ll") as file: 298 | source = file.read() 299 | 300 | mod = parse_assembly(source) 301 | interp_module(mod) 302 | 303 | .. _explanation: 304 | 305 | Explanation 306 | =========== 307 | 308 | 309 | .. _memory_model: 310 | 311 | Memory model 312 | ------------ 313 | 314 | Let's add a couple of comments 315 | 316 | .. code-block:: python 317 | 318 | class Mem: 319 | def __init__(self): 320 | self.stack_frames = [] 321 | 322 | def alloca(self): 323 | addr = len(self.stack_frames[-1]) 324 | self.stack_frames[-1].append(None) 325 | return addr 326 | 327 | def store(self, value, addr): 328 | self.stack_frames[-1][addr] = value 329 | 330 | def load(self, addr): 331 | return self.stack_frames[-1][addr] 332 | 333 | def new_frame(self): 334 | self.stack_frames.append([]) 335 | 336 | def pop_frame(self): 337 | return self.stack_frames.pop() 338 | 339 | This class models operations with stack memory 340 | 341 | - alloca returns the address on the stack of the function that is currently being executed. 342 | - load and store in our program work only with addresses obtained using alloca. 343 | - After the function is executed, the stack frame memory is freed. 344 | 345 | Summarizing these facts, we can allow to operate only with the last stack frame. 346 | 347 | alloca allocates memory on the stack and returns its “address” 348 | load and store work with memory by “address” 349 | new_frame and pop_frame create and delete stack frames, which is useful. 350 | 351 | .. _state: 352 | 353 | State 354 | ----- 355 | 356 | .. code-block:: python 357 | 358 | class State(NamedTuple): 359 | globals: dict 360 | vars: dict 361 | mem: Mem 362 | 363 | This class is needed to store the program execution state. 364 | 365 | .. _module_interpretation: 366 | 367 | Module interpretation 368 | --------------------- 369 | 370 | .. code-block:: python 371 | 372 | def interp_module(mod: Module): 373 | state = State(mod.global_vars, {}, Mem()) 374 | main = mod.funcs["main"] 375 | return interp_function(main, [], state) 376 | 377 | Code execution starts with the main function. 378 | 379 | .. _function_interpretation: 380 | 381 | Function interpretation 382 | ----------------------- 383 | 384 | .. code-block:: python 385 | 386 | def interp_function(func: Function, args, state): 387 | globals, _, mem = state 388 | vars = {} 389 | new_state = State(globals, vars, mem) 390 | 391 | for param, arg in zip(func.args, args): 392 | vars[param.val] = arg 393 | 394 | if func.has_no_body(): 395 | return interp_external_function(func, args) 396 | 397 | mem.new_frame() 398 | blocks = func.blocks 399 | fst_block = first_value_in_dict(blocks) 400 | label, value = interp_block(fst_block, new_state) 401 | while label is not None: 402 | label, value = interp_block(blocks[label], new_state) 403 | 404 | mem.pop_frame() 405 | return value 406 | 407 | Here a new state is created with an empty vars environment 408 | 409 | then the arguments passed to the function are added to the environment 410 | 411 | Handle the case when the function is not defined, as for example with printf. 412 | 413 | If the function is defined, a new stack frame is created and the execution 414 | of basic blocks starts, starting from the first one (first_value_in_dict), 415 | until one of them returns a value using the Ret instruction. 416 | 417 | After that, the stack frame is deleted and the value is returned. 418 | 419 | .. _block_interpretation: 420 | 421 | Block interpretation 422 | --------------------- 423 | 424 | .. code-block:: python 425 | 426 | def interp_block(block: Block, state: State): 427 | *instrs, tail = block.instrs 428 | for instr in instrs: 429 | interp_instr(instr, state) 430 | return interp_tail(tail, state) 431 | 432 | Since the terminator instruction appears at the end, 433 | the logic for interpreting the basic block is implemented explicitly. 434 | 435 | How to understand when a block has finished using the Ret function, 436 | and when the execution should be continued in another block? 437 | 438 | .. _tail_interpretation: 439 | 440 | Tail interpretation 441 | ------------------- 442 | 443 | .. code-block:: python 444 | 445 | def interp_tail(instr: Instruction, state: State): 446 | match instr: 447 | case Br(None, Value(label)): 448 | return (label, None) 449 | case Br(cond, Value(label_false), Value(label_true)): 450 | cond_val = interp_value(cond, state) 451 | if cond_val: 452 | return (label_true, None) 453 | else: 454 | return (label_false, None) 455 | case Ret(value): 456 | val = interp_value(value, state) 457 | return (None, val) 458 | 459 | .. _instruction_interpretation: 460 | 461 | Instruction interpretation 462 | -------------------------- 463 | 464 | .. code-block:: python 465 | 466 | def interp_instr(instr: Instruction, state: State): 467 | _, vars, mem = state 468 | match instr: 469 | case Alloca(Value(addr)): 470 | vars[addr] = mem.alloca() 471 | case Store(value, Value(addr)): 472 | val = interp_value(value, state) 473 | mem.store(val, vars[addr]) 474 | case Load(Value(res), Value(addr)): 475 | vars[res] = mem.load(vars[addr]) 476 | case ICmp(cond, Value(res), fst, snd): 477 | arg0 = interp_value(fst, state) 478 | arg1 = interp_value(snd, state) 479 | match cond: 480 | case "sgt": 481 | vars[res] = arg0 > arg1 482 | case "eq": 483 | vars[res] = arg0 == arg1 484 | case _: 485 | raise NotImplementedError() 486 | 487 | case BinOp("mul", Value(res), fst, snd): 488 | arg0 = interp_value(fst, state) 489 | arg1 = interp_value(snd, state) 490 | vars[res] = arg0 * arg1 491 | case BinOp("sub", Value(res), fst, snd): 492 | arg0 = interp_value(fst, state) 493 | arg1 = interp_value(snd, state) 494 | vars[res] = arg0 - arg1 495 | case Call(Value(res), Value(func), args): 496 | func = mod.funcs[func] 497 | arg_vals = [interp_value(arg, state) for arg in args] 498 | vars[res] = interp_function(func, arg_vals, state) 499 | case _: 500 | raise NotImplementedError() 501 | 502 | interp_instr is fairly straightforward. 503 | alloca, store and load use the Mem class interface. 504 | Call supports only direct calls and calls interp_function after preparing arguments. 505 | All instructions store the result of execution in the vars environment. 506 | 507 | .. _value_interpretation: 508 | 509 | Value interpretation 510 | -------------------- 511 | 512 | .. code-block:: python 513 | 514 | def interp_value(value: Value, state): 515 | globals, vars, _ = state 516 | match value.val: 517 | case str(name): 518 | if name in vars: 519 | new_value = vars[name] 520 | else: 521 | new_value = globals[name] 522 | match new_value: 523 | case GlobalVariable(_, initializer): 524 | return interp_value(initializer, state) 525 | case _: 526 | return new_value 527 | case int(val): 528 | return val 529 | case bytes(val): 530 | return str(val, "utf-8") 531 | case _: 532 | raise NotImplementedError() 533 | 534 | This function is used to get the computed result from Value. 535 | If Value stores a name, the function first checks the local environment (vars) and then the global environment (globals), after which it can recursively call itself to calculate the global variable. 536 | 537 | You may notice that with this implementation, global variables cannot be changed, but this is not required for the computation of our program. 538 | 539 | .. _summary: 540 | 541 | Summary 542 | ======= 543 | 544 | Thus, we have implemented a factorial interpreter based on the LLVM IR representation. 545 | 546 | Of course, this is not the only usage of the library, but this example is intended to illustrate the general principles of working with the library. 547 | 548 | It is worth noting that this implementation does not rely on the usual Visitor pattern, 549 | but uses the match case construct, which will allow you to write more declarative programs. 550 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/module.h" 2 | #include "../include/parser.h" 3 | #include "../include/tools.h" 4 | #include 5 | 6 | namespace llvm2py { 7 | 8 | struct PythonTypes { 9 | py::object IRPyModule; 10 | py::object ModulePyClass; 11 | py::object FunctionPyClass; 12 | py::object BlockPyClass; 13 | py::object GlobalObjectPyClass; 14 | py::object GlobalVariablePyClass; 15 | py::object ValuePyClass; 16 | 17 | // Types 18 | py::object VoidTypePyClass; 19 | py::object FunctionTypePyClass; 20 | py::object IntegerTypePyClass; 21 | py::object FPTypePyClass; 22 | py::object X86_amxTypePyClass; 23 | py::object PtrTypePyClass; 24 | py::object TargetExtensionTypePyClass; 25 | py::object VectorTypePyClass; 26 | py::object LabelTypePyClass; 27 | py::object TokenTypePyClass; 28 | py::object MetadataTypePyClass; 29 | py::object ArrayTypePyClass; 30 | py::object StructureTypePyClass; 31 | py::object OpaqueTypePyClass; 32 | 33 | // Instruction Factory 34 | py::object createInstructionFactory; 35 | }; 36 | 37 | bool supportsFastMathFlags(unsigned opcode) 38 | { 39 | switch (opcode) 40 | { 41 | case Instruction::FNeg: 42 | case Instruction::FAdd: 43 | case Instruction::FSub: 44 | case Instruction::FMul: 45 | case Instruction::FDiv: 46 | case Instruction::FRem: 47 | case Instruction::FPTrunc: 48 | case Instruction::FPExt: 49 | case Instruction::FCmp: 50 | case Instruction::PHI: 51 | case Instruction::Select: 52 | case Instruction::Call: 53 | return true; 54 | default: 55 | return false; 56 | } 57 | } 58 | 59 | bool supportsNUWandNSW(unsigned opcode) 60 | { 61 | switch (opcode) 62 | { 63 | case Instruction::Add: 64 | case Instruction::Sub: 65 | case Instruction::Mul: 66 | case Instruction::Shl: 67 | case Instruction::Trunc: 68 | return true; 69 | default: 70 | return false; 71 | } 72 | } 73 | 74 | bool supportsExact(unsigned opcode) 75 | { 76 | switch (opcode) 77 | { 78 | case Instruction::UDiv: 79 | case Instruction::SDiv: 80 | case Instruction::LShr: 81 | case Instruction::AShr: 82 | return true; 83 | default: 84 | return false; 85 | } 86 | } 87 | 88 | py::object tailkindToStr(const CallInst::TailCallKind kind) 89 | { 90 | switch (kind) 91 | { 92 | case CallInst::TailCallKind::TCK_None: 93 | return py::none(); 94 | case CallInst::TailCallKind::TCK_MustTail: 95 | return py::str("musttail"); 96 | case CallInst::TailCallKind::TCK_Tail: 97 | return py::str("tail"); 98 | case CallInst::TailCallKind::TCK_NoTail: 99 | return py::str("notail"); 100 | default: 101 | return py::str("New unsupported TailCallKind, report if you see it"); 102 | } 103 | } 104 | 105 | py::object handleType(const Type *type, const PythonTypes &PT) 106 | { 107 | switch (type->getTypeID()) 108 | { 109 | // Simple types 110 | case Type::TypeID::HalfTyID: 111 | return PT.FPTypePyClass(py::str("half")); 112 | case Type::TypeID::BFloatTyID: 113 | return PT.FPTypePyClass(py::str("bfloat")); 114 | case Type::TypeID::FloatTyID: 115 | return PT.FPTypePyClass(py::str("float")); 116 | case Type::TypeID::DoubleTyID: 117 | return PT.FPTypePyClass(py::str("double")); 118 | case Type::TypeID::X86_FP80TyID: 119 | return PT.FPTypePyClass(py::str("x86_fp80")); 120 | case Type::TypeID::FP128TyID: 121 | return PT.FPTypePyClass(py::str("fp128")); 122 | case Type::TypeID::PPC_FP128TyID: 123 | return PT.FPTypePyClass(py::str("ppc_fp128")); 124 | case Type::TypeID::VoidTyID: 125 | return PT.VoidTypePyClass(); 126 | case Type::TypeID::LabelTyID: 127 | return PT.LabelTypePyClass(); 128 | case Type::TypeID::MetadataTyID: 129 | return PT.MetadataTypePyClass(); 130 | case Type::TypeID::X86_AMXTyID: 131 | return PT.X86_amxTypePyClass(); 132 | case Type::TypeID::TokenTyID: 133 | return PT.TokenTypePyClass(); 134 | 135 | // Complex types 136 | case Type::TypeID::IntegerTyID: 137 | { 138 | IntegerType* integerTy = (IntegerType*) type; 139 | return PT.IntegerTypePyClass( 140 | py::int_(integerTy->getBitWidth()) 141 | ); 142 | } 143 | case Type::TypeID::FunctionTyID: 144 | { 145 | FunctionType* functionTy = (FunctionType*) type; 146 | std::vector params; 147 | for (Type* paramTy : functionTy->params()) 148 | { 149 | params.push_back(handleType(paramTy, PT)); 150 | } 151 | return PT.FunctionTypePyClass( 152 | py::list(py::cast(params)), 153 | handleType(functionTy->getReturnType(), PT) 154 | ); 155 | } 156 | case Type::TypeID::PointerTyID: 157 | { 158 | PointerType* ptrTy = (PointerType*) type; 159 | return PT.PtrTypePyClass(py::int_(ptrTy->getAddressSpace()), py::none()); 160 | } 161 | case Type::TypeID::StructTyID: 162 | { 163 | StructType* structTy = (StructType*)(type); 164 | if (structTy->isOpaque()) 165 | { 166 | return PT.OpaqueTypePyClass(); 167 | } 168 | else 169 | { 170 | py::object name; 171 | if (structTy->hasName()) 172 | { 173 | name = py::str(structTy->getName().str()); 174 | } 175 | else 176 | { 177 | name = py::none(); 178 | } 179 | std::vector elemTypes; 180 | for (Type* elemTy : structTy->elements()) 181 | { 182 | elemTypes.push_back(handleType(elemTy, PT)); 183 | } 184 | return PT.StructureTypePyClass( 185 | name, 186 | py::list(py::cast(elemTypes)), 187 | py::bool_(structTy->isPacked()) 188 | ); 189 | } 190 | } 191 | case Type::TypeID::ArrayTyID: 192 | { 193 | ArrayType* arrayTy = (ArrayType*) type; 194 | return PT.ArrayTypePyClass( 195 | py::int_(arrayTy->getNumElements()), 196 | handleType(arrayTy->getElementType(), PT) 197 | ); 198 | } 199 | case Type::TypeID::FixedVectorTyID: 200 | { 201 | FixedVectorType* vectorTy = (FixedVectorType*) type; 202 | return PT.VectorTypePyClass( 203 | py::int_(vectorTy->getNumElements()), 204 | handleType(vectorTy->getElementType(), PT), 205 | py::bool_(false) 206 | ); 207 | } 208 | case Type::TypeID::ScalableVectorTyID: 209 | { 210 | ScalableVectorType* vectorTy = (ScalableVectorType*) type; 211 | return PT.VectorTypePyClass( 212 | py::int_(vectorTy->getMinNumElements()), 213 | handleType(vectorTy->getElementType(), PT), 214 | py::bool_(true) 215 | ); 216 | } 217 | case Type::TypeID::TypedPointerTyID: 218 | { 219 | TypedPointerType* typedPtrTy = (TypedPointerType*) type; 220 | return PT.PtrTypePyClass( 221 | py::int_(typedPtrTy->getAddressSpace()), 222 | handleType(typedPtrTy->getElementType(), PT) 223 | ); 224 | } 225 | case Type::TypeID::TargetExtTyID: 226 | { 227 | TargetExtType* targetExtType = (TargetExtType*) type; 228 | std::vector params; 229 | for (Type* paramTy : targetExtType->type_params()) 230 | { 231 | params.push_back(handleType(paramTy, PT)); 232 | } 233 | 234 | for (unsigned intParam : targetExtType->int_params()) 235 | { 236 | params.push_back(py::int_(intParam)); 237 | } 238 | 239 | return PT.TargetExtensionTypePyClass( 240 | py::str(targetExtType->getName().str()), 241 | py::list(py::cast(params)) 242 | ); 243 | } 244 | default: 245 | { 246 | return py::none(); 247 | } 248 | } 249 | } 250 | 251 | py::object handleGlobalObject(const GlobalObject& globalObject, const PythonTypes& PT) 252 | { 253 | MaybeAlign Align = globalObject.getAlign(); 254 | int alignment = Align ? Align->value() : 0; 255 | return PT.GlobalObjectPyClass( 256 | py::int_((int ) globalObject.getAddressSpace()), 257 | py::int_(alignment), 258 | py::int_((int ) globalObject.getLinkage()), 259 | py::int_((int) globalObject.getUnnamedAddr()), 260 | py::int_((int) globalObject.getVisibility()), 261 | py::int_((int) globalObject.getThreadLocalMode()), 262 | py::str(globalObject.getSection().str()) 263 | ); 264 | } 265 | 266 | py::object handleValue(const Value& value, const PythonTypes& PT) 267 | { 268 | py::object data; 269 | if (auto* constInt = dyn_cast(&value)) 270 | { 271 | data = py::int_(constInt->getSExtValue()); 272 | } 273 | else if (auto* constFP = dyn_cast(&value)) 274 | { 275 | data = py::float_(constFP->getValue().convertToDouble()); 276 | } 277 | else if (dyn_cast(&value)) 278 | { 279 | data = py::int_(0); 280 | } 281 | else if (dyn_cast(&value)) 282 | { 283 | data = py::none(); 284 | } 285 | else if (auto* seqData = dyn_cast(&value)) 286 | { 287 | if (seqData->isString()) 288 | { 289 | std::string string = seqData->getAsString().str(); 290 | data = py::bytes(string); 291 | } 292 | else if (seqData->getElementType()->isIntegerTy()) 293 | { 294 | std::vector elems; 295 | elems.reserve(seqData->getNumElements()); 296 | for (unsigned i = 0; i < seqData->getNumElements(); i++) 297 | { 298 | int64_t elem = seqData->getElementAsAPInt(i).getSExtValue(); 299 | elems.push_back(py::int_(elem)); 300 | } 301 | data = py::list(py::cast(elems)); 302 | } 303 | else if (seqData->getElementType()->isFloatingPointTy()) 304 | { 305 | std::vector elems; 306 | elems.reserve(seqData->getNumElements()); 307 | for (unsigned i = 0; i < seqData->getNumElements(); i++) 308 | { 309 | double elem = seqData->getElementAsDouble(i); 310 | elems.push_back(py::float_(elem)); 311 | } 312 | data = py::list(py::cast(elems)); 313 | } 314 | else 315 | { 316 | std::vector elems; 317 | elems.reserve(seqData->getNumElements()); 318 | for (unsigned i = 0; i < seqData->getNumElements(); i++) 319 | { 320 | Constant* elem = seqData->getElementAsConstant(i); 321 | elems.push_back(handleValue(*elem, PT)); 322 | } 323 | data = py::list(py::cast(elems)); 324 | } 325 | } 326 | else if (auto* constAggregate = dyn_cast(&value)) 327 | { 328 | std::vector elems; 329 | elems.reserve(constAggregate->getNumOperands()); 330 | for (const Use& operand : constAggregate->operands()) 331 | { 332 | Value* elem = operand.get(); 333 | elems.push_back(handleValue(*elem, PT)); 334 | } 335 | data = py::list(py::cast(elems)); 336 | } 337 | else if (auto* blockAddr = dyn_cast(&value)) 338 | { 339 | data = py::make_tuple( 340 | py::str(getNameOrAsOperand(blockAddr->getFunction())), 341 | py::str(getNameOrAsOperand(blockAddr->getBasicBlock())) 342 | ); 343 | } 344 | else if (auto* constExpr = dyn_cast(&value)) 345 | { 346 | Instruction* instr = constExpr->getAsInstruction(); 347 | data = handleInstruction(*instr, PT); 348 | } 349 | else 350 | { 351 | data = py::str(getNameOrAsOperand(value)); 352 | } 353 | 354 | const Type* ty; 355 | if (const GlobalValue* globalValue = dyn_cast(&value)) 356 | { 357 | ty = globalValue->getValueType(); 358 | } 359 | else 360 | { 361 | ty = value.getType(); 362 | } 363 | 364 | return PT.ValuePyClass( 365 | data, 366 | handleType(ty, PT) 367 | ); 368 | } 369 | 370 | py::object handleInstructionSpecific(const Instruction& instruction, const PythonTypes &PT) 371 | { 372 | unsigned opcode = instruction.getOpcode(); 373 | switch (opcode) 374 | { 375 | case Instruction::CallBr: 376 | { 377 | CallBrInst* instr = (CallBrInst*)(&instruction); 378 | return py::make_tuple( 379 | py::int_((int) instr->getCallingConv()), 380 | handleAttributeList(instr->getAttributes(), PT), 381 | py::int_(instr->getNumIndirectDests()) 382 | ); 383 | } 384 | case Instruction::Invoke: 385 | case Instruction::Call: 386 | { 387 | CallBase* instr = (CallBase*)(&instruction); 388 | return py::make_tuple( 389 | py::int_((int) instr->getCallingConv()), 390 | handleAttributeList(instr->getAttributes(), PT) 391 | ); 392 | } 393 | 394 | case Instruction::CatchSwitch: 395 | { 396 | CatchSwitchInst* instr = (CatchSwitchInst*)(&instruction); 397 | return py::make_tuple(py::bool_(instr->hasUnwindDest())); 398 | } 399 | case Instruction::ExtractValue: 400 | { 401 | ExtractValueInst* instr = (ExtractValueInst*)(&instruction); 402 | std::vector arr; 403 | ArrayRef indices = instr->getIndices(); 404 | arr.reserve(indices.size()); 405 | for (unsigned i : indices) 406 | { 407 | arr.push_back(py::int_(i)); 408 | } 409 | return py::make_tuple(py::list(py::cast(arr))); 410 | } 411 | case Instruction::InsertValue: 412 | { 413 | InsertValueInst* instr = (InsertValueInst*)(&instruction); 414 | std::vector arr; 415 | ArrayRef indices = instr->getIndices(); 416 | arr.reserve(indices.size()); 417 | for (unsigned i : indices) 418 | { 419 | arr.push_back(py::int_(i)); 420 | } 421 | return py::make_tuple(py::list(py::cast(arr))); 422 | } 423 | case Instruction::Alloca: 424 | { 425 | AllocaInst* instr = (AllocaInst*)(&instruction); 426 | return py::make_tuple( 427 | handleType(instr->getAllocatedType(), PT) 428 | ); 429 | } 430 | case Instruction::GetElementPtr: 431 | { 432 | GetElementPtrInst* instr = (GetElementPtrInst*)(&instruction); 433 | return py::make_tuple( 434 | handleType(instr->getResultElementType(), PT), 435 | handleType(instr->getSourceElementType(), PT) 436 | ); 437 | } 438 | case Instruction::ICmp: 439 | case Instruction::FCmp: 440 | { 441 | CmpInst* instr = (CmpInst*)(&instruction); 442 | return py::make_tuple( 443 | py::str(instr->getPredicateName(instr->getPredicate()).str()) 444 | ); 445 | } 446 | case Instruction::PHI: 447 | { 448 | PHINode* instr = (PHINode*)(&instruction); 449 | std::vector incomingBlocks; 450 | for (const BasicBlock* block : instr->blocks()) 451 | { 452 | incomingBlocks.push_back(handleValue(*block, PT)); 453 | } 454 | return py::make_tuple(py::list(py::cast(incomingBlocks))); 455 | } 456 | case Instruction::LandingPad: 457 | { 458 | LandingPadInst* instr = (LandingPadInst*)(&instruction); 459 | std::vector arr; 460 | unsigned numClauses = instr->getNumClauses(); 461 | arr.reserve(numClauses); 462 | for (unsigned i = 0; i < numClauses; i++) 463 | { 464 | arr.push_back(py::bool_(instr->isCatch(i))); 465 | } 466 | return py::make_tuple( 467 | py::bool_(instr->isCleanup()), 468 | py::list(py::cast(arr)) 469 | ); 470 | } 471 | case Instruction::AtomicRMW: 472 | { 473 | AtomicRMWInst* instr = (AtomicRMWInst*)(&instruction); 474 | return py::make_tuple(py::str(instr->getOperationName(instr->getOperation()).str())); 475 | } 476 | case Instruction::ShuffleVector: 477 | { 478 | ShuffleVectorInst* instr = (ShuffleVectorInst*)(&instruction); 479 | ArrayRef mask = instr->getShuffleMask(); 480 | std::vector arr; 481 | arr.reserve(mask.size()); 482 | for (int i : mask) 483 | { 484 | arr.push_back(py::int_(i)); 485 | } 486 | return py::make_tuple(py::list(py::cast(arr))); 487 | } 488 | case Instruction::AtomicCmpXchg: 489 | { 490 | AtomicCmpXchgInst* instr = (AtomicCmpXchgInst*)(&instruction); 491 | return py::make_tuple( 492 | py::int_((int)instr->getSuccessOrdering()), 493 | py::int_((int)instr->getFailureOrdering()) 494 | ); 495 | } 496 | default: 497 | return py::none(); 498 | } 499 | } 500 | 501 | void extractFastMathFlags(const Instruction &instruction, std::vector* flags) 502 | { 503 | if (!supportsFastMathFlags(instruction.getOpcode())) return; 504 | 505 | const FastMathFlags fastFlags = instruction.getFastMathFlags(); 506 | if (fastFlags.allowReassoc()) flags->push_back(py::str("reassoc")); 507 | if (fastFlags.noNaNs()) flags->push_back(py::str("nnan")); 508 | if (fastFlags.noInfs()) flags->push_back(py::str("ninf")); 509 | if (fastFlags.noSignedZeros()) flags->push_back(py::str("nsz")); 510 | if (fastFlags.allowReciprocal()) flags->push_back(py::str("arcp")); 511 | if (fastFlags.allowContract()) flags->push_back(py::str("contract")); 512 | if (fastFlags.approxFunc()) flags->push_back(py::str("afn")); 513 | } 514 | 515 | py::object extractInstructionFlags(const Instruction &instruction) 516 | { 517 | std::vector flags; 518 | unsigned opcode = instruction.getOpcode(); 519 | 520 | // fast-math flags 521 | if (supportsFastMathFlags(opcode)) 522 | { 523 | std::vector fastMathFlags; 524 | extractFastMathFlags(instruction, &fastMathFlags); 525 | flags.push_back(py::make_tuple(py::str("fmf"), fastMathFlags)); 526 | } 527 | 528 | // nuw or nsw 529 | if (supportsNUWandNSW(opcode)) 530 | { 531 | if (instruction.hasNoUnsignedWrap()) flags.push_back(py::make_tuple(py::str("nuw"))); 532 | if (instruction.hasNoSignedWrap()) flags.push_back(py::make_tuple(py::str("nsw"))); 533 | } 534 | 535 | // exact 536 | if (supportsExact(opcode) && instruction.isExact()) flags.push_back(py::make_tuple(py::str("exact"))); 537 | 538 | if (auto *instr = dyn_cast(&instruction)) 539 | { 540 | // volatile 541 | if (instr->isVolatile()) flags.push_back(py::make_tuple(py::str("volatile"))); 542 | // align 543 | int align = instr->getAlign().value(); 544 | if (align != 0) flags.push_back(py::make_tuple(py::str("align"), py::int_(align))); 545 | // syncscope 546 | SyncScope::ID scopeID = instr->getSyncScopeID(); 547 | flags.push_back(py::make_tuple(py::str("syncscope"), py::int_((int)scopeID))); 548 | // ordering 549 | AtomicOrdering ordering = instr->getOrdering(); 550 | flags.push_back(py::make_tuple(py::str("ordering"), py::int_((int)ordering))); 551 | // atomic 552 | if (instr->isAtomic()) flags.push_back(py::make_tuple(py::str("atomic"))); 553 | } 554 | else if (auto *instr = dyn_cast(&instruction)) 555 | { 556 | // volatile 557 | if (instr->isVolatile()) flags.push_back(py::make_tuple(py::str("volatile"))); 558 | // align 559 | int align = instr->getAlign().value(); 560 | if (align != 0) flags.push_back(py::make_tuple(py::str("align"), py::int_(align))); 561 | // syncscope 562 | SyncScope::ID scopeID = instr->getSyncScopeID(); 563 | flags.push_back(py::make_tuple(py::str("syncscope"), py::int_((int)scopeID))); 564 | // ordering 565 | AtomicOrdering ordering = instr->getOrdering(); 566 | flags.push_back(py::make_tuple(py::str("ordering"), py::int_((int)ordering))); 567 | // atomic 568 | if (instr->isAtomic()) flags.push_back(py::make_tuple(py::str("atomic"))); 569 | } 570 | else if (auto *instr = dyn_cast(&instruction)) 571 | { 572 | // volatile 573 | if (instr->isVolatile()) flags.push_back(py::make_tuple(py::str("volatile"))); 574 | // align 575 | int align = instr->getAlign().value(); 576 | if (align != 0) flags.push_back(py::make_tuple(py::str("align"), py::int_(align))); 577 | // syncscope 578 | SyncScope::ID scopeID = instr->getSyncScopeID(); 579 | flags.push_back(py::make_tuple(py::str("syncscope"), py::int_((int)scopeID))); 580 | // weak 581 | if (instr->isWeak()) flags.push_back(py::make_tuple(py::str("weak"))); 582 | } 583 | else if (auto *instr = dyn_cast(&instruction)) 584 | { 585 | // volatile 586 | if (instr->isVolatile()) flags.push_back(py::make_tuple(py::str("volatile"))); 587 | // align 588 | int align = instr->getAlign().value(); 589 | if (align != 0) flags.push_back(py::make_tuple(py::str("align"), py::int_(align))); 590 | // syncscope 591 | SyncScope::ID scopeID = instr->getSyncScopeID(); 592 | flags.push_back(py::make_tuple(py::str("syncscope"), py::int_((int)scopeID))); 593 | // ordering 594 | AtomicOrdering ordering = instr->getOrdering(); 595 | flags.push_back(py::make_tuple(py::str("ordering"), py::int_((int)ordering))); 596 | } 597 | else if (auto *instr = dyn_cast(&instruction)) 598 | { 599 | // syncscope 600 | SyncScope::ID scopeID = instr->getSyncScopeID(); 601 | flags.push_back(py::make_tuple(py::str("syncscope"), py::int_((int)scopeID))); 602 | // ordering 603 | AtomicOrdering ordering = instr->getOrdering(); 604 | flags.push_back(py::make_tuple(py::str("ordering"), py::int_((int)ordering))); 605 | } 606 | else if (auto* instr = dyn_cast(&instruction)) 607 | { 608 | // disjoint 609 | if (instr->isDisjoint()) flags.push_back(py::make_tuple(py::str("disjoint"))); 610 | } 611 | else if (auto* instr = dyn_cast(&instruction)) 612 | { 613 | // inbounds 614 | if (instr->isInBounds()) flags.push_back(py::make_tuple(py::str("inbounds"))); 615 | } 616 | else if (auto* instr = dyn_cast(&instruction)) 617 | { 618 | // tailcall kind 619 | CallInst::TailCallKind kind = instr->getTailCallKind(); 620 | flags.push_back(py::make_tuple(py::str("tailkind"), tailkindToStr(kind))); 621 | } 622 | else if (auto* instr = dyn_cast(&instruction)) 623 | { 624 | // align 625 | int align = instr->getAlign().value(); 626 | if (align != 0) flags.push_back(py::make_tuple(py::str("align"), py::int_(align))); 627 | 628 | // inalloca 629 | if (instr->isUsedWithInAlloca()) flags.push_back(py::make_tuple(py::str("inalloca"))); 630 | } 631 | return py::list(py::cast(flags)); 632 | } 633 | 634 | py::object handleAttributeSet(const AttributeSet &attrs, const PythonTypes &PT) 635 | { 636 | std::vector localAttrs; 637 | localAttrs.reserve(attrs.getNumAttributes()); 638 | for (const Attribute attr : attrs) 639 | { 640 | if (!attr.isValid()) 641 | { 642 | continue; 643 | } 644 | Attribute::AttrKind kind = attr.getKindAsEnum(); 645 | if (kind == Attribute::AttrKind::VScaleRange) 646 | { 647 | unsigned min = attr.getVScaleRangeMin(); 648 | std::optional max = attr.getVScaleRangeMax(); 649 | if (max.has_value()) 650 | { 651 | localAttrs.push_back(py::make_tuple( 652 | py::str(attr.getNameFromAttrKind(kind).str()), 653 | py::int_(min), 654 | py::int_(max.value()) 655 | )); 656 | } 657 | else 658 | { 659 | localAttrs.push_back(py::make_tuple( 660 | py::str(attr.getNameFromAttrKind(kind).str()), 661 | py::int_(min) 662 | )); 663 | } 664 | } 665 | else if (Attribute::isEnumAttrKind(kind)) 666 | { 667 | localAttrs.push_back(py::make_tuple( 668 | py::str(attr.getNameFromAttrKind(kind).str()) 669 | )); 670 | } 671 | else if (Attribute::isIntAttrKind(kind)) 672 | { 673 | localAttrs.push_back(py::make_tuple( 674 | py::str(attr.getNameFromAttrKind(kind).str()), 675 | py::int_(attr.getValueAsInt()) 676 | )); 677 | } 678 | else if (Attribute::isTypeAttrKind(kind)) 679 | { 680 | localAttrs.push_back(py::make_tuple( 681 | py::str(attr.getNameFromAttrKind(kind).str()), 682 | handleType(attr.getValueAsType(), PT) 683 | )); 684 | } 685 | else 686 | { 687 | localAttrs.push_back(py::make_tuple( 688 | py::str(attr.getNameFromAttrKind(kind).str()), 689 | py::str(attr.getValueAsString().str()) 690 | )); 691 | } 692 | } 693 | return py::list(py::cast(localAttrs)); 694 | } 695 | 696 | py::object handleAttributeList(AttributeList attributes, const PythonTypes &PT) 697 | { 698 | std::vector allAttrs; 699 | for (const AttributeSet attrs : attributes) 700 | { 701 | py::object localAttrs = handleAttributeSet(attrs, PT); 702 | allAttrs.push_back(localAttrs); 703 | } 704 | return py::list(py::cast(allAttrs)); 705 | } 706 | 707 | py::object handleInstruction(const Instruction &instruction, const PythonTypes &PT) 708 | { 709 | std::vector operands; 710 | 711 | py::object additional = handleInstructionSpecific(instruction, PT); 712 | 713 | for (const Use& operand : instruction.operands()) 714 | { 715 | py::object operandObject = handleValue(*operand.get(), PT); 716 | operands.push_back(operandObject); 717 | } 718 | 719 | py::object flags = extractInstructionFlags(instruction); 720 | 721 | return PT.createInstructionFactory( 722 | py::int_(instruction.getOpcode()), 723 | py::str(instruction.getOpcodeName()), 724 | py::list(py::cast(operands)), 725 | additional, 726 | flags, 727 | handleValue(instruction, PT) 728 | ); 729 | } 730 | 731 | py::object handleBlock(const BasicBlock &block, const PythonTypes &PT) 732 | { 733 | std::vector instructions; 734 | std::vector predBlocks; 735 | 736 | instructions.reserve(block.size()); 737 | 738 | for (const BasicBlock* basicBlock : predecessors(&block)) 739 | { 740 | predBlocks.push_back(py::str(getNameOrAsOperand(basicBlock))); 741 | } 742 | 743 | for (const Instruction &instruction: block) { 744 | py::object instructionObject = handleInstruction(instruction, PT); 745 | instructions.push_back(instructionObject); 746 | } 747 | 748 | py::object blockObject = PT.BlockPyClass( 749 | handleValue(block, PT), 750 | py::list(py::cast(instructions)), 751 | py::list(py::cast(predBlocks)) 752 | ); 753 | return blockObject; 754 | } 755 | 756 | py::object handleGlobalVariable(const GlobalVariable& var, const PythonTypes &PT) 757 | { 758 | py::object attributes = handleAttributeSet(var.getAttributes(), PT); 759 | 760 | py::object initializer; 761 | if (var.hasInitializer()) 762 | { 763 | initializer = handleValue(*var.getInitializer(), PT); 764 | } 765 | else 766 | { 767 | initializer = py::none(); 768 | } 769 | 770 | return PT.GlobalVariablePyClass( 771 | handleValue(var, PT), 772 | initializer, 773 | py::bool_(var.isConstant()), 774 | attributes, 775 | handleGlobalObject(var, PT), 776 | py::bool_(var.isExternallyInitialized()) 777 | ); 778 | } 779 | 780 | py::object handleFunction(const Function &function, const PythonTypes &PT) 781 | { 782 | std::vector args; 783 | 784 | for (const Argument& arg: function.args()) 785 | { 786 | py::object argumentObject = handleValue(arg, PT); 787 | args.push_back(argumentObject); 788 | } 789 | 790 | std::vector blocks; 791 | 792 | for (const BasicBlock &block: function) 793 | { 794 | blocks.push_back(handleBlock(block, PT)); 795 | } 796 | 797 | py::object attributes = handleAttributeList(function.getAttributes(), PT); 798 | 799 | py::object functionObject = PT.FunctionPyClass( 800 | handleValue(function, PT), 801 | py::list(py::cast(args)), 802 | py::list(py::cast(blocks)), 803 | attributes, 804 | py::int_((int )function.getCallingConv()), 805 | py::bool_(function.isVarArg()), 806 | handleGlobalObject(function, PT) 807 | ); 808 | return functionObject; 809 | } 810 | 811 | py::object handleModule(Module *module, const PythonTypes &PT) 812 | { 813 | std::vector functions; 814 | functions.reserve(module->size()); 815 | 816 | std::vector globalVariables; 817 | globalVariables.reserve(module->global_size()); 818 | 819 | for (const GlobalVariable& var : module->globals()) 820 | { 821 | py::object varObject = handleGlobalVariable(var, PT); 822 | globalVariables.push_back(varObject); 823 | } 824 | 825 | for (const Function &function: *module) 826 | { 827 | functions.push_back(handleFunction(function, PT)); 828 | } 829 | 830 | py::object moduleObject = PT.ModulePyClass( 831 | py::list(py::cast(functions)), 832 | py::list(py::cast(globalVariables)) 833 | ); 834 | return moduleObject; 835 | } 836 | 837 | py::object createModule(const std::string &IR) 838 | { 839 | py::object IRPyModule = py::module_::import("llvm2py.ir"); 840 | PythonTypes PT = PythonTypes{ 841 | IRPyModule, 842 | IRPyModule.attr("Module"), 843 | IRPyModule.attr("Function"), 844 | IRPyModule.attr("Block"), 845 | IRPyModule.attr("GlobalObject"), 846 | IRPyModule.attr("GlobalVariable"), 847 | IRPyModule.attr("Value"), 848 | // types 849 | IRPyModule.attr("VoidType"), 850 | IRPyModule.attr("FunctionType"), 851 | IRPyModule.attr("IntegerType"), 852 | IRPyModule.attr("FPType"), 853 | IRPyModule.attr("X86_amxType"), 854 | IRPyModule.attr("PtrType"), 855 | IRPyModule.attr("TargetExtensionType"), 856 | IRPyModule.attr("VectorType"), 857 | IRPyModule.attr("LabelType"), 858 | IRPyModule.attr("TokenType"), 859 | IRPyModule.attr("MetadataType"), 860 | IRPyModule.attr("ArrayType"), 861 | IRPyModule.attr("StructureType"), 862 | IRPyModule.attr("OpaqueType"), 863 | 864 | // Instruction Factory 865 | IRPyModule.attr("_create_instruction"), 866 | }; 867 | 868 | std::unique_ptr module = parse_module(IR); 869 | py::object result = handleModule(module.get(), PT); 870 | return result; 871 | } 872 | } 873 | -------------------------------------------------------------------------------- /tests/test_lib.py: -------------------------------------------------------------------------------- 1 | import llvm2py 2 | import re 3 | 4 | src = """ 5 | ; RUN: llvm-as < %s | llvm-dis > %t.orig 6 | ; RUN: llvm-as < %s | llvm-c-test --echo > %t.echo 7 | ; RUN: diff -w %t.orig %t.echo 8 | 9 | source_filename = "/test/Bindings/echo.ll" 10 | target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 11 | target triple = "x86_64-apple-macosx10.11.0" 12 | 13 | module asm "classical GAS" 14 | 15 | %S = type { i64, %S* } 16 | 17 | @var = global i32 42 18 | @ext = external global i32* 19 | @cst = constant %S { i64 1, %S* @cst } 20 | @tl = thread_local global { i64, %S* } { i64 1, %S* @cst } 21 | @arr = linkonce_odr global [5 x i8] [ i8 2, i8 3, i8 5, i8 7, i8 11 ] 22 | @str = private unnamed_addr constant [13 x i8] c"hello world\\0A\\00" 23 | @locStr = private local_unnamed_addr constant [13 x i8] c"hello world\\0A\\00" 24 | @hidden = hidden global i32 7 25 | @protected = protected global i32 23 26 | @section = global i32 27, section ".custom" 27 | @align = global i32 31, align 4 28 | @nullptr = global i32* null 29 | 30 | @const_gep = global i32* getelementptr (i32, i32* @var, i64 2) 31 | @const_inbounds_gep = global i32* getelementptr inbounds (i32, i32* @var, i64 1) 32 | 33 | @aliased1 = alias i32, i32* @var 34 | @aliased2 = internal alias i32, i32* @var 35 | @aliased3 = external alias i32, i32* @var 36 | @aliased4 = weak alias i32, i32* @var 37 | @aliased5 = weak_odr alias i32, i32* @var 38 | 39 | @ifunc = ifunc i32 (i32), i32 (i32)* ()* @ifunc_resolver 40 | 41 | define i32 (i32)* @ifunc_resolver() { 42 | entry: 43 | ret i32 (i32)* null 44 | } 45 | 46 | define { i64, %S* } @unpackrepack(%S %s) { 47 | %1 = extractvalue %S %s, 0 48 | %2 = extractvalue %S %s, 1 49 | %3 = insertvalue { i64, %S* } undef, %S* %2, 1 50 | %4 = insertvalue { i64, %S* } %3, i64 %1, 0 51 | ret { i64, %S* } %4 52 | } 53 | 54 | declare void @decl() 55 | 56 | ; TODO: label and metadata types 57 | define void @types() { 58 | %1 = alloca half, align 2 59 | %2 = alloca float, align 4 60 | %3 = alloca double, align 8 61 | %4 = alloca x86_fp80, align 16 62 | %5 = alloca fp128, align 16 63 | %6 = alloca ppc_fp128, align 16 64 | %7 = alloca inalloca i7, align 1 65 | %8 = alloca void (i1)*, align 8 66 | %9 = alloca [3 x i22], align 4 67 | %10 = alloca i328 addrspace(5)*, align 8 68 | %11 = alloca <5 x i23*>, align 64 69 | ret void 70 | } 71 | 72 | define double @fops(double %a, double %b) { 73 | %1 = fneg nnan double %a 74 | %2 = fadd ninf double %a, %b 75 | %3 = fsub nsz double %a, %b 76 | %4 = fmul arcp double %a, %b 77 | %5 = fdiv contract afn reassoc double %a, %b 78 | %6 = frem contract afn reassoc double %a, %b 79 | ret double %6 80 | } 81 | 82 | define i32 @iops(i32 %a, i32 %b) { 83 | %1 = add i32 %a, %b 84 | %2 = mul nsw i32 %a, %1 85 | %3 = sub nuw nsw i32 %2, %1 86 | %4 = udiv exact i32 %3, %b 87 | %5 = sdiv i32 %2, %4 88 | %6 = urem i32 %3, %5 89 | %7 = srem i32 %2, %6 90 | %8 = shl nuw i32 %1, %b 91 | %9 = lshr exact i32 %a, %7 92 | %10 = ashr i32 %b, %8 93 | %11 = and i32 %9, %10 94 | %12 = or disjoint i32 %2, %11 95 | %13 = xor i32 %12, %4 96 | ret i32 %13 97 | } 98 | 99 | define i32 @call() { 100 | %1 = call i32 @iops(i32 23, i32 19) 101 | %2 = call fast double @fops(double immarg 3.0, double 71.0) 102 | ret i32 %1 103 | } 104 | 105 | define i32 @cond(i32 %a, i32 %b) { 106 | br label %br 107 | unreachable: 108 | unreachable 109 | br: 110 | %1 = icmp eq i32 %a, %b 111 | br i1 %1, label %next0, label %unreachable 112 | next0: 113 | %2 = icmp ne i32 %a, %b 114 | br i1 %2, label %next1, label %unreachable 115 | next1: 116 | %3 = icmp ugt i32 %a, %b 117 | br i1 %3, label %next2, label %unreachable 118 | next2: 119 | %4 = icmp uge i32 %a, %b 120 | br i1 %4, label %next3, label %unreachable 121 | next3: 122 | %5 = icmp ult i32 %a, %b 123 | br i1 %5, label %next4, label %unreachable 124 | next4: 125 | %6 = icmp ule i32 %a, %b 126 | br i1 %6, label %next5, label %unreachable 127 | next5: 128 | %7 = icmp sgt i32 %a, %b 129 | br i1 %7, label %next6, label %unreachable 130 | next6: 131 | %8 = icmp sge i32 %a, %b 132 | br i1 %8, label %next7, label %unreachable 133 | next7: 134 | %9 = icmp slt i32 %a, %b 135 | br i1 %9, label %next8, label %unreachable 136 | next8: 137 | %10 = fcmp fast olt double 1.0, 2.0 138 | br i1 %10, label %next9, label %unreachable 139 | next9: 140 | ret i32 0 141 | } 142 | 143 | define i32 @loop(i32 %i) { 144 | br label %cond 145 | cond: 146 | %c = phi i32 [ %i, %0 ], [ %j, %do ] 147 | %p = phi i32 [ %r, %do ], [ 789, %0 ] 148 | %1 = icmp eq i32 %c, 0 149 | br i1 %1, label %do, label %done 150 | do: 151 | %2 = sub i32 %p, 23 152 | %j = sub i32 %i, 1 153 | %r = mul i32 %2, 3 154 | br label %cond 155 | done: 156 | ret i32 %p 157 | } 158 | 159 | define void @memops(i8* %ptr) { 160 | %a = load i8, i8* %ptr 161 | %b = load volatile i8, i8* %ptr 162 | %c = load i8, i8* %ptr, align 8 163 | %d = load atomic i8, i8* %ptr acquire, align 32 164 | store i8 0, i8* %ptr 165 | store volatile i8 0, i8* %ptr 166 | store i8 0, i8* %ptr, align 8 167 | store atomic i8 0, i8* %ptr release, align 32 168 | %e = atomicrmw add i8* %ptr, i8 0 monotonic, align 1 169 | %f = atomicrmw volatile xchg i8* %ptr, i8 0 acq_rel, align 8 170 | %g = cmpxchg i8* %ptr, i8 1, i8 2 seq_cst acquire, align 1 171 | %h = cmpxchg weak i8* %ptr, i8 1, i8 2 seq_cst acquire, align 8 172 | %i = cmpxchg volatile i8* %ptr, i8 1, i8 2 monotonic monotonic, align 16 173 | ret void 174 | } 175 | 176 | define i32 @vectorops(i32, i32) { 177 | %a = insertelement <4 x i32> undef, i32 %0, i32 0 178 | %b = insertelement <4 x i32> %a, i32 %1, i32 2 179 | %c = shufflevector <4 x i32> %b, <4 x i32> undef, <4 x i32> zeroinitializer 180 | %d = shufflevector <4 x i32> %c, <4 x i32> %b, <4 x i32> 181 | %e = add <4 x i32> %d, %a 182 | %f = mul <4 x i32> %e, %b 183 | %g = xor <4 x i32> %f, %d 184 | %h = or <4 x i32> %f, %e 185 | %i = lshr <4 x i32> %h, 186 | %j = shl <4 x i32> %i, 187 | %k = shufflevector <4 x i32> %j, <4 x i32> %i, <4 x i32> 188 | %m = shufflevector <4 x i32> %k, <4 x i32> undef, <1 x i32> 189 | %n = shufflevector <4 x i32> %j, <4 x i32> undef, <8 x i32> 190 | %p = extractelement <8 x i32> %n, i32 5 191 | ret i32 %p 192 | } 193 | 194 | define i32 @scalablevectorops(i32, ) { 195 | %a = insertelement undef, i32 %0, i32 0 196 | %b = insertelement %a, i32 %0, i32 2 197 | %c = shufflevector %b, undef, zeroinitializer 198 | %e = add %a, %1 199 | %f = mul %e, %b 200 | %g = xor %f, %e 201 | %h = or %g, %e 202 | %i = lshr %h, undef 203 | %j = extractelement %i, i32 3 204 | ret i32 %j 205 | } 206 | 207 | declare void @personalityFn() 208 | 209 | define void @exn() personality void ()* @personalityFn { 210 | entry: 211 | invoke void @decl() 212 | to label %via.cleanup unwind label %exn.dispatch 213 | via.cleanup: 214 | invoke void @decl() 215 | to label %via.catchswitch unwind label %cleanup.inner 216 | cleanup.inner: 217 | %cp.inner = cleanuppad within none [] 218 | cleanupret from %cp.inner unwind label %exn.dispatch 219 | via.catchswitch: 220 | invoke void @decl() 221 | to label %exit unwind label %dispatch.inner 222 | dispatch.inner: 223 | %cs.inner = catchswitch within none [label %pad.inner] unwind label %exn.dispatch 224 | pad.inner: 225 | %catch.inner = catchpad within %cs.inner [i32 0] 226 | catchret from %catch.inner to label %exit 227 | exn.dispatch: 228 | %cs = catchswitch within none [label %pad1, label %pad2] unwind label %cleanup 229 | pad1: 230 | catchpad within %cs [i32 1] 231 | unreachable 232 | pad2: 233 | catchpad within %cs [i32 2] 234 | unreachable 235 | cleanup: 236 | %cp = cleanuppad within none [] 237 | cleanupret from %cp unwind to caller 238 | exit: 239 | ret void 240 | } 241 | 242 | declare i8* @llvm.stacksave() 243 | declare void @llvm.stackrestore(i8*) 244 | declare void @llvm.lifetime.start.p0i8(i64, i8*) 245 | declare void @llvm.lifetime.end.p0i8(i64, i8*) 246 | 247 | define void @test_intrinsics() { 248 | entry: 249 | %sp = call i8* @llvm.stacksave() 250 | %x = alloca i32, align 4 251 | %0 = bitcast i32* %x to i8* 252 | call void @llvm.lifetime.start.p0i8(i64 4, i8* %0) 253 | call void @llvm.lifetime.end.p0i8(i64 4, i8* %0) 254 | call void @llvm.stackrestore(i8* %sp) 255 | ret void 256 | } 257 | """ 258 | 259 | reference = """Module(funcs={'ifunc_resolver': Function(value=Value(val='ifunc_resolver', ty=FunctionType(param_tys=[], ret_ty=PtrType(addr_space=0, ty=None))), args=[], blocks={'entry': Block(value=Value(val='entry', ty=LabelType()), instrs=[Ret(value=Value(val=None, ty=PtrType(addr_space=0, ty=None)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'unpackrepack': Function(value=Value(val='unpackrepack', ty=FunctionType(param_tys=[StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)], ret_ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False))), args=[Value(val='s', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[ExtractValue(result=Value(val='%1', ty=IntegerType(num_bits=64)), aggregate=Value(val='s', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), indices=[0]), ExtractValue(result=Value(val='%2', ty=PtrType(addr_space=0, ty=None)), aggregate=Value(val='s', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), indices=[1]), InsertValue(result=Value(val='%3', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), aggregate=Value(val='undef', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), elem=Value(val='%2', ty=PtrType(addr_space=0, ty=None)), indices=[1]), InsertValue(result=Value(val='%4', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), aggregate=Value(val='%3', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), elem=Value(val='%1', ty=IntegerType(num_bits=64)), indices=[0]), Ret(value=Value(val='%4', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'decl': Function(value=Value(val='decl', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], blocks={}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'types': Function(value=Value(val='types', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[Alloca(result=Value(val='%1', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='half'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=2, is_inalloca=False), Alloca(result=Value(val='%2', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='float'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=4, is_inalloca=False), Alloca(result=Value(val='%3', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='double'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=8, is_inalloca=False), Alloca(result=Value(val='%4', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='x86_fp80'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=16, is_inalloca=False), Alloca(result=Value(val='%5', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='fp128'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=16, is_inalloca=False), Alloca(result=Value(val='%6', ty=PtrType(addr_space=0, ty=None)), allocated_ty=FPType(kind='ppc_fp128'), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=16, is_inalloca=False), Alloca(result=Value(val='%7', ty=PtrType(addr_space=0, ty=None)), allocated_ty=IntegerType(num_bits=7), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=1, is_inalloca=True), Alloca(result=Value(val='%8', ty=PtrType(addr_space=0, ty=None)), allocated_ty=PtrType(addr_space=0, ty=None), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=8, is_inalloca=False), Alloca(result=Value(val='%9', ty=PtrType(addr_space=0, ty=None)), allocated_ty=ArrayType(elem_count=3, elem_ty=IntegerType(num_bits=22)), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=4, is_inalloca=False), Alloca(result=Value(val='%10', ty=PtrType(addr_space=0, ty=None)), allocated_ty=PtrType(addr_space=5, ty=None), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=8, is_inalloca=False), Alloca(result=Value(val='%11', ty=PtrType(addr_space=0, ty=None)), allocated_ty=VectorType(elem_count=5, elem_ty=PtrType(addr_space=0, ty=None), is_scalable=False), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=64, is_inalloca=False), Ret(value=None)], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'fops': Function(value=Value(val='fops', ty=FunctionType(param_tys=[FPType(kind='double'), FPType(kind='double')], ret_ty=FPType(kind='double'))), args=[Value(val='a', ty=FPType(kind='double')), Value(val='b', ty=FPType(kind='double'))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[UnaryOp(opcode='fneg', result=Value(val='%1', ty=FPType(kind='double')), operand=Value(val='a', ty=FPType(kind='double')), fast_math_flags=frozenset), BinOp(opcode='fadd', result=Value(val='%2', ty=FPType(kind='double')), fst_operand=Value(val='a', ty=FPType(kind='double')), snd_operand=Value(val='b', ty=FPType(kind='double')), fast_math_flags=frozenset, is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='fsub', result=Value(val='%3', ty=FPType(kind='double')), fst_operand=Value(val='a', ty=FPType(kind='double')), snd_operand=Value(val='b', ty=FPType(kind='double')), fast_math_flags=frozenset, is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='fmul', result=Value(val='%4', ty=FPType(kind='double')), fst_operand=Value(val='a', ty=FPType(kind='double')), snd_operand=Value(val='b', ty=FPType(kind='double')), fast_math_flags=frozenset, is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='fdiv', result=Value(val='%5', ty=FPType(kind='double')), fst_operand=Value(val='a', ty=FPType(kind='double')), snd_operand=Value(val='b', ty=FPType(kind='double')), fast_math_flags=frozenset, is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='frem', result=Value(val='%6', ty=FPType(kind='double')), fst_operand=Value(val='a', ty=FPType(kind='double')), snd_operand=Value(val='b', ty=FPType(kind='double')), fast_math_flags=frozenset, is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), Ret(value=Value(val='%6', ty=FPType(kind='double')))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'iops': Function(value=Value(val='iops', ty=FunctionType(param_tys=[IntegerType(num_bits=32), IntegerType(num_bits=32)], ret_ty=IntegerType(num_bits=32))), args=[Value(val='a', ty=IntegerType(num_bits=32)), Value(val='b', ty=IntegerType(num_bits=32))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[BinOp(opcode='add', result=Value(val='%1', ty=IntegerType(num_bits=32)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='mul', result=Value(val='%2', ty=IntegerType(num_bits=32)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%1', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=True, is_exact=False, is_disjoint=False), BinOp(opcode='sub', result=Value(val='%3', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%2', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%1', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=True, is_nsw=True, is_exact=False, is_disjoint=False), BinOp(opcode='udiv', result=Value(val='%4', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%3', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=True, is_disjoint=False), BinOp(opcode='sdiv', result=Value(val='%5', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%2', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%4', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='urem', result=Value(val='%6', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%3', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%5', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='srem', result=Value(val='%7', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%2', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%6', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='shl', result=Value(val='%8', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%1', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=True, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='lshr', result=Value(val='%9', ty=IntegerType(num_bits=32)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%7', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=True, is_disjoint=False), BinOp(opcode='ashr', result=Value(val='%10', ty=IntegerType(num_bits=32)), fst_operand=Value(val='b', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%8', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='and', result=Value(val='%11', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%9', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%10', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='or', result=Value(val='%12', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%2', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%11', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=True), BinOp(opcode='xor', result=Value(val='%13', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%12', ty=IntegerType(num_bits=32)), snd_operand=Value(val='%4', ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), Ret(value=Value(val='%13', ty=IntegerType(num_bits=32)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'call': Function(value=Value(val='call', ty=FunctionType(param_tys=[], ret_ty=IntegerType(num_bits=32))), args=[], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[Call(result=Value(val='%1', ty=IntegerType(num_bits=32)), callee=Value(val='iops', ty=FunctionType(param_tys=[IntegerType(num_bits=32), IntegerType(num_bits=32)], ret_ty=IntegerType(num_bits=32))), args=[Value(val=23, ty=IntegerType(num_bits=32)), Value(val=19, ty=IntegerType(num_bits=32))], calling_conv=, call_attrs=[], tail_kind=None, fast_math_flags=frozenset()), Call(result=Value(val='%2', ty=FPType(kind='double')), callee=Value(val='fops', ty=FunctionType(param_tys=[FPType(kind='double'), FPType(kind='double')], ret_ty=FPType(kind='double'))), args=[Value(val=3.0, ty=FPType(kind='double')), Value(val=71.0, ty=FPType(kind='double'))], calling_conv=, call_attrs=[{}, {}, {'immarg': ()}], tail_kind=None, fast_math_flags=frozenset), Ret(value=Value(val='%1', ty=IntegerType(num_bits=32)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'cond': Function(value=Value(val='cond', ty=FunctionType(param_tys=[IntegerType(num_bits=32), IntegerType(num_bits=32)], ret_ty=IntegerType(num_bits=32))), args=[Value(val='a', ty=IntegerType(num_bits=32)), Value(val='b', ty=IntegerType(num_bits=32))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[Br(cond=None, label_false=Value(val='br', ty=LabelType()), label_true=None)], pred_blocks=[]), 'unreachable': Block(value=Value(val='unreachable', ty=LabelType()), instrs=[Unreacheble], pred_blocks=['next8', 'next7', 'next6', 'next5', 'next4', 'next3', 'next2', 'next1', 'next0', 'br']), 'br': Block(value=Value(val='br', ty=LabelType()), instrs=[ICmp(cond='eq', result=Value(val='%1', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%1', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next0', ty=LabelType()))], pred_blocks=['%0']), 'next0': Block(value=Value(val='next0', ty=LabelType()), instrs=[ICmp(cond='ne', result=Value(val='%2', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%2', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next1', ty=LabelType()))], pred_blocks=['br']), 'next1': Block(value=Value(val='next1', ty=LabelType()), instrs=[ICmp(cond='ugt', result=Value(val='%3', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%3', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next2', ty=LabelType()))], pred_blocks=['next0']), 'next2': Block(value=Value(val='next2', ty=LabelType()), instrs=[ICmp(cond='uge', result=Value(val='%4', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%4', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next3', ty=LabelType()))], pred_blocks=['next1']), 'next3': Block(value=Value(val='next3', ty=LabelType()), instrs=[ICmp(cond='ult', result=Value(val='%5', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%5', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next4', ty=LabelType()))], pred_blocks=['next2']), 'next4': Block(value=Value(val='next4', ty=LabelType()), instrs=[ICmp(cond='ule', result=Value(val='%6', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%6', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next5', ty=LabelType()))], pred_blocks=['next3']), 'next5': Block(value=Value(val='next5', ty=LabelType()), instrs=[ICmp(cond='sgt', result=Value(val='%7', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%7', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next6', ty=LabelType()))], pred_blocks=['next4']), 'next6': Block(value=Value(val='next6', ty=LabelType()), instrs=[ICmp(cond='sge', result=Value(val='%8', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%8', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next7', ty=LabelType()))], pred_blocks=['next5']), 'next7': Block(value=Value(val='next7', ty=LabelType()), instrs=[ICmp(cond='slt', result=Value(val='%9', ty=IntegerType(num_bits=1)), fst_operand=Value(val='a', ty=IntegerType(num_bits=32)), snd_operand=Value(val='b', ty=IntegerType(num_bits=32))), Br(cond=Value(val='%9', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next8', ty=LabelType()))], pred_blocks=['next6']), 'next8': Block(value=Value(val='next8', ty=LabelType()), instrs=[FCmp(cond='olt', result=Value(val='%10', ty=IntegerType(num_bits=1)), fst_operand=Value(val=1.0, ty=FPType(kind='double')), snd_operand=Value(val=2.0, ty=FPType(kind='double')), fast_math_flags=frozenset), Br(cond=Value(val='%10', ty=IntegerType(num_bits=1)), label_false=Value(val='unreachable', ty=LabelType()), label_true=Value(val='next9', ty=LabelType()))], pred_blocks=['next7']), 'next9': Block(value=Value(val='next9', ty=LabelType()), instrs=[Ret(value=Value(val=0, ty=IntegerType(num_bits=32)))], pred_blocks=['next8'])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'loop': Function(value=Value(val='loop', ty=FunctionType(param_tys=[IntegerType(num_bits=32)], ret_ty=IntegerType(num_bits=32))), args=[Value(val='i', ty=IntegerType(num_bits=32))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[Br(cond=None, label_false=Value(val='cond', ty=LabelType()), label_true=None)], pred_blocks=[]), 'cond': Block(value=Value(val='cond', ty=LabelType()), instrs=[Phi(result=Value(val='c', ty=IntegerType(num_bits=32)), vals=[(Value(val='i', ty=IntegerType(num_bits=32)), Value(val='%0', ty=LabelType())), (Value(val='j', ty=IntegerType(num_bits=32)), Value(val='do', ty=LabelType()))], fast_math_flags=frozenset()), Phi(result=Value(val='p', ty=IntegerType(num_bits=32)), vals=[(Value(val='r', ty=IntegerType(num_bits=32)), Value(val='do', ty=LabelType())), (Value(val=789, ty=IntegerType(num_bits=32)), Value(val='%0', ty=LabelType()))], fast_math_flags=frozenset()), ICmp(cond='eq', result=Value(val='%1', ty=IntegerType(num_bits=1)), fst_operand=Value(val='c', ty=IntegerType(num_bits=32)), snd_operand=Value(val=0, ty=IntegerType(num_bits=32))), Br(cond=Value(val='%1', ty=IntegerType(num_bits=1)), label_false=Value(val='done', ty=LabelType()), label_true=Value(val='do', ty=LabelType()))], pred_blocks=['do', '%0']), 'do': Block(value=Value(val='do', ty=LabelType()), instrs=[BinOp(opcode='sub', result=Value(val='%2', ty=IntegerType(num_bits=32)), fst_operand=Value(val='p', ty=IntegerType(num_bits=32)), snd_operand=Value(val=23, ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='sub', result=Value(val='j', ty=IntegerType(num_bits=32)), fst_operand=Value(val='i', ty=IntegerType(num_bits=32)), snd_operand=Value(val=1, ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='mul', result=Value(val='r', ty=IntegerType(num_bits=32)), fst_operand=Value(val='%2', ty=IntegerType(num_bits=32)), snd_operand=Value(val=3, ty=IntegerType(num_bits=32)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), Br(cond=None, label_false=Value(val='cond', ty=LabelType()), label_true=None)], pred_blocks=['cond']), 'done': Block(value=Value(val='done', ty=LabelType()), instrs=[Ret(value=Value(val='p', ty=IntegerType(num_bits=32)))], pred_blocks=['cond'])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'memops': Function(value=Value(val='memops', ty=FunctionType(param_tys=[PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val='ptr', ty=PtrType(addr_space=0, ty=None))], blocks={'%0': Block(value=Value(val='%0', ty=LabelType()), instrs=[Load(result=Value(val='a', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=1, is_volatile=False, is_atomic=False, ordering=, syncscope=1), Load(result=Value(val='b', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=1, is_volatile=True, is_atomic=False, ordering=, syncscope=1), Load(result=Value(val='c', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=8, is_volatile=False, is_atomic=False, ordering=, syncscope=1), Load(result=Value(val='d', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=32, is_volatile=False, is_atomic=True, ordering=, syncscope=1), Store(value=Value(val=0, ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=1, is_volatile=False, is_atomic=False, ordering=, syncscope=1), Store(value=Value(val=0, ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=1, is_volatile=True, is_atomic=False, ordering=, syncscope=1), Store(value=Value(val=0, ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=8, is_volatile=False, is_atomic=False, ordering=, syncscope=1), Store(value=Value(val=0, ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), align=32, is_volatile=False, is_atomic=True, ordering=, syncscope=1), AtomicRmw(op='add', result=Value(val='e', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), operand=Value(val=0, ty=IntegerType(num_bits=8)), ordering=, align=1, is_volatile=False, syncscope=1), AtomicRmw(op='xchg', result=Value(val='f', ty=IntegerType(num_bits=8)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), operand=Value(val=0, ty=IntegerType(num_bits=8)), ordering=, align=8, is_volatile=True, syncscope=1), CmpXchg(result=Value(val='g', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=8), IntegerType(num_bits=1)], is_packed=False)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), compare_value=Value(val=1, ty=IntegerType(num_bits=8)), new_value=Value(val=2, ty=IntegerType(num_bits=8)), ordering_ok=, ordering_err=, align=1, is_weak=False, is_volatile=False, syncscope=1), CmpXchg(result=Value(val='h', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=8), IntegerType(num_bits=1)], is_packed=False)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), compare_value=Value(val=1, ty=IntegerType(num_bits=8)), new_value=Value(val=2, ty=IntegerType(num_bits=8)), ordering_ok=, ordering_err=, align=8, is_weak=True, is_volatile=False, syncscope=1), CmpXchg(result=Value(val='i', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=8), IntegerType(num_bits=1)], is_packed=False)), address=Value(val='ptr', ty=PtrType(addr_space=0, ty=None)), compare_value=Value(val=1, ty=IntegerType(num_bits=8)), new_value=Value(val=2, ty=IntegerType(num_bits=8)), ordering_ok=, ordering_err=, align=16, is_weak=False, is_volatile=True, syncscope=1), Ret(value=None)], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'vectorops': Function(value=Value(val='vectorops', ty=FunctionType(param_tys=[IntegerType(num_bits=32), IntegerType(num_bits=32)], ret_ty=IntegerType(num_bits=32))), args=[Value(val='%0', ty=IntegerType(num_bits=32)), Value(val='%1', ty=IntegerType(num_bits=32))], blocks={'%2': Block(value=Value(val='%2', ty=LabelType()), instrs=[InsertElement(result=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), elem=Value(val='%0', ty=IntegerType(num_bits=32)), index=Value(val=0, ty=IntegerType(num_bits=32))), InsertElement(result=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), vector=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), elem=Value(val='%1', ty=IntegerType(num_bits=32)), index=Value(val=2, ty=IntegerType(num_bits=32))), ShuffleVector(result=Value(val='c', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_vector=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), mask_vector=[0, 0, 0, 0]), ShuffleVector(result=Value(val='d', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_vector=Value(val='c', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_vector=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), mask_vector=[1, 2, 3, 0]), BinOp(opcode='add', result=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='d', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='mul', result=Value(val='f', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='xor', result=Value(val='g', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='f', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val='d', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='or', result=Value(val='h', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='f', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='lshr', result=Value(val='i', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='h', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val=[2, 2, 2, 2], ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='shl', result=Value(val='j', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_operand=Value(val='i', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_operand=Value(val=[2, 3, 4, 5], ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), ShuffleVector(result=Value(val='k', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_vector=Value(val='j', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_vector=Value(val='i', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), mask_vector=[2, 3, -1, -1]), ShuffleVector(result=Value(val='m', ty=VectorType(elem_count=1, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_vector=Value(val='k', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), mask_vector=[1]), ShuffleVector(result=Value(val='n', ty=VectorType(elem_count=8, elem_ty=IntegerType(num_bits=32), is_scalable=False)), fst_vector=Value(val='j', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), snd_vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=False)), mask_vector=[0, 0, 1, 2, -1, 3, -1, -1]), ExtractElement(result=Value(val='p', ty=IntegerType(num_bits=32)), vector=Value(val='n', ty=VectorType(elem_count=8, elem_ty=IntegerType(num_bits=32), is_scalable=False)), index=Value(val=5, ty=IntegerType(num_bits=32))), Ret(value=Value(val='p', ty=IntegerType(num_bits=32)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'scalablevectorops': Function(value=Value(val='scalablevectorops', ty=FunctionType(param_tys=[IntegerType(num_bits=32), VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)], ret_ty=IntegerType(num_bits=32))), args=[Value(val='%0', ty=IntegerType(num_bits=32)), Value(val='%1', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True))], blocks={'%2': Block(value=Value(val='%2', ty=LabelType()), instrs=[InsertElement(result=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), elem=Value(val='%0', ty=IntegerType(num_bits=32)), index=Value(val=0, ty=IntegerType(num_bits=32))), InsertElement(result=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), vector=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), elem=Value(val='%0', ty=IntegerType(num_bits=32)), index=Value(val=2, ty=IntegerType(num_bits=32))), ShuffleVector(result=Value(val='c', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_vector=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_vector=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), mask_vector=[0, 0, 0, 0]), BinOp(opcode='add', result=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_operand=Value(val='a', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_operand=Value(val='%1', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='mul', result=Value(val='f', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_operand=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_operand=Value(val='b', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='xor', result=Value(val='g', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_operand=Value(val='f', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_operand=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='or', result=Value(val='h', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_operand=Value(val='g', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_operand=Value(val='e', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), BinOp(opcode='lshr', result=Value(val='i', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fst_operand=Value(val='h', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), snd_operand=Value(val='undef', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False, is_exact=False, is_disjoint=False), ExtractElement(result=Value(val='j', ty=IntegerType(num_bits=32)), vector=Value(val='i', ty=VectorType(elem_count=4, elem_ty=IntegerType(num_bits=32), is_scalable=True)), index=Value(val=3, ty=IntegerType(num_bits=32))), Ret(value=Value(val='j', ty=IntegerType(num_bits=32)))], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'personalityFn': Function(value=Value(val='personalityFn', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], blocks={}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'exn': Function(value=Value(val='exn', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], blocks={'entry': Block(value=Value(val='entry', ty=LabelType()), instrs=[Invoke(result=Value(val='', ty=VoidType()), callee=Value(val='decl', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], label_ok=Value(val='via.cleanup', ty=LabelType()), label_err=Value(val='exn.dispatch', ty=LabelType()), calling_conv=, call_attrs=[])], pred_blocks=[]), 'via.cleanup': Block(value=Value(val='via.cleanup', ty=LabelType()), instrs=[Invoke(result=Value(val='', ty=VoidType()), callee=Value(val='decl', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], label_ok=Value(val='via.catchswitch', ty=LabelType()), label_err=Value(val='cleanup.inner', ty=LabelType()), calling_conv=, call_attrs=[])], pred_blocks=['entry']), 'cleanup.inner': Block(value=Value(val='cleanup.inner', ty=LabelType()), instrs=[CleanupPad(result=Value(val='cp.inner', ty=TokenType()), parent=Value(val='none', ty=TokenType()), args=[]), CleanupRet(cleanup=Value(val='cp.inner', ty=TokenType()), succ_label=Value(val='exn.dispatch', ty=LabelType()))], pred_blocks=['via.cleanup']), 'via.catchswitch': Block(value=Value(val='via.catchswitch', ty=LabelType()), instrs=[Invoke(result=Value(val='', ty=VoidType()), callee=Value(val='decl', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], label_ok=Value(val='exit', ty=LabelType()), label_err=Value(val='dispatch.inner', ty=LabelType()), calling_conv=, call_attrs=[])], pred_blocks=['via.cleanup']), 'dispatch.inner': Block(value=Value(val='dispatch.inner', ty=LabelType()), instrs=[CatchSwitch(result=Value(val='cs.inner', ty=TokenType()), parent=Value(val='none', ty=TokenType()), handler_labels=[Value(val='pad.inner', ty=LabelType())], label_default=Value(val='exn.dispatch', ty=LabelType()))], pred_blocks=['via.catchswitch']), 'pad.inner': Block(value=Value(val='pad.inner', ty=LabelType()), instrs=[CatchPad(result=Value(val='catch.inner', ty=TokenType()), catchswith=Value(val='cs.inner', ty=TokenType()), args=[Value(val=0, ty=IntegerType(num_bits=32))]), CatchRet(catch=Value(val='catch.inner', ty=TokenType()), succ_label=Value(val='exit', ty=LabelType()))], pred_blocks=['dispatch.inner']), 'exn.dispatch': Block(value=Value(val='exn.dispatch', ty=LabelType()), instrs=[CatchSwitch(result=Value(val='cs', ty=TokenType()), parent=Value(val='none', ty=TokenType()), handler_labels=[Value(val='pad1', ty=LabelType()), Value(val='pad2', ty=LabelType())], label_default=Value(val='cleanup', ty=LabelType()))], pred_blocks=['dispatch.inner', 'cleanup.inner', 'entry']), 'pad1': Block(value=Value(val='pad1', ty=LabelType()), instrs=[CatchPad(result=Value(val='%0', ty=TokenType()), catchswith=Value(val='cs', ty=TokenType()), args=[Value(val=1, ty=IntegerType(num_bits=32))]), Unreacheble], pred_blocks=['exn.dispatch']), 'pad2': Block(value=Value(val='pad2', ty=LabelType()), instrs=[CatchPad(result=Value(val='%1', ty=TokenType()), catchswith=Value(val='cs', ty=TokenType()), args=[Value(val=2, ty=IntegerType(num_bits=32))]), Unreacheble], pred_blocks=['exn.dispatch']), 'cleanup': Block(value=Value(val='cleanup', ty=LabelType()), instrs=[CleanupPad(result=Value(val='cp', ty=TokenType()), parent=Value(val='none', ty=TokenType()), args=[]), CleanupRet(cleanup=Value(val='cp', ty=TokenType()), succ_label=None)], pred_blocks=['exn.dispatch']), 'exit': Block(value=Value(val='exit', ty=LabelType()), instrs=[Ret(value=None)], pred_blocks=['pad.inner', 'via.catchswitch'])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'test_intrinsics': Function(value=Value(val='test_intrinsics', ty=FunctionType(param_tys=[], ret_ty=VoidType())), args=[], blocks={'entry': Block(value=Value(val='entry', ty=LabelType()), instrs=[Call(result=Value(val='sp', ty=PtrType(addr_space=0, ty=None)), callee=Value(val='llvm.stacksave.p0', ty=FunctionType(param_tys=[], ret_ty=PtrType(addr_space=0, ty=None))), args=[], calling_conv=, call_attrs=[], tail_kind=None, fast_math_flags=frozenset()), Alloca(result=Value(val='x', ty=PtrType(addr_space=0, ty=None)), allocated_ty=IntegerType(num_bits=32), num_elements=Value(val=1, ty=IntegerType(num_bits=32)), align=4, is_inalloca=False), Conversion(opcode='bitcast', result=Value(val='%0', ty=PtrType(addr_space=0, ty=None)), value=Value(val='x', ty=PtrType(addr_space=0, ty=None)), fast_math_flags=frozenset(), is_nuw=False, is_nsw=False), Call(result=Value(val='', ty=VoidType()), callee=Value(val='llvm.lifetime.start.p0', ty=FunctionType(param_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val=4, ty=IntegerType(num_bits=64)), Value(val='%0', ty=PtrType(addr_space=0, ty=None))], calling_conv=, call_attrs=[], tail_kind=None, fast_math_flags=frozenset()), Call(result=Value(val='', ty=VoidType()), callee=Value(val='llvm.lifetime.end.p0', ty=FunctionType(param_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val=4, ty=IntegerType(num_bits=64)), Value(val='%0', ty=PtrType(addr_space=0, ty=None))], calling_conv=, call_attrs=[], tail_kind=None, fast_math_flags=frozenset()), Call(result=Value(val='', ty=VoidType()), callee=Value(val='llvm.stackrestore.p0', ty=FunctionType(param_tys=[PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val='sp', ty=PtrType(addr_space=0, ty=None))], calling_conv=, call_attrs=[], tail_kind=None, fast_math_flags=frozenset()), Ret(value=None)], pred_blocks=[])}, attrs=[], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'llvm.stacksave.p0': Function(value=Value(val='llvm.stacksave.p0', ty=FunctionType(param_tys=[], ret_ty=PtrType(addr_space=0, ty=None))), args=[], blocks={}, attrs=[{'nocallback': (), 'nofree': (), 'nosync': (), 'nounwind': (), 'willreturn': ()}], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'llvm.stackrestore.p0': Function(value=Value(val='llvm.stackrestore.p0', ty=FunctionType(param_tys=[PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val='%0', ty=PtrType(addr_space=0, ty=None))], blocks={}, attrs=[{'nocallback': (), 'nofree': (), 'nosync': (), 'nounwind': (), 'willreturn': ()}], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'llvm.lifetime.start.p0': Function(value=Value(val='llvm.lifetime.start.p0', ty=FunctionType(param_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val='%0', ty=IntegerType(num_bits=64)), Value(val='%1', ty=PtrType(addr_space=0, ty=None))], blocks={}, attrs=[{'nocallback': (), 'nofree': (), 'nosync': (), 'nounwind': (), 'willreturn': (), 'memory': 3}, {}, {'immarg': ()}, {'nocapture': ()}], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None)), 'llvm.lifetime.end.p0': Function(value=Value(val='llvm.lifetime.end.p0', ty=FunctionType(param_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], ret_ty=VoidType())), args=[Value(val='%0', ty=IntegerType(num_bits=64)), Value(val='%1', ty=PtrType(addr_space=0, ty=None))], blocks={}, attrs=[{'nocallback': (), 'nofree': (), 'nosync': (), 'nounwind': (), 'willreturn': (), 'memory': 3}, {}, {'immarg': ()}, {'nocapture': ()}], calling_convention=, is_vararg=False, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None))}, global_vars={'var': GlobalVariable(value=Value(val='var', ty=IntegerType(num_bits=32)), initializer=Value(val=42, ty=IntegerType(num_bits=32)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'ext': GlobalVariable(value=Value(val='ext', ty=PtrType(addr_space=0, ty=None)), initializer=None, is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'cst': GlobalVariable(value=Value(val='cst', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), initializer=Value(val=[Value(val=1, ty=IntegerType(num_bits=64)), Value(val='cst', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False))], ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), is_const=True, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'tl': GlobalVariable(value=Value(val='tl', ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), initializer=Value(val=[Value(val=1, ty=IntegerType(num_bits=64)), Value(val='cst', ty=StructureType(name='S', elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False))], ty=StructureType(name=None, elem_tys=[IntegerType(num_bits=64), PtrType(addr_space=0, ty=None)], is_packed=False)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'arr': GlobalVariable(value=Value(val='arr', ty=ArrayType(elem_count=5, elem_ty=IntegerType(num_bits=8))), initializer=Value(val=b'\\x02\\x03\\x05\\x07\\x0b', ty=ArrayType(elem_count=5, elem_ty=IntegerType(num_bits=8))), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'str': GlobalVariable(value=Value(val='str', ty=ArrayType(elem_count=13, elem_ty=IntegerType(num_bits=8))), initializer=Value(val=b'hello world\\n\\x00', ty=ArrayType(elem_count=13, elem_ty=IntegerType(num_bits=8))), is_const=True, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'locStr': GlobalVariable(value=Value(val='locStr', ty=ArrayType(elem_count=13, elem_ty=IntegerType(num_bits=8))), initializer=Value(val=b'hello world\\n\\x00', ty=ArrayType(elem_count=13, elem_ty=IntegerType(num_bits=8))), is_const=True, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'hidden': GlobalVariable(value=Value(val='hidden', ty=IntegerType(num_bits=32)), initializer=Value(val=7, ty=IntegerType(num_bits=32)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'protected': GlobalVariable(value=Value(val='protected', ty=IntegerType(num_bits=32)), initializer=Value(val=23, ty=IntegerType(num_bits=32)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'section': GlobalVariable(value=Value(val='section', ty=IntegerType(num_bits=32)), initializer=Value(val=27, ty=IntegerType(num_bits=32)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section='.custom'), is_externally_initialized=False), 'align': GlobalVariable(value=Value(val='align', ty=IntegerType(num_bits=32)), initializer=Value(val=31, ty=IntegerType(num_bits=32)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=4, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'nullptr': GlobalVariable(value=Value(val='nullptr', ty=PtrType(addr_space=0, ty=None)), initializer=Value(val=None, ty=PtrType(addr_space=0, ty=None)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'const_gep': GlobalVariable(value=Value(val='const_gep', ty=PtrType(addr_space=0, ty=None)), initializer=Value(val=GetElementPtr(result=Value(val='', ty=PtrType(addr_space=0, ty=None)), source_ty=IntegerType(num_bits=32), base_addr=Value(val='var', ty=IntegerType(num_bits=32)), indices=[Value(val=2, ty=IntegerType(num_bits=64))], dest_ty=IntegerType(num_bits=32), is_inbounds=False), ty=PtrType(addr_space=0, ty=None)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False), 'const_inbounds_gep': GlobalVariable(value=Value(val='const_inbounds_gep', ty=PtrType(addr_space=0, ty=None)), initializer=Value(val=GetElementPtr(result=Value(val='', ty=PtrType(addr_space=0, ty=None)), source_ty=IntegerType(num_bits=32), base_addr=Value(val='var', ty=IntegerType(num_bits=32)), indices=[Value(val=1, ty=IntegerType(num_bits=64))], dest_ty=IntegerType(num_bits=32), is_inbounds=True), ty=PtrType(addr_space=0, ty=None)), is_const=False, attrs={}, global_object=GlobalObject(addr_space=0, align=0, linkage=, visibility=, unnamed_addr=, thread_local=, section=None), is_externally_initialized=False)})""" 260 | 261 | mod = llvm2py.parse_assembly(src) 262 | 263 | dumped = str(mod) 264 | dumped = re.sub(r"frozenset\([^)]+\)", "frozenset", dumped) 265 | dumped = re.sub(r"]+\>", "Unreacheble", dumped) 266 | 267 | assert dumped == reference, "Error, IR dump is different from original" 268 | --------------------------------------------------------------------------------