Skip to content

Commit 0d35002

Browse files
committed
fix
1 parent 6007cf7 commit 0d35002

File tree

7 files changed

+208
-79
lines changed

7 files changed

+208
-79
lines changed

crates/pecos-llvm-utils/src/bin/pecos-llvm.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
//! Handles LLVM 14 detection, installation, and configuration for PECOS.
55
66
use clap::{Parser, Subcommand};
7-
use pecos_llvm_utils::{find_llvm_14, get_repo_root_from_manifest, print_llvm_not_found_error};
7+
use pecos_llvm_utils::{
8+
find_llvm_14, find_tool, get_repo_root_from_manifest, print_llvm_not_found_error,
9+
};
810
use std::process;
911

1012
#[derive(Parser)]
@@ -57,6 +59,11 @@ enum Commands {
5759
/// Path to the LLVM installation to validate
5860
path: std::path::PathBuf,
5961
},
62+
/// Find a specific LLVM tool (e.g., llvm-as, clang)
63+
Tool {
64+
/// Name of the tool to find (e.g., "llvm-as", "clang", "llvm-link")
65+
name: String,
66+
},
6067
}
6168

6269
fn main() {
@@ -86,6 +93,7 @@ fn main() {
8693
Commands::Configure => cmd_configure(),
8794
Commands::Version => cmd_version(),
8895
Commands::Validate { path } => cmd_validate(&path),
96+
Commands::Tool { name } => cmd_tool(&name),
8997
}
9098
}
9199

@@ -245,3 +253,16 @@ fn cmd_validate(path: &std::path::Path) {
245253
}
246254
}
247255
}
256+
257+
fn cmd_tool(tool_name: &str) {
258+
if let Some(tool_path) = find_tool(tool_name) {
259+
println!("{}", tool_path.display());
260+
process::exit(0);
261+
} else {
262+
eprintln!("ERROR: Tool '{tool_name}' not found");
263+
eprintln!();
264+
eprintln!("Make sure LLVM 14 is installed:");
265+
eprintln!(" cargo run -p pecos-llvm-utils --bin pecos-llvm -- check");
266+
process::exit(1);
267+
}
268+
}

crates/pecos-llvm-utils/src/lib.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,49 @@ pub fn find_cargo_project_root() -> Option<PathBuf> {
360360
}
361361
}
362362

363+
/// Find a specific LLVM tool by name
364+
///
365+
/// This function locates a specific LLVM tool (e.g., `llvm-as`, `clang`) by:
366+
/// 1. Finding the LLVM 14 installation
367+
/// 2. Constructing the tool path with proper OS-specific naming (e.g., `.exe` on Windows)
368+
/// 3. Verifying the tool exists
369+
///
370+
/// # Arguments
371+
/// * `tool_name` - The name of the tool (e.g., "llvm-as", "clang", "llvm-link")
372+
///
373+
/// # Returns
374+
/// * `Some(PathBuf)` if the tool is found
375+
/// * `None` if LLVM 14 is not found or the tool doesn't exist
376+
///
377+
/// # Example
378+
/// ```no_run
379+
/// use pecos_llvm_utils::find_tool;
380+
///
381+
/// if let Some(llvm_as) = find_tool("llvm-as") {
382+
/// println!("Found llvm-as at: {}", llvm_as.display());
383+
/// }
384+
/// ```
385+
#[must_use]
386+
pub fn find_tool(tool_name: &str) -> Option<PathBuf> {
387+
// Find LLVM installation
388+
let repo_root = get_repo_root_from_manifest();
389+
let llvm_path = find_llvm_14(repo_root)?;
390+
391+
// Construct tool path with OS-specific extension
392+
let tool_path = if cfg!(windows) {
393+
llvm_path.join("bin").join(format!("{tool_name}.exe"))
394+
} else {
395+
llvm_path.join("bin").join(tool_name)
396+
};
397+
398+
// Verify the tool exists
399+
if tool_path.exists() {
400+
Some(tool_path)
401+
} else {
402+
None
403+
}
404+
}
405+
363406
/// Write or update .cargo/config.toml with LLVM configuration
364407
///
365408
/// # Arguments

crates/pecos-qis-ffi-types/src/operations.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ pub enum Operation {
2020
/// Release a qubit
2121
ReleaseQubit { id: usize },
2222

23+
/// Record output mapping from result ID to classical register name
24+
RecordOutput {
25+
result_id: usize,
26+
register_name: String,
27+
},
28+
2329
/// Classical control flow marker
2430
Barrier,
2531
}

crates/pecos-qis-ffi/src/ffi.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -678,27 +678,38 @@ pub unsafe extern "C" fn panic(code: i32, message: *const std::ffi::c_char) {
678678
/// This is typically used to record measurement results to classical registers
679679
///
680680
/// # Safety
681-
/// This function is safe to call from C/LLVM code. The `result_id` parameter must be a valid
682-
/// non-negative result ID that fits in usize. The `register_name` pointer may be null or must
683-
/// point to a valid null-terminated C string. Invalid IDs or pointers will cause undefined behavior.
681+
/// This function is safe to call from C/LLVM code. The `result_ptr` parameter is an i8* pointer
682+
/// that represents a result ID (typically from inttoptr i64 conversion in LLVM IR).
683+
/// The `register_name` pointer may be null or must point to a valid null-terminated C string.
684+
/// Invalid pointers will cause undefined behavior.
684685
#[unsafe(no_mangle)]
685686
pub unsafe extern "C" fn __quantum__rt__result_record_output(
686-
result_id: i64,
687+
result_ptr: *const std::ffi::c_void,
687688
register_name: *const std::ffi::c_char,
688689
) {
689-
// For now, this is a no-op since we're collecting operations rather than executing them
690-
// In a real implementation, this would record the measurement result to the specified register
691-
// The actual measurement results are handled by the runtime during execution
692-
693-
// We could potentially add this as metadata to the interface if needed
694-
// For debugging, we can at least validate the inputs
695-
let _result_id = i64_to_usize(result_id);
696-
697-
if !register_name.is_null() {
698-
// Mark the unsafe operation explicitly
699-
let _register = unsafe { std::ffi::CStr::from_ptr(register_name) }.to_string_lossy();
700-
// In the future, we might want to record this information
701-
}
690+
// Extract the result ID from the pointer
691+
// HUGR generates: %result_ptr = inttoptr i64 %result_id to i8*
692+
let result_id = result_ptr as usize;
693+
694+
// Convert the C string to a Rust String
695+
let register_name_str = if register_name.is_null() {
696+
"unknown".to_string()
697+
} else {
698+
let c_str = unsafe { std::ffi::CStr::from_ptr(register_name) };
699+
c_str.to_str().unwrap_or("unknown").to_string()
700+
};
701+
702+
log::trace!(
703+
"Recording output mapping: result_id={result_id} -> register_name='{register_name_str}'"
704+
);
705+
706+
// Queue the operation to record this output mapping
707+
with_interface(|interface| {
708+
interface.queue_operation(Operation::RecordOutput {
709+
result_id,
710+
register_name: register_name_str,
711+
});
712+
});
702713
}
703714

704715
// =============================================================================

crates/pecos-qis-selene/src/selene_runtime.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,17 @@ impl SeleneRuntime {
148148
let _ = id; // Just track it
149149
self.current_op_index += 1;
150150
}
151+
Operation::RecordOutput {
152+
result_id,
153+
register_name,
154+
} => {
155+
trace!(
156+
"Recording output: result_id={result_id}, register_name={register_name}"
157+
);
158+
// Metadata operation - just advance the index
159+
// The actual result mapping is handled by the runtime's results collection
160+
self.current_op_index += 1;
161+
}
151162
Operation::Barrier => {
152163
trace!("Barrier encountered");
153164
// Barriers don't produce quantum ops but can break batches

python/quantum-pecos/tests/guppy/test_hugr_compilation.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,38 @@ def test_llvm_ir_format_validation(self) -> None:
126126
llvm_file = Path(f.name)
127127

128128
try:
129-
# Find llvm-as - check PATH first, then LLVM_SYS_140_PREFIX
129+
# Find llvm-as - check PATH first, then use pecos-llvm-utils
130130
llvm_as_path = shutil.which("llvm-as")
131131

132132
if not llvm_as_path:
133-
# Not in PATH, try LLVM_SYS_140_PREFIX environment variable
134-
llvm_prefix = os.environ.get("LLVM_SYS_140_PREFIX")
135-
if llvm_prefix:
136-
potential_path = (
137-
Path(llvm_prefix) / "bin" / "llvm-as.exe"
138-
if os.name == "nt"
139-
else Path(llvm_prefix) / "bin" / "llvm-as"
140-
)
141-
if potential_path.exists():
142-
llvm_as_path = str(potential_path)
133+
# Use pecos-llvm-utils to find the tool
134+
cargo_path = shutil.which("cargo")
135+
if cargo_path:
136+
try:
137+
result = subprocess.run(
138+
[
139+
cargo_path,
140+
"run",
141+
"-q",
142+
"--release",
143+
"-p",
144+
"pecos-llvm-utils",
145+
"--bin",
146+
"pecos-llvm",
147+
"--",
148+
"tool",
149+
"llvm-as",
150+
],
151+
capture_output=True,
152+
text=True,
153+
check=False,
154+
timeout=30,
155+
)
156+
if result.returncode == 0 and result.stdout.strip():
157+
llvm_as_path = result.stdout.strip()
158+
except (subprocess.TimeoutExpired, Exception): # noqa: S110
159+
# Silently fall through to failure case - error will be reported below
160+
pass
143161

144162
if llvm_as_path:
145163
# Validate with llvm-as

python/quantum-pecos/tests/pecos/test_sim_api_integration.py

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -141,33 +141,44 @@ class TestLLVMSimulation:
141141

142142
def test_sim_api_with_llvm_simple(self) -> None:
143143
"""Test sim API with simple LLVM IR program."""
144-
# Proper QIR-compliant LLVM IR
144+
# QIS format LLVM IR - uses i64 for qubit indices and qmain entry point
145+
# This matches the format that PECOS HUGR compiler generates
145146
llvm_ir = """
146147
; ModuleID = 'quantum_test'
148+
source_filename = "quantum_test"
147149
148-
%Qubit = type opaque
149-
%Result = type opaque
150+
@str_c = constant [2 x i8] c"c\\00"
150151
151-
declare void @__quantum__qis__h__body(%Qubit*)
152-
declare %Result* @__quantum__qis__mz__body(%Qubit*)
153-
declare %Qubit* @__quantum__rt__qubit_allocate()
154-
declare void @__quantum__rt__qubit_release(%Qubit*)
155-
declare void @__quantum__rt__result_record_output(%Result*, i8*)
152+
; Entry point using qmain(i64)->i64 protocol
153+
define i64 @qmain(i64 %0) #0 {
154+
entry:
155+
; Allocate qubit (returns i64)
156+
%qubit = call i64 @__quantum__rt__qubit_allocate()
156157
157-
@0 = internal constant [2 x i8] c"c\\00"
158+
; Apply H gate (takes i64 qubit index)
159+
call void @__quantum__qis__h__body(i64 %qubit)
158160
159-
define void @main() #0 {
160-
entry:
161-
%qubit = call %Qubit* @__quantum__rt__qubit_allocate()
162-
call void @__quantum__qis__h__body(%Qubit* %qubit)
163-
%result = call %Result* @__quantum__qis__mz__body(%Qubit* %qubit)
164-
call void @__quantum__rt__result_record_output(%Result* %result,
165-
i8* getelementptr inbounds ([2 x i8], [2 x i8]* @0, i32 0, i32 0))
166-
call void @__quantum__rt__qubit_release(%Qubit* %qubit)
167-
ret void
161+
; Allocate result handle
162+
%result_id = call i64 @__quantum__rt__result_allocate()
163+
164+
; Measure qubit (returns i32 measurement)
165+
%measurement = call i32 @__quantum__qis__m__body(i64 %qubit, i64 %result_id)
166+
167+
; Record output (convert i64 to i8* pointer for register name)
168+
%result_ptr = inttoptr i64 %result_id to i8*
169+
call void @__quantum__rt__result_record_output(i8* %result_ptr,
170+
i8* getelementptr inbounds ([2 x i8], [2 x i8]* @str_c, i32 0, i32 0))
171+
172+
ret i64 0
168173
}
169174
170-
attributes #0 = { "EntryPoint" "requiredQubits"="1" }
175+
declare i64 @__quantum__rt__qubit_allocate()
176+
declare void @__quantum__qis__h__body(i64)
177+
declare i64 @__quantum__rt__result_allocate()
178+
declare i32 @__quantum__qis__m__body(i64, i64)
179+
declare void @__quantum__rt__result_record_output(i8*, i8*)
180+
181+
attributes #0 = { "EntryPoint" }
171182
"""
172183

173184
try:
@@ -207,45 +218,53 @@ def test_sim_api_with_llvm_simple(self) -> None:
207218

208219
def test_sim_api_with_llvm_bell_state(self) -> None:
209220
"""Test sim API with Bell state in LLVM IR."""
210-
# Bell state in QIS format - uses i64 qubit indices (not QIR opaque pointers)
211-
# This is the format PECOS actually supports (from HUGR compilation)
221+
# QIS format LLVM IR - uses i64 for qubit indices and qmain entry point
222+
# This matches the format that PECOS HUGR compiler generates
212223
llvm_ir = """
213224
; ModuleID = 'bell_state'
225+
source_filename = "bell_state"
214226
215-
declare void @__quantum__qis__h__body(i64)
216-
declare void @__quantum__qis__cnot__body(i64, i64)
217-
declare i1 @__quantum__qis__mz__body(i64)
218-
declare void @__quantum__rt__result_record_output(i64, i8*)
219-
220-
@0 = internal constant [3 x i8] c"c0\\00"
221-
@1 = internal constant [3 x i8] c"c1\\00"
227+
@str_c0 = constant [3 x i8] c"c0\\00"
228+
@str_c1 = constant [3 x i8] c"c1\\00"
222229
223-
define void @main() #0 {
230+
; Entry point using qmain(i64)->i64 protocol
231+
define i64 @qmain(i64 %0) #0 {
224232
entry:
225-
; Apply H to qubit 0
226-
call void @__quantum__qis__h__body(i64 0)
227-
228-
; Apply CNOT(0, 1)
229-
call void @__quantum__qis__cnot__body(i64 0, i64 1)
230-
231-
; Measure both qubits
232-
%m0 = call i1 @__quantum__qis__mz__body(i64 0)
233-
%m1 = call i1 @__quantum__qis__mz__body(i64 1)
234-
235-
; Convert i1 to i64 for result recording
236-
%r0 = zext i1 %m0 to i64
237-
%r1 = zext i1 %m1 to i64
233+
; Allocate qubits (returns i64)
234+
%q0 = call i64 @__quantum__rt__qubit_allocate()
235+
%q1 = call i64 @__quantum__rt__qubit_allocate()
238236
239-
; Record results
240-
call void @__quantum__rt__result_record_output(i64 %r0,
241-
i8* getelementptr inbounds ([3 x i8], [3 x i8]* @0, i32 0, i32 0))
242-
call void @__quantum__rt__result_record_output(i64 %r1,
243-
i8* getelementptr inbounds ([3 x i8], [3 x i8]* @1, i32 0, i32 0))
244-
245-
ret void
237+
; Apply H to qubit 0
238+
call void @__quantum__qis__h__body(i64 %q0)
239+
240+
; Apply CX(q0, q1)
241+
call void @__quantum__qis__cx__body(i64 %q0, i64 %q1)
242+
243+
; Measure qubit 0
244+
%result_id0 = call i64 @__quantum__rt__result_allocate()
245+
%measurement0 = call i32 @__quantum__qis__m__body(i64 %q0, i64 %result_id0)
246+
%result_ptr0 = inttoptr i64 %result_id0 to i8*
247+
call void @__quantum__rt__result_record_output(i8* %result_ptr0,
248+
i8* getelementptr inbounds ([3 x i8], [3 x i8]* @str_c0, i32 0, i32 0))
249+
250+
; Measure qubit 1
251+
%result_id1 = call i64 @__quantum__rt__result_allocate()
252+
%measurement1 = call i32 @__quantum__qis__m__body(i64 %q1, i64 %result_id1)
253+
%result_ptr1 = inttoptr i64 %result_id1 to i8*
254+
call void @__quantum__rt__result_record_output(i8* %result_ptr1,
255+
i8* getelementptr inbounds ([3 x i8], [3 x i8]* @str_c1, i32 0, i32 0))
256+
257+
ret i64 0
246258
}
247259
248-
attributes #0 = { "EntryPoint" "requiredQubits"="2" }
260+
declare i64 @__quantum__rt__qubit_allocate()
261+
declare void @__quantum__qis__h__body(i64)
262+
declare void @__quantum__qis__cx__body(i64, i64)
263+
declare i64 @__quantum__rt__result_allocate()
264+
declare i32 @__quantum__qis__m__body(i64, i64)
265+
declare void @__quantum__rt__result_record_output(i8*, i8*)
266+
267+
attributes #0 = { "EntryPoint" }
249268
"""
250269

251270
try:

0 commit comments

Comments
 (0)