runner/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![deny(clippy::all, clippy::pedantic)]
5#![allow(unused)]
6
7mod cli;
8pub use cli::main;
9
10pub use qir_backend::{
11    arrays::*, bigints::*, callables::*, exp::*, math::*, output_recording::*, range_support::*,
12    result_bool::*, strings::*, tuples::*, *,
13};
14
15use inkwell::{
16    OptimizationLevel,
17    attributes::AttributeLoc,
18    context::Context,
19    execution_engine::ExecutionEngine,
20    llvm_sys::{core::LLVMCreateMemoryBufferWithMemoryRange, ir_reader::LLVMParseIRInContext},
21    memory_buffer::MemoryBuffer,
22    module::Module,
23    passes::{PassBuilderOptions, PassManager},
24    targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple},
25    values::FunctionValue,
26};
27use std::{
28    collections::HashMap,
29    ffi::{CStr, CString, OsStr, c_char},
30    io::{Read, Write},
31    iter::once,
32    path::Path,
33    ptr::{self, NonNull, null_mut},
34};
35
36/// # Errors
37///
38/// Will return `Err` if
39/// - `filename` does not exist or the user does not have permission to read it.
40/// - `filename` does not contain a valid bitcode module
41/// - `filename` does not have either a .ll or .bc as an extension
42/// - `entry_point` is not found in the QIR
43/// - Entry point has parameters or a non-void return type.
44pub fn run_file(
45    path: impl AsRef<Path>,
46    entry_point: Option<&str>,
47    shots: u32,
48    rng_seed: Option<u64>,
49    output_writer: &mut impl Write,
50) -> Result<(), String> {
51    if let Some(seed) = rng_seed {
52        qir_backend::set_rng_seed(seed);
53    }
54    let context = Context::create();
55    let module = load_file(path, &context)?;
56    run_module(&module, entry_point, shots, output_writer)
57}
58
59/// # Errors
60///
61/// Will return `Err` if
62/// - The input cannot be read from `input`.
63/// - The input is empty.
64/// - The input does not contain a valid bitcode module or LLVM IR string.
65/// - `entry_point` is not found in the QIR.
66/// - Entry point has parameters or a non-void return type.
67pub fn run_input<R: Read>(
68    input: &mut R,
69    entry_point: Option<&str>,
70    shots: u32,
71    rng_seed: Option<u64>,
72    output_writer: &mut impl Write,
73) -> Result<(), String> {
74    let mut bytes = Vec::new();
75    input
76        .read_to_end(&mut bytes)
77        .map_err(|e| format!("Failed to read input: {e}"))?;
78    if bytes.is_empty() {
79        return Err("Input is empty".to_string());
80    }
81    run_bytes(&bytes, entry_point, shots, rng_seed, output_writer)
82}
83
84/// # Errors
85///
86/// Will return `Err` if
87/// - `bytes` does not contain a valid bitcode module
88/// - `entry_point` is not found in the QIR
89/// - Entry point has parameters or a non-void return type.
90pub fn run_bitcode(
91    bytes: &[u8],
92    entry_point: Option<&str>,
93    shots: u32,
94    output_writer: &mut impl Write,
95) -> Result<(), String> {
96    run_bytes(bytes, entry_point, shots, None, output_writer)
97}
98
99/// # Errors
100///
101/// Will return `Err` if
102/// - `bytes` does not contain a valid bitcode module or LLVM IR string
103/// - `entry_point` is not found in the QIR
104/// - Entry point has parameters or a non-void return type.
105pub fn run_bytes(
106    bytes: &[u8],
107    entry_point: Option<&str>,
108    shots: u32,
109    rng_seed: Option<u64>,
110    output_writer: &mut impl Write,
111) -> Result<(), String> {
112    if let Some(seed) = rng_seed {
113        qir_backend::set_rng_seed(seed);
114    }
115
116    let context = Context::create();
117
118    // To know if the bytes are bitcode, check for both the wrapped and non-wrapped magic bytes.
119    // See the definition for llvm::isBitCode at https://llvm.org/doxygen/namespacellvm.html#ae0ccf1c0633b02c90c21118d0c1c7ec4
120    // for reference.
121    let bytes_len = bytes.len();
122    if bytes_len < 4 {
123        return Err("byte array is too short".to_string());
124    }
125    let is_bitcode =
126        bytes[0..4] == [0xDE, 0xC0, 0x17, 0x0B] || bytes[0..4] == [0x42, 0x43, 0xC0, 0xDE];
127    let bytes = if is_bitcode {
128        bytes.to_vec()
129    } else {
130        // The bytes represent LLVM IR string, so we must ensure it is null-terminated.
131        // Note that we use the original bytes length to avoid including the null terminator in the IR parsing, which would cause it to fail.
132        bytes.iter().copied().chain(once(0_u8)).collect()
133    };
134
135    let buffer = MemoryBuffer::create_from_memory_range(&bytes[0..bytes_len], Default::default());
136    context
137        .create_module_from_ir(buffer)
138        .map_err(|e| format!("Failed to parse module from IR: {}", e.to_string()))
139        .and_then(|module| run_module(&module, entry_point, shots, output_writer))
140}
141
142fn run_module(
143    module: &Module,
144    entry_point: Option<&str>,
145    shots: u32,
146    output_writer: &mut impl Write,
147) -> Result<(), String> {
148    module
149        .verify()
150        .map_err(|e| format!("Failed to verify module: {}", e.to_string()))?;
151
152    Target::initialize_native(&InitializationConfig::default())?;
153    let default_triple = TargetMachine::get_default_triple();
154    let target = Target::from_triple(&default_triple).map_err(|e| e.to_string())?;
155    if !target.has_asm_backend() {
156        return Err("Target doesn't have an ASM backend.".to_owned());
157    }
158    if !target.has_target_machine() {
159        return Err("Target doesn't have a target machine.".to_owned());
160    }
161
162    run_basic_passes_on(module, &default_triple, &target)?;
163
164    inkwell::support::load_library_permanently(Path::new(""));
165
166    let execution_engine = module
167        .create_jit_execution_engine(OptimizationLevel::None)
168        .map_err(|e| e.to_string())?;
169
170    bind_functions(module, &execution_engine)?;
171
172    let entry_point = choose_entry_point(module_functions(module), entry_point)?;
173    // TODO: need a cleaner way to get the attr strings for metadata
174    let attrs: Vec<(String, String)> = entry_point
175        .attributes(AttributeLoc::Function)
176        .iter()
177        .map(|attr| {
178            (
179                attr.get_string_kind_id()
180                    .to_str()
181                    .expect("Invalid UTF8 data")
182                    .to_string(),
183                attr.get_string_value()
184                    .to_str()
185                    .expect("Invalid UTF8 data")
186                    .to_string(),
187            )
188        })
189        .collect();
190
191    for _ in 1..=shots {
192        output_writer
193            .write_all("START\n".as_bytes())
194            .expect("Failed to write output");
195        for attr in &attrs {
196            output_writer
197                .write_all(format!("METADATA\t{}", attr.0).as_bytes())
198                .expect("Failed to write output");
199            if !attr.1.is_empty() {
200                output_writer
201                    .write_all(format!("\t{}", attr.1).as_bytes())
202                    .expect("Failed to write output");
203            }
204            output_writer
205                .write_all(qir_stdlib::output_recording::LINE_ENDING)
206                .expect("Failed to write output");
207        }
208
209        __quantum__rt__initialize(null_mut());
210        unsafe { run_entry_point(&execution_engine, entry_point)? }
211
212        // Write the saved output records to the output_writer
213        OUTPUT.with(|output| {
214            let mut output = output.borrow_mut();
215            output_writer
216                .write_all(output.drain().as_slice())
217                .expect("Failed to write output");
218        });
219
220        // Write the end of the shot
221        output_writer
222            .write_all("END\t0".as_bytes())
223            .expect("Failed to write output");
224        output_writer
225            .write_all(qir_stdlib::output_recording::LINE_ENDING)
226            .expect("Failed to write output");
227    }
228    Ok(())
229}
230
231fn load_file(path: impl AsRef<Path>, context: &Context) -> Result<Module<'_>, String> {
232    let path = path.as_ref();
233    let extension = path.extension().and_then(OsStr::to_str);
234
235    match extension {
236        Some("ll") => MemoryBuffer::create_from_file(path)
237            .and_then(|buffer| context.create_module_from_ir(buffer))
238            .map_err(|e| e.to_string()),
239        Some("bc") => Module::parse_bitcode_from_path(path, context).map_err(|e| e.to_string()),
240        _ => Err(format!("Unsupported file extension '{extension:?}'.")),
241    }
242}
243
244unsafe fn run_entry_point(
245    execution_engine: &ExecutionEngine,
246    entry_point: FunctionValue,
247) -> Result<(), String> {
248    unsafe {
249        if entry_point.count_params() == 0 {
250            execution_engine.run_function(entry_point, &[]);
251            Ok(())
252        } else {
253            Err("Entry point has parameters or a non-void return type.".to_owned())
254        }
255    }
256}
257
258fn choose_entry_point<'ctx>(
259    functions: impl Iterator<Item = FunctionValue<'ctx>>,
260    name: Option<&str>,
261) -> Result<FunctionValue<'ctx>, String> {
262    let mut entry_points = functions
263        .filter(|f| is_entry_point(*f) && name.iter().all(|n| f.get_name().to_str() == Ok(n)));
264
265    let entry_point = entry_points
266        .next()
267        .ok_or_else(|| "No matching entry point found.".to_owned())?;
268
269    if entry_points.next().is_some() {
270        Err("Multiple matching entry points found.".to_owned())
271    } else {
272        Ok(entry_point)
273    }
274}
275
276fn module_functions<'ctx>(module: &Module<'ctx>) -> impl Iterator<Item = FunctionValue<'ctx>> {
277    struct FunctionValueIter<'ctx>(Option<FunctionValue<'ctx>>);
278
279    impl<'ctx> Iterator for FunctionValueIter<'ctx> {
280        type Item = FunctionValue<'ctx>;
281
282        fn next(&mut self) -> Option<Self::Item> {
283            let function = self.0;
284            self.0 = function.and_then(inkwell::values::FunctionValue::get_next_function);
285            function
286        }
287    }
288
289    FunctionValueIter(module.get_first_function())
290}
291
292fn is_entry_point(function: FunctionValue) -> bool {
293    function
294        .get_string_attribute(AttributeLoc::Function, "entry_point")
295        .is_some()
296        || function
297            .get_string_attribute(AttributeLoc::Function, "EntryPoint")
298            .is_some()
299}
300
301fn run_basic_passes_on(
302    module: &Module,
303    target_triple: &TargetTriple,
304    target: &Target,
305) -> Result<(), String> {
306    // Description of this syntax:
307    // https://github.com/llvm/llvm-project/blob/2ba08386156ef25913b1bee170d8fe95aaceb234/llvm/include/llvm/Passes/PassBuilder.h#L308-L347
308    const BASIC_PASS_PIPELINE: &str = "globaldce,strip-dead-prototypes";
309
310    // Boilerplate taken from here:
311    // https://github.com/TheDan64/inkwell/blob/5c9f7fcbb0a667f7391b94beb65f1a670ad13221/examples/kaleidoscope/main.rs#L86-L95
312    let target_machine = target
313        .create_target_machine(
314            target_triple,
315            "generic",
316            "",
317            OptimizationLevel::None,
318            RelocMode::Default,
319            CodeModel::Default,
320        )
321        .ok_or("Unable to create TargetMachine from Target")?;
322    module
323        .run_passes(
324            BASIC_PASS_PIPELINE,
325            &target_machine,
326            PassBuilderOptions::create(),
327        )
328        .map_err(|e| e.to_string())
329}
330
331#[allow(clippy::too_many_lines)]
332fn bind_functions(module: &Module, execution_engine: &ExecutionEngine) -> Result<(), String> {
333    let mut uses_legacy = vec![];
334    let mut declarations: HashMap<String, FunctionValue> = HashMap::default();
335    for func in module_functions(module).filter(|f| {
336        f.count_basic_blocks() == 0
337            && !f
338                .get_name()
339                .to_str()
340                .expect("Unable to coerce function name into str.")
341                .starts_with("llvm.")
342    }) {
343        declarations.insert(
344            func.get_name()
345                .to_str()
346                .expect("Unable to coerce function name into str.")
347                .to_owned(),
348            func,
349        );
350    }
351
352    macro_rules! bind {
353        ($func:ident, $param_count:expr) => {
354            if let Some(func) = declarations.get(stringify!($func)) {
355                if func.get_params().len() != $param_count {
356                    return Err(format!(
357                        "Function '{}' has mismatched parameters: expected {}, found {}",
358                        stringify!($func),
359                        $param_count,
360                        func.get_params().len()
361                    ));
362                }
363                execution_engine.add_global_mapping(func, $func as *const () as usize);
364                declarations.remove(stringify!($func));
365            }
366        };
367    }
368
369    macro_rules! legacy_output {
370        ($func:ident) => {
371            if let Some(func) = declarations.get(stringify!($func)) {
372                execution_engine.add_global_mapping(
373                    func,
374                    qir_backend::output_recording::legacy::$func as *const () as usize,
375                );
376                declarations.remove(stringify!($func));
377                Some(true)
378            } else {
379                None
380            }
381        };
382    }
383
384    macro_rules! bind_output_record {
385        ($func:ident) => {
386            if let Some(func) = declarations.get(stringify!($func)) {
387                if func.get_params().len() == 1 {
388                    execution_engine.add_global_mapping(
389                        func,
390                        qir_backend::output_recording::legacy::$func as *const () as usize,
391                    );
392                    declarations.remove(stringify!($func));
393                    Some(true)
394                } else {
395                    execution_engine.add_global_mapping(func, $func as *const () as usize);
396                    declarations.remove(stringify!($func));
397                    Some(false)
398                }
399            } else {
400                None
401            }
402        };
403    }
404
405    // Legacy output methods
406    uses_legacy.push(legacy_output!(__quantum__rt__array_end_record_output));
407    uses_legacy.push(legacy_output!(__quantum__rt__array_start_record_output));
408    uses_legacy.push(legacy_output!(__quantum__rt__tuple_end_record_output));
409    uses_legacy.push(legacy_output!(__quantum__rt__tuple_start_record_output));
410
411    bind!(__quantum__rt__initialize, 1);
412    bind!(__quantum__qis__arccos__body, 1);
413    bind!(__quantum__qis__arcsin__body, 1);
414    bind!(__quantum__qis__arctan__body, 1);
415    bind!(__quantum__qis__arctan2__body, 2);
416    bind!(__quantum__qis__assertmeasurementprobability__body, 6);
417    bind!(__quantum__qis__assertmeasurementprobability__ctl, 6);
418    bind!(__quantum__qis__barrier__body, 0);
419    bind!(__quantum__qis__ccx__body, 3);
420    bind!(__quantum__qis__cnot__body, 2);
421    bind!(__quantum__qis__cos__body, 1);
422    bind!(__quantum__qis__cosh__body, 1);
423    bind!(__quantum__qis__cx__body, 2);
424    bind!(__quantum__qis__cy__body, 2);
425    bind!(__quantum__qis__cz__body, 2);
426    bind!(__quantum__qis__drawrandomdouble__body, 2);
427    bind!(__quantum__qis__drawrandomint__body, 2);
428    bind!(__quantum__qis__dumpmachine__body, 1);
429    bind!(__quantum__qis__exp__body, 3);
430    bind!(__quantum__qis__exp__adj, 3);
431    bind!(__quantum__qis__exp__ctl, 2);
432    bind!(__quantum__qis__exp__ctladj, 2);
433    bind!(__quantum__qis__h__body, 1);
434    bind!(__quantum__qis__h__ctl, 2);
435    bind!(__quantum__qis__ieeeremainder__body, 2);
436    bind!(__quantum__qis__infinity__body, 0);
437    bind!(__quantum__qis__isinf__body, 1);
438    bind!(__quantum__qis__isnan__body, 1);
439    bind!(__quantum__qis__isnegativeinfinity__body, 1);
440    bind!(__quantum__qis__log__body, 1);
441    bind!(__quantum__qis__measure__body, 2);
442    bind!(__quantum__qis__mresetz__body, 2);
443    bind!(__quantum__qis__mz__body, 2);
444    bind!(__quantum__qis__nan__body, 0);
445    bind!(__quantum__qis__r__adj, 3);
446    bind!(__quantum__qis__r__body, 3);
447    bind!(__quantum__qis__r__ctl, 2);
448    bind!(__quantum__qis__r__ctladj, 2);
449    bind!(__quantum__qis__read_result__body, 1);
450    bind!(__quantum__qis__reset__body, 1);
451    bind!(__quantum__qis__rx__body, 2);
452    bind!(__quantum__qis__rx__ctl, 2);
453    bind!(__quantum__qis__rxx__body, 3);
454    bind!(__quantum__qis__ry__body, 2);
455    bind!(__quantum__qis__ry__ctl, 2);
456    bind!(__quantum__qis__ryy__body, 3);
457    bind!(__quantum__qis__rz__body, 2);
458    bind!(__quantum__qis__rz__ctl, 2);
459    bind!(__quantum__qis__rzz__body, 3);
460    bind!(__quantum__qis__s__adj, 1);
461    bind!(__quantum__qis__s__body, 1);
462    bind!(__quantum__qis__s__ctl, 2);
463    bind!(__quantum__qis__s__ctladj, 2);
464    bind!(__quantum__qis__sx__body, 1);
465    bind!(__quantum__qis__sin__body, 1);
466    bind!(__quantum__qis__sinh__body, 1);
467    bind!(__quantum__qis__sqrt__body, 1);
468    bind!(__quantum__qis__swap__body, 2);
469    bind!(__quantum__qis__t__adj, 1);
470    bind!(__quantum__qis__t__body, 1);
471    bind!(__quantum__qis__t__ctl, 2);
472    bind!(__quantum__qis__t__ctladj, 2);
473    bind!(__quantum__qis__tan__body, 1);
474    bind!(__quantum__qis__tanh__body, 1);
475    bind!(__quantum__qis__x__body, 1);
476    bind!(__quantum__qis__x__ctl, 2);
477    bind!(__quantum__qis__y__body, 1);
478    bind!(__quantum__qis__y__ctl, 2);
479    bind!(__quantum__qis__z__body, 1);
480    bind!(__quantum__qis__z__ctl, 2);
481    bind!(__quantum__rt__array_concatenate, 2);
482    bind!(__quantum__rt__array_copy, 2);
483    bind!(__quantum__rt__array_create_1d, 2);
484
485    // New calls
486    bind!(__quantum__rt__array_record_output, 2);
487    bind!(__quantum__rt__tuple_record_output, 2);
488
489    // calls with unlabeled signature variants
490    uses_legacy.push(bind_output_record!(__quantum__rt__bool_record_output));
491    uses_legacy.push(bind_output_record!(__quantum__rt__double_record_output));
492    uses_legacy.push(bind_output_record!(__quantum__rt__int_record_output));
493
494    // results need special handling as they aren't in the std lib
495    uses_legacy.push(
496        if let Some(func) = declarations.get("__quantum__rt__result_record_output") {
497            if func.get_params().len() == 1 {
498                execution_engine.add_global_mapping(
499                    func,
500                    qir_backend::legacy_output::__quantum__rt__result_record_output as *const ()
501                        as usize,
502                );
503                declarations.remove("__quantum__rt__result_record_output");
504                Some(true)
505            } else {
506                execution_engine.add_global_mapping(
507                    func,
508                    __quantum__rt__result_record_output as *const () as usize,
509                );
510                declarations.remove("__quantum__rt__result_record_output");
511                Some(false)
512            }
513        } else {
514            None
515        },
516    );
517
518    // calls to __quantum__qis__m__body may use either dynamic or static results, so bind to the right
519    // implementation based on number of arguments.
520    if let Some(func) = declarations.get("__quantum__qis__m__body") {
521        if func.get_params().len() == 2 {
522            execution_engine.add_global_mapping(
523                func,
524                qir_backend::__quantum__qis__mz__body as *const () as usize,
525            );
526        } else if func.get_params().len() == 1 {
527            execution_engine.add_global_mapping(
528                func,
529                qir_backend::__quantum__qis__m__body as *const () as usize,
530            );
531        } else {
532            return Err(format!(
533                "Function '__quantum__qis__m__body' has mismatched parameters: expected 1 or 2, found {}",
534                func.get_params().len()
535            ));
536        }
537        declarations.remove("__quantum__qis__m__body");
538    }
539
540    bind!(__quantum__rt__array_get_element_ptr_1d, 2);
541    bind!(__quantum__rt__array_get_size_1d, 1);
542    bind!(quantum__rt__array_slice_1d, 3);
543    bind!(__quantum__rt__array_update_alias_count, 2);
544    bind!(__quantum__rt__array_update_reference_count, 2);
545    bind!(__quantum__rt__bigint_add, 2);
546    bind!(__quantum__rt__bigint_bitand, 2);
547    bind!(__quantum__rt__bigint_bitnot, 1);
548    bind!(__quantum__rt__bigint_bitor, 2);
549    bind!(__quantum__rt__bigint_bitxor, 2);
550    bind!(__quantum__rt__bigint_create_array, 2);
551    bind!(__quantum__rt__bigint_create_i64, 1);
552    bind!(__quantum__rt__bigint_divide, 2);
553    bind!(__quantum__rt__bigint_equal, 2);
554    bind!(__quantum__rt__bigint_get_data, 1);
555    bind!(__quantum__rt__bigint_get_length, 1);
556    bind!(__quantum__rt__bigint_greater, 2);
557    bind!(__quantum__rt__bigint_greater_eq, 2);
558    bind!(__quantum__rt__bigint_modulus, 2);
559    bind!(__quantum__rt__bigint_multiply, 2);
560    bind!(__quantum__rt__bigint_negate, 1);
561    bind!(__quantum__rt__bigint_power, 2);
562    bind!(__quantum__rt__bigint_shiftleft, 2);
563    bind!(__quantum__rt__bigint_shiftright, 2);
564    bind!(__quantum__rt__bigint_subtract, 2);
565    bind!(__quantum__rt__bigint_to_string, 1);
566    bind!(__quantum__rt__bigint_update_reference_count, 2);
567    bind!(__quantum__rt__bool_to_string, 1);
568    bind!(__quantum__rt__callable_copy, 2);
569    bind!(__quantum__rt__callable_create, 3);
570    bind!(__quantum__rt__callable_invoke, 3);
571    bind!(__quantum__rt__callable_make_adjoint, 1);
572    bind!(__quantum__rt__callable_make_controlled, 1);
573    bind!(__quantum__rt__callable_update_alias_count, 2);
574    bind!(__quantum__rt__callable_update_reference_count, 2);
575    bind!(__quantum__rt__capture_update_alias_count, 2);
576    bind!(__quantum__rt__capture_update_reference_count, 2);
577    bind!(__quantum__rt__double_to_string, 1);
578    bind!(__quantum__rt__fail, 1);
579    bind!(__quantum__rt__int_to_string, 1);
580    bind!(__quantum__rt__memory_allocate, 1);
581    bind!(__quantum__rt__message, 1);
582    bind!(__quantum__rt__pauli_to_string, 1);
583    bind!(__quantum__rt__qubit_allocate, 0);
584    bind!(__quantum__rt__qubit_allocate_array, 1);
585    bind!(__quantum__rt__qubit_release, 1);
586    bind!(__quantum__rt__qubit_release_array, 1);
587    bind!(__quantum__rt__qubit_to_string, 1);
588    bind!(__quantum__rt__read_result, 1);
589    bind!(__quantum__rt__result_equal, 2);
590    bind!(quantum__rt__range_to_string, 1);
591    bind!(__quantum__rt__result_get_one, 0);
592    bind!(__quantum__rt__result_get_zero, 0);
593    bind!(__quantum__rt__result_to_string, 1);
594    bind!(__quantum__rt__result_update_reference_count, 2);
595    bind!(__quantum__rt__string_concatenate, 2);
596    bind!(__quantum__rt__string_create, 1);
597    bind!(__quantum__rt__string_equal, 2);
598    bind!(__quantum__rt__string_get_data, 1);
599    bind!(__quantum__rt__string_get_length, 1);
600    bind!(__quantum__rt__string_update_reference_count, 2);
601    bind!(__quantum__rt__tuple_copy, 2);
602    bind!(__quantum__rt__tuple_create, 1);
603    bind!(__quantum__rt__tuple_update_alias_count, 2);
604    bind!(__quantum__rt__tuple_update_reference_count, 2);
605    bind!(__quantum__rt__write_result, 2);
606
607    if !(uses_legacy.iter().filter_map(|&b| b).all(|b| b)
608        || uses_legacy.iter().filter_map(|&b| b).all(|b| !b))
609    {
610        Err("Use of legacy and current output recording functions in the same program is not supported".to_string())
611    } else if declarations.is_empty() {
612        Ok(())
613    } else {
614        let keys = declarations.keys().collect::<Vec<_>>();
615        let (first, rest) = keys
616            .split_first()
617            .expect("Declarations list should be non-empty.");
618        Err(format!(
619            "Failed to link some declared functions: {}",
620            rest.iter().fold((*first).clone(), |mut accum, f| {
621                accum.push_str(", ");
622                accum.push_str(f);
623                accum
624            })
625        ))
626    }
627}