qir_stdlib/
strings.rs

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