qir_stdlib/
strings.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::update_counts;
5use crate::Pauli;
6use num_bigint::BigInt;
7use std::{
8    ffi::{CStr, CString},
9    os::raw::{c_char, c_double},
10    rc::Rc,
11};
12
13#[no_mangle]
14pub unsafe extern "C" fn __quantum__rt__string_create(str: *mut c_char) -> *const CString {
15    let cstring = CString::new(CStr::from_ptr(str).to_owned()).expect("Failed to create %String");
16    Rc::into_raw(Rc::new(cstring))
17}
18
19#[no_mangle]
20pub unsafe extern "C" fn __quantum__rt__string_get_data(str: *const CString) -> *const c_char {
21    (*str).as_bytes_with_nul().as_ptr().cast::<c_char>()
22}
23
24#[no_mangle]
25pub unsafe extern "C" fn __quantum__rt__string_get_length(str: *const CString) -> u32 {
26    (*str)
27        .as_bytes()
28        .len()
29        .try_into()
30        .expect("String length is too large for 32-bit integer.")
31}
32
33#[no_mangle]
34pub unsafe extern "C" fn __quantum__rt__string_update_reference_count(
35    str: *const CString,
36    update: i32,
37) {
38    update_counts(str, update, false);
39}
40
41#[no_mangle]
42pub unsafe extern "C" fn __quantum__rt__string_concatenate(
43    s1: *const CString,
44    s2: *const CString,
45) -> *const CString {
46    let mut new_str = (*s1).clone().into_bytes();
47    new_str.extend_from_slice((*s2).to_bytes());
48
49    Rc::into_raw(Rc::new(
50        CString::new(new_str).expect("Unable to convert string"),
51    ))
52}
53
54#[no_mangle]
55pub unsafe extern "C" fn __quantum__rt__string_equal(
56    s1: *const CString,
57    s2: *const CString,
58) -> bool {
59    *s1 == *s2
60}
61
62pub(crate) fn convert<T>(input: &T) -> *const CString
63where
64    T: ToString,
65{
66    unsafe {
67        __quantum__rt__string_create(
68            CString::new(input.to_string())
69                .expect("Unable to allocate string for conversion.")
70                .as_bytes_with_nul()
71                .as_ptr() as *mut c_char,
72        )
73    }
74}
75
76#[no_mangle]
77pub extern "C" fn __quantum__rt__int_to_string(input: i64) -> *const CString {
78    convert(&input)
79}
80
81pub(crate) fn double_to_string(input: c_double) -> String {
82    if (input.floor() - input.ceil()).abs() < c_double::EPSILON {
83        // The value is a whole number, which by convention is displayed with one decimal point
84        // to differentiate it from an integer value.
85        format!("{input:.1}")
86    } else {
87        format!("{input}")
88    }
89}
90
91#[no_mangle]
92pub extern "C" fn __quantum__rt__double_to_string(input: c_double) -> *const CString {
93    convert(&double_to_string(input))
94}
95
96#[no_mangle]
97pub extern "C" fn __quantum__rt__bool_to_string(input: bool) -> *const CString {
98    convert(&input)
99}
100
101#[no_mangle]
102pub extern "C" fn __quantum__rt__pauli_to_string(input: Pauli) -> *const CString {
103    match input {
104        Pauli::I => convert(&"PauliI"),
105        Pauli::X => convert(&"PauliX"),
106        Pauli::Y => convert(&"PauliY"),
107        Pauli::Z => convert(&"PauliZ"),
108    }
109}
110
111#[no_mangle]
112pub unsafe extern "C" fn __quantum__rt__bigint_to_string(input: *const BigInt) -> *const CString {
113    convert(&*input)
114}
115
116#[cfg(test)]
117mod tests {
118    use std::mem::ManuallyDrop;
119
120    use super::*;
121    use crate::bigints::{
122        __quantum__rt__bigint_create_array, __quantum__rt__bigint_create_i64,
123        __quantum__rt__bigint_update_reference_count,
124    };
125
126    #[test]
127    fn test_string_create() {
128        let orig_str = CString::new("Test String").unwrap();
129        let str = unsafe {
130            __quantum__rt__string_create(
131                orig_str.as_bytes_with_nul().as_ptr() as *mut std::ffi::c_char
132            )
133        };
134        // string_create should make a copy, not consume original.
135        assert_eq!(orig_str.to_str().unwrap(), "Test String");
136        drop(orig_str);
137        assert!(!str.is_null());
138        unsafe {
139            // Copy should be valid after original is dropped.
140            assert_eq!(
141                Rc::from_raw(str)
142                    .to_str()
143                    .expect("Unable to convert input string"),
144                "Test String"
145            );
146        }
147    }
148
149    #[test]
150    fn test_string_get_data() {
151        let str = unsafe {
152            __quantum__rt__string_create(
153                CString::new("Data").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char
154            )
155        };
156        unsafe {
157            assert_eq!(
158                CStr::from_ptr(__quantum__rt__string_get_data(str))
159                    .to_str()
160                    .unwrap(),
161                "Data"
162            );
163        }
164        unsafe {
165            __quantum__rt__string_update_reference_count(str, -1);
166        }
167    }
168
169    #[test]
170    fn test_string_get_length() {
171        let str = unsafe {
172            __quantum__rt__string_create(
173                CString::new("Data").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char
174            )
175        };
176        assert_eq!(unsafe { __quantum__rt__string_get_length(str) }, 4);
177        unsafe {
178            __quantum__rt__string_update_reference_count(str, -1);
179        }
180    }
181
182    #[test]
183    fn test_string_update_reference_count() {
184        unsafe {
185            let str = __quantum__rt__string_create(
186                CString::new("Data").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char,
187            );
188            let rc = ManuallyDrop::new(Rc::from_raw(str));
189            assert_eq!(Rc::strong_count(&rc), 1);
190            __quantum__rt__string_update_reference_count(str, 2);
191            assert_eq!(Rc::strong_count(&rc), 3);
192            __quantum__rt__string_update_reference_count(str, -2);
193            assert_eq!(Rc::strong_count(&rc), 1);
194            __quantum__rt__string_update_reference_count(str, -1);
195        }
196    }
197
198    #[test]
199    fn test_string_concatenate() {
200        unsafe {
201            let str1 = __quantum__rt__string_create(
202                CString::new("Hello").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char,
203            );
204            let str2 = __quantum__rt__string_create(
205                CString::new(", World!")
206                    .unwrap()
207                    .as_bytes_with_nul()
208                    .as_ptr() as *mut c_char,
209            );
210            let str3 = __quantum__rt__string_concatenate(str1, str2);
211            // Concatenated string should have combined value.
212            let rc = ManuallyDrop::new(Rc::from_raw(str3));
213            assert_eq!(Rc::strong_count(&rc), 1);
214            assert_eq!(
215                CStr::from_ptr(__quantum__rt__string_get_data(str3))
216                    .to_str()
217                    .unwrap(),
218                "Hello, World!"
219            );
220            __quantum__rt__string_update_reference_count(str3, -1);
221            // After decrement and drop, original strings should still be valid.
222            let rc = ManuallyDrop::new(Rc::from_raw(str2));
223            assert_eq!(Rc::strong_count(&rc), 1);
224            assert_eq!(
225                CStr::from_ptr(__quantum__rt__string_get_data(str2))
226                    .to_str()
227                    .unwrap(),
228                ", World!"
229            );
230            __quantum__rt__string_update_reference_count(str2, -1);
231            let rc = ManuallyDrop::new(Rc::from_raw(str1));
232            assert_eq!(Rc::strong_count(&rc), 1);
233            assert_eq!(
234                CStr::from_ptr(__quantum__rt__string_get_data(str1))
235                    .to_str()
236                    .unwrap(),
237                "Hello"
238            );
239            __quantum__rt__string_update_reference_count(str1, -1);
240        }
241    }
242
243    #[test]
244    fn test_string_equal() {
245        unsafe {
246            let str1 = __quantum__rt__string_create(
247                CString::new("Data").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char,
248            );
249            let str2 = __quantum__rt__string_create(
250                CString::new("Data").unwrap().as_bytes_with_nul().as_ptr() as *mut c_char,
251            );
252            let str3 = __quantum__rt__string_create(
253                CString::new("Not Data")
254                    .unwrap()
255                    .as_bytes_with_nul()
256                    .as_ptr() as *mut c_char,
257            );
258            assert!(__quantum__rt__string_equal(str1, str2));
259            assert!(!__quantum__rt__string_equal(str1, str3));
260            // Confirm data is still valid and not consumed by the check.
261            let rc = ManuallyDrop::new(Rc::from_raw(str3));
262            assert_eq!(Rc::strong_count(&rc), 1);
263            assert_eq!(
264                CStr::from_ptr(__quantum__rt__string_get_data(str3))
265                    .to_str()
266                    .unwrap(),
267                "Not Data"
268            );
269            __quantum__rt__string_update_reference_count(str3, -1);
270            let rc = ManuallyDrop::new(Rc::from_raw(str2));
271            assert_eq!(Rc::strong_count(&rc), 1);
272            assert_eq!(
273                CStr::from_ptr(__quantum__rt__string_get_data(str2))
274                    .to_str()
275                    .unwrap(),
276                "Data"
277            );
278            __quantum__rt__string_update_reference_count(str2, -1);
279            let rc = ManuallyDrop::new(Rc::from_raw(str1));
280            assert_eq!(Rc::strong_count(&rc), 1);
281            assert_eq!(
282                CStr::from_ptr(__quantum__rt__string_get_data(str1))
283                    .to_str()
284                    .unwrap(),
285                "Data"
286            );
287            __quantum__rt__string_update_reference_count(str1, -1);
288        }
289    }
290
291    #[test]
292    fn test_to_string() {
293        let str0 = __quantum__rt__int_to_string(42_i64);
294        unsafe {
295            assert_eq!(
296                CStr::from_ptr(__quantum__rt__string_get_data(str0))
297                    .to_str()
298                    .unwrap(),
299                "42"
300            );
301        }
302        let str1 = __quantum__rt__double_to_string(4.2_f64);
303        unsafe {
304            assert_eq!(
305                CStr::from_ptr(__quantum__rt__string_get_data(str1))
306                    .to_str()
307                    .unwrap(),
308                "4.2"
309            );
310        }
311        let str1_1 = __quantum__rt__double_to_string(4.0_f64);
312        unsafe {
313            assert_eq!(
314                CStr::from_ptr(__quantum__rt__string_get_data(str1_1))
315                    .to_str()
316                    .unwrap(),
317                "4.0"
318            );
319        }
320        let str1_2 = __quantum__rt__double_to_string(0.1_f64);
321        unsafe {
322            assert_eq!(
323                CStr::from_ptr(__quantum__rt__string_get_data(str1_2))
324                    .to_str()
325                    .unwrap(),
326                "0.1"
327            );
328        }
329        let str1_3 = __quantum__rt__double_to_string(0.100_000_000_01_f64);
330        unsafe {
331            assert_eq!(
332                CStr::from_ptr(__quantum__rt__string_get_data(str1_3))
333                    .to_str()
334                    .unwrap(),
335                "0.10000000001"
336            );
337        }
338        let str2 = __quantum__rt__bool_to_string(false);
339        unsafe {
340            assert_eq!(
341                CStr::from_ptr(__quantum__rt__string_get_data(str2))
342                    .to_str()
343                    .unwrap(),
344                "false"
345            );
346        }
347        let str3 = __quantum__rt__pauli_to_string(Pauli::Z);
348        unsafe {
349            assert_eq!(
350                CStr::from_ptr(__quantum__rt__string_get_data(str3))
351                    .to_str()
352                    .unwrap(),
353                "PauliZ"
354            );
355        }
356        let input4 = __quantum__rt__bigint_create_i64(400_002);
357        unsafe {
358            let str4 = __quantum__rt__bigint_to_string(input4);
359            assert_eq!(
360                CStr::from_ptr(__quantum__rt__string_get_data(str4))
361                    .to_str()
362                    .unwrap(),
363                "400002"
364            );
365            __quantum__rt__string_update_reference_count(str4, -1);
366        }
367        unsafe {
368            let bytes = [0x18, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0x01];
369            let input5 =
370                __quantum__rt__bigint_create_array(bytes.len().try_into().unwrap(), bytes.as_ptr());
371            let str5 = __quantum__rt__bigint_to_string(input5);
372            assert_eq!(
373                CStr::from_ptr(__quantum__rt__string_get_data(str5))
374                    .to_str()
375                    .unwrap(),
376                "9223372036854775807000"
377            );
378            __quantum__rt__string_update_reference_count(str0, -1);
379            __quantum__rt__string_update_reference_count(str1, -1);
380            __quantum__rt__string_update_reference_count(str1_1, -1);
381            __quantum__rt__string_update_reference_count(str1_2, -1);
382            __quantum__rt__string_update_reference_count(str1_3, -1);
383            __quantum__rt__string_update_reference_count(str2, -1);
384            __quantum__rt__string_update_reference_count(str3, -1);
385            __quantum__rt__bigint_update_reference_count(input4, -1);
386            __quantum__rt__bigint_update_reference_count(input5, -1);
387        }
388    }
389}