# Upgrading PyQIR ## PyQIR 0.8 PyQIR 0.7 was the last version of PyQIR to support QIR evaluation. Simulation of QIR is now available via the [`qir-runner`](https://github.com/qir-alliance/qir-runner) sparse simulator. ## PyQIR 0.7 ### Packages PyQIR 0.6 was the last version of PyQIR to use three packages (`pyqir-evaluator`, `pyqir-generator`, and `pyqir-parser`) and a metapackage (`pyqir`). PyQIR 0.7 instead uses only a single package (`pyqir`) that has the functionality of all previous packages. If you imported the `pyqir.generator` or `pyqir.parser` modules, then the same or an equivalent API is available in the `pyqir` module. If you imported the `pyqir.evaluator` module, it is still available under the same name with no API changes. ## Generator ### IR and bitcode conversion The functions `bitcode_to_ir` and `ir_to_bitcode` were removed because the new `Module` class has the same functionality. `Module` supports both parsing and generating QIR. For example, instead of: ```python from pyqir.generator import bitcode_to_ir, ir_to_bitcode ir = bitcode_to_ir(bitcode, "module_name") bitcode = ir_to_bitcode(ir, "module_name") # Or with a source filename: ir = bitcode_to_ir(bitcode, "module_name", "source_filename") ``` Use this: ```python from pyqir import Context, Module ir = str(Module.from_bitcode(Context(), bitcode, "name")) bitcode = Module.from_ir(Context(), ir, "name").bitcode # Or with a source filename: m = Module.from_bitcode(Context(), bitcode, "module_name") m.source_filename = "source_filename" ir = str(m) ``` ### Types If you generated programs with externally-linked functions, then you used the `pyqir.generator.types` module to describe the type of the functions. This module has been removed. Types need to be created differently because they now directly contain LLVM type objects, which require an LLVM context. | PyQIR 0.6 | PyQIR 0.7 | | --------------------------------------------- | --------------------------------- | | `pyqir.generator.types.VOID` | `pyqir.Type.void(context)` | | `pyqir.generator.types.BOOL` | `pyqir.IntType(context, 1)` | | `pyqir.generator.types.Int(width)` | `pyqir.IntType(context, width)` | | `pyqir.generator.types.DOUBLE` | `pyqir.Type.double(context)` | | `pyqir.generator.types.QUBIT` | `pyqir.qubit_type(context)` | | `pyqir.generator.types.RESULT` | `pyqir.result_type(context)` | | `pyqir.generator.types.Function(params, ret)` | `pyqir.FunctionType(ret, params)` | There are two ways to get a `context` object: 1. Use the `context` property on `SimpleModule`. For example: ```python from pyqir import SimpleModule, Type module = SimpleModule("name", num_qubits=1, num_results=1) void = Type.void(module.context) ``` 2. Create one yourself. But you also need to give the context you created to `SimpleModule`. For example: ```python from pyqir import Context, SimpleModule, Type context = Context() module = SimpleModule("name", num_qubits=1, num_results=1, context=context) void = Type.void(context) ``` ## Parser PyQIR 0.7 unified parsing and code generation into a single API that is designed to support both. This makes PyQIR much more powerful and will enable workflows that involve inspecting, running passes on, or otherwise transforming QIR that is parsed or generated using PyQIR. This means that the API for `pyqir-parser` had to be completely redesigned, which unfortunately makes upgrading challenging. Here are some tips. ### Modules Use `Module.from_bitcode` or `Module.from_ir` instead of the `QirModule` constructor. See [IR and bitcode conversion](#ir-and-bitcode-conversion). ### Entry points and interop-friendly functions Instead of `QirModule.entrypoint_funcs`, `QirModule.interop_funcs`, or `QirModule.get_funcs_by_attr`, filter the `Module.functions` list instead. For example: ```python entry_point = next(filter(pyqir.is_entry_point, module.functions)) interops = filter(pyqir.is_interop_friendly, module.functions) ``` ### Instructions The instruction class hierarchy was trimmed down significantly. Most subclasses of `QirInstr` were removed. The surviving subclasses are `Call`, `FCmp`, `ICmp`, `Phi` and `Switch`. For any other instruction, it should be possible to use the base `Instruction` class to do anything that was previously possible. Use the `opcode` property to check what kind of instruction it is, and the `operands` property to read all of the values that the instruction references. The `successors` property is a subset of `operands` and contains just the values that are basic blocks, which can be useful to follow control flow with `br` instructions. For example, if you have a terminator instruction `term`, then you can get the instructions of its first successor with `term.successors[0].instructions`. ### Qubit and result IDs In PyQIR 0.6, `QirQubitConstant` and `QirResultConstant` were subclasses of `QirOperand`. Instead, you can try to extract a static qubit or result ID from any value using `pyqir.qubit_id(value)` and `pyqir.result_id(value)`. If the value isn't the right kind, it will return `None`. ### Strings In PyQIR 0.6, you could read a global string constant from a string value using `QirModule.get_global_bytes_value(value)`. This is possible now using `pyqir.extract_byte_string(value)`. ### Examples To see examples of how the new parser API can be used, take a look at [test_parser.py](https://github.com/qir-alliance/pyqir/blob/53e4aebfdb456e9603fae28543a8391075021a9f/pyqir/tests/test_parser.py). You can also compare it with [test_parser_api.py](https://github.com/qir-alliance/pyqir/blob/v0.6.2/pyqir-parser/tests/test_parser_api.py) from PyQIR 0.6 for a before-and-after view of the same test cases using both the old and new APIs.