Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Conversation

@troels-im
Copy link
Contributor

@troels-im troels-im commented Jul 27, 2021

Qubit Allocation Analysis

Dependencies

This PR depends on #1092.

Purpose

The purpose of this pass is to analyse the code for qubit allocations and identify
the allocation dependency. This helps subsequent transfomation passes expand the code
to, for instance, eliminate loops and classical logic. This is desirable as the control
logic for some quantum computing systems may be limited and one may therefore wish
to reduce its complexity as much as possible at compile time.

Quick start

The following depnds on:

  • A working LLVM installation, including paths correctly setup
  • CMake
  • C#, Q# and the .NET framework

Running following command

% make run

will first build the pass, then build the QIR using Q# following by removing the noise using opt with optimisation level 1. Finally, it will execute the analysis pass and should provide you with information about qubit allocation in the Q# program defined in ConstSizeArray/ConstSizeArray.qs.

Detailed run

From the Passes root (two levels up from this directory), make a new build

% mkdir Debug
% cd Debug
% cmake ..

and then compile the QubitAllocationAnalysis:

% make QubitAllocationAnalysis

Next return examples/QubitAllocationAnalysis and enter the directory ConstSizeArray to build the QIR:

% make analysis-example.ll

or execute the commands manually,

% dotnet build ConstSizeArray.csproj
% opt -S qir/ConstSizeArray.ll -O1  > ../analysis-example.ll
% make clean

Returning to examples/QubitAllocationAnalysis, the pass can now be ran by executing:

% opt -load-pass-plugin ../../Debug/libs/libQubitAllocationAnalysis.dylib --passes="print<qubit-allocation-analysis>" -disable-output analysis-example.ll

Example cases

Below we will consider a few different examples. You can run them by updating the code in ConstSizeArray/ConstSizeArray.qs and executing make run from the examples/QubitAllocationAnalysis folder subsequently. You will need to delete analysis-example.ll between runs.

Trivially constant

This is the simplest example we can think of:

namespace Example {
    @EntryPoint()
    operation QuantumProgram() : Unit {
        use qubits = Qubit[3];
    }
}

The corresponding QIR is:

; ModuleID = 'qir/ConstSizeArray.ll'
source_filename = "qir/ConstSizeArray.ll"

%Array = type opaque

define internal fastcc void @Example__QuantumProgram__body() unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 3)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}

; (...)

Running the pass procudes following output:

% opt -load-pass-plugin ../../Debug/libs/libQubitAllocationAnalysis.dylib --passes="print<qubit-allocation-analysis>" -disable-output analysis-example.ll

Example__QuantumProgram__body
====================

qubits is trivially static with 3 qubits.

Dependency case

In some cases, a qubit array will be compile time constant in size if the function arguments
provided are compile-time constants. One example of this is:

namespace Example {
    @EntryPoint()
    operation Main() : Int
    {
        QuantumProgram(3);
        QuantumProgram(4);
        return 0;
    }

    operation QuantumProgram(x: Int) : Unit {
        use qubits = Qubit[x];
    }
}

The corresponding QIR is

; ModuleID = 'qir/ConstSizeArray.ll'
source_filename = "qir/ConstSizeArray.ll"

%Array = type opaque
%String = type opaque

define internal fastcc void @Example__Main__body() unnamed_addr {
entry:
  call fastcc void @Example__QuantumProgram__body(i64 3)
  call fastcc void @Example__QuantumProgram__body(i64 4)
  ret void
}

define internal fastcc void @Example__QuantumProgram__body(i64 %x) unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 %x)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}
; ( ... )

The analyser returns following output:

% opt -load-pass-plugin ../../Debug/libs/libQubitAllocationAnalysis.dylib --passes="print<qubit-allocation-analysis>" -disable-output analysis-example.ll

Example__QuantumProgram__body
====================

qubits depends on x being constant to be static.

Summary case

Finally, we do a summary case that demonstrates some of the more elaborate cases:

namespace Example {
    @EntryPoint()
    operation Main() : Int
    {
        QuantumProgram(3,2,1);
        QuantumProgram(4,9,4);
        return 0;
    }

    function X(value: Int): Int
    {
        return 3 * value;
    }

    operation QuantumProgram(x: Int, h: Int, g: Int) : Unit {
        let z = x * (x + 1) - 47;
        let y = 3 * x;

        use qubits0 = Qubit[9];
        use qubits1 = Qubit[(y - 2)/2-z];
        use qubits2 = Qubit[y - g];
        use qubits3 = Qubit[h];
        use qubits4 = Qubit[X(x)];
    }
}

We will omit the QIR in the documenation as it is a long. The output of the anaysis is:

% opt -load-pass-plugin ../../Debug/libs/libQubitAllocationAnalysis.dylib --passes="print<qubit-allocation-analysis>" -disable-output analysis-example.ll

Example__QuantumProgram__body
====================

qubits0 is trivially static with 9 qubits.
qubits1 depends on x being constant to be static.
qubits2 depends on x, g being constant to be static.
qubits3 depends on h being constant to be static.
qubits4 is dynamic.

Tests

Finally, this PR introduces Lit tests for QIR analysis and transformations:

-- Testing: 2 tests, 2 workers --
PASS: Quantum-Passes :: QubitAllocationAnalysis/case1.ll (1 of 2)
PASS: Quantum-Passes :: QubitAllocationAnalysis/case2.ll (2 of 2)

Testing Time: 0.28s

These are located in tests/.

Qubit Static Expansion

Purpose

A complementary pass to the one described above, is the expansion pass. Based on the analysis performed by the first pass, this pass attempts to change qubit allocations to static allocations (i.e. allocations with known size at compile time). This is the first step towards compiling for targets that does not have loop support.

Quick Start

The dependencies are described in above. To run this example, execute

% make run-expand

in the examples/QubitAllocationAnalysis. If ran with following Q# code :

namespace Example {
    @EntryPoint()
    operation Main() : Int
    {
        QuantumProgram(3);
        QuantumProgram(4);
        return 0;
    }

    operation QuantumProgram(x: Int) : Unit {
        use qubits = Qubit[x];
    }
}

the original QIR becomes

define internal fastcc void @Example__Main__body() unnamed_addr {
entry:
  call fastcc void @Example__QuantumProgram__body(i64 3)
  call fastcc void @Example__QuantumProgram__body(i64 4)
  ret void
}

define internal fastcc void @Example__QuantumProgram__body(i64 %x) unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 %x)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}

After running the pass, the new QIR is

; ModuleID = 'analysis-example.ll'
source_filename = "qir/ConstSizeArray.ll"

%Array = type opaque
%String = type opaque

define internal fastcc void @Example__Main__body() unnamed_addr {
entry:
  call void @Example__QuantumProgram__body.1()
  call void @Example__QuantumProgram__body.2()
  ret void
}

define internal fastcc void @Example__QuantumProgram__body(i64 %x) unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 %x)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}

define internal fastcc void @Example__QuantumProgram__body.1() unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 3)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}

define internal fastcc void @Example__QuantumProgram__body.2() unnamed_addr {
entry:
  %qubits = call %Array* @__quantum__rt__qubit_allocate_array(i64 4)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 1)
  call void @__quantum__rt__array_update_alias_count(%Array* %qubits, i32 -1)
  call void @__quantum__rt__qubit_release_array(%Array* %qubits)
  ret void
}

We see that the original code is expanded with two new functions where the constants are added inside the function body to replace the function arguments. This, in turn, allows us to do constant folding and eventually loop unrolling for the logic. The original function is preserved, albeit being dead code. In a future update, we may choose to discard the original functoin.

@troels-im
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@troels-im troels-im mentioned this pull request Aug 4, 2021
@@ -0,0 +1 @@
# {ExpandStaticAllocation} Specification
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, what is this md for? Is it a placeholder for future expansion or does it provide some auto-documentation functionality with LLVM?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a SPECIFICATION.md to each of the passes. The initial thought was that each of the pass should have a specification such that it is clear what it does without the need to read the code. However, this may be revised in one of the future PRs.

@@ -0,0 +1,9 @@
# Qubit Allocation Analysis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specification.md is filled in, but the other two are not. Maybe include some placeholder text in the other two files indicating that they will be written in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good suggestion. I will add a placeholder text to the template in one of the coming PRs.

@troels-im troels-im merged commit 464a7c4 into microsoft:features/llvm-passes Aug 9, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants