Skip to content
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: 0 additions & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,6 @@ jobs:
echo "RUSTFLAGS=-D warnings -C link-arg=-fuse-ld=lld" >>"$GITHUB_ENV"
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
if: runner.os != 'Windows'
- uses: cargo-bins/[email protected]
if: runner.os == 'Windows'
- name: Install cargo-nextest
if: runner.os == 'Windows'
run: cargo binstall --no-confirm --force cargo-nextest
- uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-${{ runner.os }}-${{ env.LLVM_RUST_CACHE_FAMILY }}
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Rule of thumb:
- Prefer structure-aware fuzzing over arbitrary raw byte mutation when the goal is to exercise `qir-qis` logic rather than LLVM parser failure paths.
- If expensive fixture compilation is repeated across many tests, cache it with `LazyLock` or similar.
- Keep `make mutants` useful: kill meaningful mutants with tests, and keep `.cargo/mutants.toml` exclusions resilient to line movement.
- For external/runtime signature validation, prefer table-driven negative tests that cover return type, arity, and parameter kind/width; for numeric limits, cover both the largest accepted value and first rejected value.
- During validation-helper work, run a scoped mutation check such as `cargo mutants --package qir-qis --all-features --test-tool cargo --file src/lib.rs --re '<helper_name>'`.

## LLVM and platform guidance

Expand Down
168 changes: 168 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3846,6 +3846,39 @@ attributes #0 = {{ {rendered_attrs} }}
)
}

fn minimal_dynamic_rt_declaration_qir(declaration: &str) -> String {
format!(
r#"
define i64 @Entry_Point_Name() #0 {{
entry:
ret i64 0
}}

{declaration}

attributes #0 = {{ "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" "required_num_results"="1" }}

!llvm.module.flags = !{{!0, !1, !2, !3, !4}}
!0 = !{{i32 1, !"qir_major_version", i32 2}}
!1 = !{{i32 7, !"qir_minor_version", i32 0}}
!2 = !{{i32 1, !"dynamic_qubit_management", i1 true}}
!3 = !{{i32 1, !"dynamic_result_management", i1 true}}
!4 = !{{i32 1, !"arrays", i1 true}}
"#
)
}

fn assert_malformed_dynamic_rt_declaration(case_name: &str, declaration: &str, fn_name: &str) {
let ll_text = minimal_dynamic_rt_declaration_qir(declaration);
let bc_bytes = qir_ll_to_bc(&ll_text).unwrap();
let err = validate_qir(&bc_bytes, None)
.expect_err(&format!("{case_name} should fail validation"));
assert!(
err.contains(&format!("Malformed QIR RT function declaration: {fn_name}")),
"{case_name} reported unexpected error: {err}"
);
}

fn minimal_qir_with_duplicate_major_flags(first_major: &str, second_major: &str) -> String {
format!(
r#"
Expand Down Expand Up @@ -5274,6 +5307,114 @@ attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeli
);
}

#[test]
fn test_validate_qir_rejects_malformed_dynamic_allocate_return_signature() {
let cases = [
(
"declare void @__quantum__rt__qubit_allocate(ptr)",
"__quantum__rt__qubit_allocate",
),
(
"declare void @__quantum__rt__result_allocate(ptr)",
"__quantum__rt__result_allocate",
),
];

for (declaration, fn_name) in cases {
assert_malformed_dynamic_rt_declaration(fn_name, declaration, fn_name);
}
}

#[test]
fn test_validate_qir_rejects_malformed_dynamic_release_signatures() {
let cases = [
(
"wrong return",
"declare ptr @__quantum__rt__qubit_release(ptr)",
"__quantum__rt__qubit_release",
),
(
"wrong arity",
"declare void @__quantum__rt__qubit_release()",
"__quantum__rt__qubit_release",
),
(
"wrong param type",
"declare void @__quantum__rt__result_release(i64)",
"__quantum__rt__result_release",
),
];

for (case_name, declaration, fn_name) in cases {
assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
}
}

#[test]
fn test_validate_qir_rejects_malformed_dynamic_array_three_arg_signatures() {
let cases = [
(
"wrong return",
"declare ptr @__quantum__rt__qubit_array_allocate(i64, ptr, ptr)",
"__quantum__rt__qubit_array_allocate",
),
(
"wrong arity",
"declare void @__quantum__rt__qubit_array_allocate(i64, ptr)",
"__quantum__rt__qubit_array_allocate",
),
(
"wrong length type",
"declare void @__quantum__rt__result_array_allocate(i32, ptr, ptr)",
"__quantum__rt__result_array_allocate",
),
(
"wrong backing param type",
"declare void @__quantum__rt__result_array_allocate(i64, i64, ptr)",
"__quantum__rt__result_array_allocate",
),
(
"wrong output param type",
"declare void @__quantum__rt__result_array_record_output(i64, ptr, i64)",
"__quantum__rt__result_array_record_output",
),
];

for (case_name, declaration, fn_name) in cases {
assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
}
}

#[test]
fn test_validate_qir_rejects_malformed_dynamic_array_release_signatures() {
let cases = [
(
"wrong return",
"declare ptr @__quantum__rt__qubit_array_release(i64, ptr)",
"__quantum__rt__qubit_array_release",
),
(
"wrong arity",
"declare void @__quantum__rt__qubit_array_release(i64)",
"__quantum__rt__qubit_array_release",
),
(
"wrong length type",
"declare void @__quantum__rt__result_array_release(i32, ptr)",
"__quantum__rt__result_array_release",
),
(
"wrong backing param type",
"declare void @__quantum__rt__result_array_release(i64, i64)",
"__quantum__rt__result_array_release",
),
];

for (case_name, declaration, fn_name) in cases {
assert_malformed_dynamic_rt_declaration(case_name, declaration, fn_name);
}
}

#[test]
fn test_validate_qir_rejects_unsupported_rt_function_declaration() {
let ll_text = r#"
Expand Down Expand Up @@ -5485,6 +5626,33 @@ attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeli
));
}

#[test]
fn test_validate_dynamic_result_array_record_output_i32_max_length_succeeds() {
let ll_text = r#"
@0 = internal constant [3 x i8] c"a0\00"

define i64 @Entry_Point_Name() #0 {
entry:
%results = alloca [2147483647 x ptr], align 8
call void @__quantum__rt__result_array_record_output(i64 2147483647, ptr %results, ptr @0)
ret i64 0
}

declare void @__quantum__rt__result_array_record_output(i64, ptr, ptr)

attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "output_labeling_schema"="schema_id" "required_num_qubits"="1" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!0 = !{i32 1, !"qir_major_version", i32 2}
!1 = !{i32 7, !"qir_minor_version", i32 0}
!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
!3 = !{i32 1, !"dynamic_result_management", i1 true}
!4 = !{i32 1, !"arrays", i1 true}
"#;
let bc_bytes = qir_ll_to_bc(ll_text).unwrap();
validate_qir(&bc_bytes, None).expect("i32::MAX result array output should validate");
}

#[test]
fn test_validate_dynamic_result_allocate_outside_entry_block_fails() {
let ll_text = r#"
Expand Down
Loading