Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]

members = [
"src/Simulation/qdk_sim_rs",
"src/Qir/microsoft-quantum-qir-runtime-sys",
]
8 changes: 4 additions & 4 deletions build/pack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ function Pack-Crate() {
$OutPath = Resolve-Path (Join-Path $PSScriptRoot $OutPath);
}
Push-Location (Join-Path $PSScriptRoot $PackageDirectory)
cargo package;
# Copy only the .crate file, since we don't need all the intermediate
# artifacts brought in by the full folder under target/package.
Copy-Item -Force (Join-Path . "target" "package" "*.crate") $OutPath;
cargo package;
# Copy only the .crate file, since we don't need all the intermediate
# artifacts brought in by the full folder under target/package.
Copy-Item -Force (Join-Path $PSScriptRoot .. "target" "package" "*.crate") $OutPath;
Pop-Location
}

Expand Down
31 changes: 31 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "microsoft-quantum-qir-runtime-sys"
version = "0.1.0"
edition = "2018"
build = "build.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cty = "0.2.1"
libloading = "0.7.0"
log = "0.4.14"
tempfile = "3.2.0"
llvm-sys = { version = "110", optional = true }
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", default-features = false, features = ["llvm11-0", "target-x86"], optional = true }
lazy_static = "1.4.0"

[build-dependencies]
cmake = "0.1.46"
bindgen = "0.59.1"
cc = "1.0.71"
which = "4.2.2"

[lib]


[features]
runtime = []
foundation = []
llvm-libloading = ["llvm-sys", "inkwell"]
default = ["runtime", "foundation"]
79 changes: 79 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use cmake::Config;
use std::boxed::Box;
use std::env;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
println!("cargo:rerun-if-env-changed=TARGET");
println!("cargo:rerun-if-changed=build.rs");

for (key, value) in env::vars() {
println!("{}: {}", key, value);
}
if cfg!(target_os = "windows") {
let path_to_runtime_src = "..\\Runtime";
compile_runtime_libraries(path_to_runtime_src)?;
} else {
let path_to_runtime_src = "../Runtime";
compile_runtime_libraries(path_to_runtime_src)?;
}
Comment on lines +16 to +22
Copy link
Contributor

Choose a reason for hiding this comment

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

(Nit)
Consider deduplicating as much as possible, such that only the differing part is placed in if .. else ... Something like this (pseudocode):

Suggested change
if cfg!(target_os = "windows") {
let path_to_runtime_src = "..\\Runtime";
compile_runtime_libraries(path_to_runtime_src)?;
} else {
let path_to_runtime_src = "../Runtime";
compile_runtime_libraries(path_to_runtime_src)?;
}
let path_separator = (cfg!(target_os = "windows") ? "\\" : "/"); // Either \ or /
let path_to_runtime_src = ".." + path_separator + "Runtime"; // "..\Runtime" or "../Runtime"
compile_runtime_libraries(path_to_runtime_src)?;


Ok(())
}

fn compile_runtime_libraries(path_to_runtime_src: &str) -> Result<(), Box<dyn Error>> {
let mut config = Config::new(path_to_runtime_src);

if cfg!(target_os = "windows") {
config.static_crt(true);
}

set_compiler(&mut config)?;
set_profile(&mut config)?;

config.generator("Ninja");

let _ = config.build();
Ok(())
}
Comment on lines +27 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

(Nit. I don't know Rust. Talking in C/C++ terminology)
I have an impression that the function always returns Ok(()). If true, then doesn't it make sense to return void?

Copy link
Contributor

Choose a reason for hiding this comment

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

In Rust, expressions and functions always must return a value (by contrast with void, which is the absence of any value). Typically, a function with no meaningful return value will be marked as returning () (similar to Q#'s Unit or F#'s unit). Here, the return type is Result<(), Box<dyn Error>>, meaning it can either be Ok(()) or Err(t) for some t of type Box<dyn Error>.

All that leads up to that Ok(()) is the Rust equivalent to a void return type in C++.

As for why it seems like it always returns Ok(()), the ? operator at lines 34 and 35 cause early returns when either value is an Error. That's somewhat similar to exceptions, but has the benefit of being much more strongly typed, as it's not possible for a caller to ignore exceptions and as there's no use of exceptional control flow needed.


// https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#how-do-i-use-a-different-compiler
// We set this here as setting it in the cmakefile is discouraged
fn set_compiler(config: &mut Config) -> Result<(), Box<dyn Error>>{
if cfg!(target_os = "linux") {
let mut c_cfg = cc::Build::new();
let clang_11 = which::which("clang-11")?;
c_cfg.compiler(clang_11);
config.init_c_cfg(c_cfg);

let mut cxx_cfg = cc::Build::new();
let clangpp_11 = which::which("clang++-11")?;
cxx_cfg.compiler(clangpp_11);
config.init_cxx_cfg(cxx_cfg);
} else if cfg!(target_os = "windows") {
let mut c_cfg = cc::Build::new();
let clang = which::which("clang.exe")?;
c_cfg.compiler(clang);
config.init_c_cfg(c_cfg);

let mut cxx_cfg = cc::Build::new();
let clangpp = which::which("clang++.exe")?;
cxx_cfg.compiler(clangpp);
config.init_cxx_cfg(cxx_cfg);
} else if cfg!(target_os = "macos") {
// Use macos default
} else {
panic!("Unsupported platform")
}
Comment on lines +46 to +70
Copy link
Contributor

Choose a reason for hiding this comment

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

(Nit. . . .)
Consider dedulplicating (such that only the difference is in if .. else..), something like (Rust/C/C++ pseudocode)

Suggested change
if cfg!(target_os = "linux") {
let mut c_cfg = cc::Build::new();
let clang_11 = which::which("clang-11")?;
c_cfg.compiler(clang_11);
config.init_c_cfg(c_cfg);
let mut cxx_cfg = cc::Build::new();
let clangpp_11 = which::which("clang++-11")?;
cxx_cfg.compiler(clangpp_11);
config.init_cxx_cfg(cxx_cfg);
} else if cfg!(target_os = "windows") {
let mut c_cfg = cc::Build::new();
let clang = which::which("clang.exe")?;
c_cfg.compiler(clang);
config.init_c_cfg(c_cfg);
let mut cxx_cfg = cc::Build::new();
let clangpp = which::which("clang++.exe")?;
cxx_cfg.compiler(clangpp);
config.init_cxx_cfg(cxx_cfg);
} else if cfg!(target_os = "macos") {
// Use macos default
} else {
panic!("Unsupported platform")
}
if cfg!(target_os != "macos") {
if cfg!(target_os != "linux") && cfg!(target_os != "windows"){
panic!("Unsupported platform")
}
let clang = which::which(
cfg!(target_os = "linux") ? "clang-11" : "clang.exe")?;
let mut c_cfg = cc::Build::new();
c_cfg.compiler(clang);
config.init_c_cfg(c_cfg);
let clangpp = which::which(
cfg!(target_os = "linux") ? "clang++-11" : "clang++.exe")?;
let mut cxx_cfg = cc::Build::new();
cxx_cfg.compiler(clangpp);
config.init_cxx_cfg(cxx_cfg);
}

Ok(())
}

fn set_profile(config: &mut Config) -> Result<(), Box<dyn Error>> {
config.define("CMAKE_BUILD_TYPE", "RelWithDebInfo");
config.define("CMAKE_C_COMPILER_WORKS", "1");
config.define("CMAKE_CXX_COMPILER_WORKS", "1");
Ok(())
}
59 changes: 59 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/src/foundation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use lazy_static::lazy_static;

use libloading::Library;

#[cfg(target_os = "linux")]
const FOUNDATION_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QSharpFoundation/libMicrosoft.Quantum.Qir.QSharp.Foundation.so"
));

#[cfg(target_os = "macos")]
const FOUNDATION_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QSharpFoundation/libMicrosoft.Quantum.Qir.QSharp.Foundation.dylib"
));

#[cfg(target_os = "windows")]
const FOUNDATION_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QSharpFoundation/Microsoft.Quantum.Qir.QSharp.Foundation.dll"
));
Comment on lines +8 to +24
Copy link
Contributor

Choose a reason for hiding this comment

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

Just FYI (I'm not saying whether it is good or bad):
I have seen a case where a single name for the dynamic library (written in Rust?) is used for all 3 platforms (in C#). See here, here, and here. Written by Chris.

If that is not applicable to Rust then is it possible to write like this? (deduplicated)

const FOUNDATION_BYTES: &'static [u8] = include_bytes!(concat!(
    env!("OUT_DIR"),
#[cfg(target_os = "linux")]
    "/build/lib/QSharpFoundation/libMicrosoft.Quantum.Qir.QSharp.Foundation.so"
#[cfg(target_os = "macos")]
    "/build/lib/QSharpFoundation/libMicrosoft.Quantum.Qir.QSharp.Foundation.dylib"
#[cfg(target_os = "windows")]
    "/build/lib/QSharpFoundation/Microsoft.Quantum.Qir.QSharp.Foundation.dll"
));

Copy link
Contributor

Choose a reason for hiding this comment

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

To clarify, I did that due to requirements in our CI and packaging pipeline for .NET projects; I wouldn't recommend following my approach in this case.


lazy_static! {
pub(crate) static ref FOUNDATION_LIBRARY: Library = unsafe {
crate::qir_libloading::load_library_bytes(
"Microsoft.Quantum.Qir.QSharp.Foundation",
FOUNDATION_BYTES,
)
.unwrap()
};
}

pub struct QSharpFoundation {}

impl QSharpFoundation {
pub fn new() -> QSharpFoundation {
let _ = FOUNDATION_LIBRARY;
QSharpFoundation {}
}
}

#[cfg(test)]
mod tests {
use crate::foundation::QSharpFoundation;

#[test]
fn library_loads_on_new() {
let _ = QSharpFoundation::new();
}
#[test]
fn library_can_be_initialized_multiple_times() {
let _ = QSharpFoundation::new();
let _ = QSharpFoundation::new();
let _ = QSharpFoundation::new();
}
}
10 changes: 10 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#[cfg(feature = "foundation")]
pub mod foundation;

#[cfg(feature = "runtime")]
pub mod runtime;

mod qir_libloading;
55 changes: 55 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/src/qir_libloading.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use libloading::{library_filename, Library};
use log;
use std::error::Error;
use std::path::Path;
use tempfile::tempdir;

pub(crate) fn write_library<P: AsRef<Path>>(
path: P,
lib: &'static [u8],
) -> Result<(), Box<dyn Error>> {
log::debug!("Writing {}", path.as_ref().display());
std::fs::write(path, lib)?;
Ok(())
}

pub(crate) unsafe fn load_library_bytes(
base_name: &str,
lib: &'static [u8],
) -> Result<Library, Box<dyn Error>> {
let name = library_filename(base_name)
.into_string()
.expect("Could not get library name as string");
let path = tempdir().expect("");
let filepath = path.as_ref().join(name);
write_library(&filepath, lib)?;
let library = load_library(&filepath)?;
Ok(library)
}

pub(crate) unsafe fn load_library<P: AsRef<Path>>(path: P) -> Result<Library, Box<dyn Error>> {
log::debug!("Loading {}", path.as_ref().display());
let library = Library::new(path.as_ref().as_os_str())?;

#[cfg(feature = "llvm-libloading")]
load_library_with_llvm(path);

Ok(library)
}

#[cfg(feature = "llvm-libloading")]
fn load_library_with_llvm<P: AsRef<Path>>(path: P) {
let library_path = path
.as_ref()
.to_str()
.expect("Could not convert library path to &str");
let was_loaded_by_llvm = inkwell::support::load_library_permanently(library_path);
if was_loaded_by_llvm {
log::error!("Failed to load {} into LLVM", library_path);
} else {
log::debug!("Loaded {} into LLVM", library_path);
}
}
118 changes: 118 additions & 0 deletions src/Qir/microsoft-quantum-qir-runtime-sys/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use lazy_static::lazy_static;

use std::ffi::CString;

use cty;
use libloading::Library;

#[cfg(target_os = "linux")]
const RUNTIME_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QIR/libMicrosoft.Quantum.Qir.Runtime.so"
));

#[cfg(target_os = "macos")]
const RUNTIME_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QIR/libMicrosoft.Quantum.Qir.Runtime.dylib"
));

#[cfg(target_os = "windows")]
const RUNTIME_BYTES: &'static [u8] = include_bytes!(concat!(
env!("OUT_DIR"),
"/build/lib/QIR/Microsoft.Quantum.Qir.Runtime.dll"
));

lazy_static! {
pub(crate) static ref RUNTIME_LIBRARY: Library = unsafe {
crate::qir_libloading::load_library_bytes("Microsoft.Quantum.Qir.Runtime", RUNTIME_BYTES)
.unwrap()
};
}

pub type QUBIT = u64;

#[repr(C)]
pub struct QirArray {
private: [u8; 0],
}

pub type IRuntimeDriver = cty::c_void;

pub struct BasicRuntimeDriver {}

impl BasicRuntimeDriver {
pub unsafe fn initialize_qir_context(track_allocated_objects: bool) {
// The libloading calls need to be used instead of the extern "C" calls
// to prevent linkage. Python can't init the lib if we take a hard
// dependency on the library
let driver = QirRuntime::create_basic_runtime_driver();
QirRuntime::initialize_qir_context(driver, track_allocated_objects);
}
}

pub struct QirRuntime {}

impl QirRuntime {
pub fn new() -> QirRuntime {
let _ = RUNTIME_LIBRARY;
QirRuntime {}
}

pub unsafe fn create_basic_runtime_driver() -> *mut cty::c_void {
let library = &RUNTIME_LIBRARY;
let create = library
.get::<fn() -> *mut IRuntimeDriver>(
CString::new("CreateBasicRuntimeDriver")
.unwrap()
.as_bytes_with_nul(),
)
.unwrap();
create()
}

pub unsafe fn initialize_qir_context(driver: *mut cty::c_void, track_allocated_objects: bool) {
let library = &RUNTIME_LIBRARY;
let init = library
.get::<fn(*mut cty::c_void, bool)>(
CString::new("InitializeQirContext")
.unwrap()
.as_bytes_with_nul(),
)
.unwrap();
init(driver, track_allocated_objects)
}

pub unsafe fn quantum_rt_array_get_element_ptr_1d(
array: *mut QirArray,
index: i64,
) -> *mut cty::c_char {
let library = &RUNTIME_LIBRARY;
let get_element_ptr = library
.get::<fn(*mut QirArray, arg2: i64) -> *mut cty::c_char>(
CString::new("__quantum__rt__array_get_element_ptr_1d")
.unwrap()
.as_bytes_with_nul(),
)
.unwrap();
get_element_ptr(array, index)
}
}

#[cfg(test)]
mod tests {
use crate::runtime::QirRuntime;
#[test]
fn library_loads_on_new() {
let _ = QirRuntime::new();
}
#[test]
fn library_can_be_initialized_multiple_times() {
let _ = QirRuntime::new();
let _ = QirRuntime::new();
let _ = QirRuntime::new();
}
}