Skip to content

Commit 724b8bd

Browse files
authored
feat: Add llvm lowering for debug extension (#900)
See quantinuum-dev/hugrverse#65 for context (Needs to be tested with selene and guppy again before merging)
1 parent 9ea2a04 commit 724b8bd

File tree

5 files changed

+229
-1
lines changed

5 files changed

+229
-1
lines changed

tket2-hseries/src/llvm.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! LLVM lowerings for HUGR extensions defined in this crate.
22
pub mod array_utils;
3+
pub mod debug;
34
pub mod futures;
45
pub mod prelude;
56
pub mod qsystem;

tket2-hseries/src/llvm/debug.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! LLVM lowering implementations for "tket2.debug" extension.
2+
3+
use crate::llvm::prelude::emit_global_string;
4+
use anyhow::{anyhow, bail, Result};
5+
use hugr::extension::simple_op::MakeExtensionOp;
6+
use hugr::llvm::custom::CodegenExtension;
7+
use hugr::llvm::emit::{EmitFuncContext, EmitOpArgs};
8+
use hugr::llvm::inkwell::AddressSpace;
9+
use hugr::llvm::CodegenExtsBuilder;
10+
use hugr::ops::ExtensionOp;
11+
use hugr::{HugrView, Node};
12+
use tket2::extension::debug::{StateResult, DEBUG_EXTENSION_ID, STATE_RESULT_OP_ID};
13+
14+
use super::array_utils::{build_array_alloca, struct_1d_arr_alloc, struct_1d_arr_ptr_t, ElemType};
15+
16+
static TAG_PREFIX: &str = "USER:";
17+
18+
/// Codegen extension for debug functionality.
19+
pub struct DebugCodegenExtension;
20+
21+
impl CodegenExtension for DebugCodegenExtension {
22+
fn add_extension<'a, H: HugrView<Node = Node> + 'a>(
23+
self,
24+
builder: CodegenExtsBuilder<'a, H>,
25+
) -> CodegenExtsBuilder<'a, H>
26+
where
27+
Self: 'a,
28+
{
29+
builder.extension_op(DEBUG_EXTENSION_ID, STATE_RESULT_OP_ID, {
30+
move |context, args| self.emit_state_result(context, args)
31+
})
32+
}
33+
}
34+
35+
impl DebugCodegenExtension {
36+
/// Lower the `debug` `StateResult` op.
37+
fn emit_state_result<'c, H: HugrView<Node = Node>>(
38+
&self,
39+
ctx: &mut EmitFuncContext<'c, '_, H>,
40+
args: EmitOpArgs<'c, '_, ExtensionOp, H>,
41+
) -> Result<()> {
42+
let builder = ctx.builder();
43+
44+
// Types (qubits are just i64s).
45+
let iw_ctx = ctx.iw_context();
46+
let void_t = iw_ctx.void_type();
47+
let i8_ptr_t = iw_ctx.i8_type().ptr_type(AddressSpace::default());
48+
let i64_t = iw_ctx.i64_type();
49+
50+
// Tag arguments.
51+
let state_result = StateResult::from_extension_op(args.node().as_ref())?;
52+
let tag = state_result.tag;
53+
if tag.is_empty() {
54+
bail!("Empty state result tag received");
55+
}
56+
let tag_ptr = emit_global_string(ctx, tag, "res_", format!("{TAG_PREFIX}STATE:"))?;
57+
let tag_len = {
58+
let mut l = builder
59+
.build_load(tag_ptr.into_pointer_value(), "tag_len")?
60+
.into_int_value();
61+
if l.get_type() != i64_t {
62+
l = builder.build_int_z_extend(l, i64_t, "tag_len")?;
63+
}
64+
l
65+
};
66+
67+
// Qubit array argument.
68+
let array_len = state_result.num_qubits;
69+
let [qubits] = args
70+
.inputs
71+
.try_into()
72+
.map_err(|_| anyhow!(format!("StateResult expects a qubit array argument")))?;
73+
assert!(qubits.is_array_value());
74+
let (qubits_array, _) = build_array_alloca(builder, qubits.into_array_value())?;
75+
let (qubits_ptr, _) = struct_1d_arr_alloc(
76+
iw_ctx,
77+
builder,
78+
array_len.try_into()?,
79+
&ElemType::Int,
80+
qubits_array,
81+
)?;
82+
83+
// Build the function call.
84+
let fn_state_result = ctx.get_extern_func(
85+
"print_state_result",
86+
void_t.fn_type(
87+
&[
88+
i8_ptr_t.into(),
89+
i64_t.into(),
90+
struct_1d_arr_ptr_t(iw_ctx, &ElemType::Int).into(),
91+
],
92+
false,
93+
),
94+
)?;
95+
96+
builder.build_call(
97+
fn_state_result,
98+
&[tag_ptr.into(), tag_len.into(), qubits_ptr.into()],
99+
"print_state_result",
100+
)?;
101+
args.outputs.finish(builder, [qubits])
102+
}
103+
}
104+
105+
#[cfg(test)]
106+
#[allow(deprecated)] // TODO remove after switching to new array lowering
107+
mod test {
108+
use tket2::extension::debug::StateResult;
109+
110+
use hugr::extension::simple_op::MakeRegisteredOp;
111+
use hugr::llvm::check_emission;
112+
use hugr::llvm::extension::collections::stack_array::{
113+
ArrayCodegenExtension, DefaultArrayCodegen,
114+
};
115+
use hugr::llvm::test::llvm_ctx;
116+
use hugr::llvm::test::single_op_hugr;
117+
use hugr::llvm::test::TestContext;
118+
119+
use crate::llvm::prelude::QISPreludeCodegen;
120+
121+
use rstest::rstest;
122+
123+
use super::*;
124+
125+
#[rstest]
126+
#[case::state_result(1, StateResult::new("test_state_result".to_string(), 2))]
127+
fn emit_debug_codegen(
128+
#[case] _i: i32,
129+
#[with(_i)] mut llvm_ctx: TestContext,
130+
#[case] op: StateResult,
131+
) {
132+
let pcg = QISPreludeCodegen;
133+
llvm_ctx.add_extensions(move |ceb| {
134+
ceb.add_extension(DebugCodegenExtension)
135+
.add_prelude_extensions(pcg.clone())
136+
.add_extension(ArrayCodegenExtension::new(DefaultArrayCodegen))
137+
.add_default_int_extensions()
138+
.add_float_extensions()
139+
});
140+
let ext_op = op.to_extension_op().unwrap().into();
141+
let hugr = single_op_hugr(ext_op);
142+
check_emission!(hugr, llvm_ctx);
143+
}
144+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
source: tket2-hseries/src/llvm/debug.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@res_test_state.900F7606.0 = private constant [29 x i8] c"\1CUSER:STATE:test_state_result"
9+
10+
define [2 x i64] @_hl.main.1([2 x i64] %0) {
11+
alloca_block:
12+
br label %entry_block
13+
14+
entry_block: ; preds = %alloca_block
15+
%tag_len = load i8, i8* getelementptr inbounds ([29 x i8], [29 x i8]* @res_test_state.900F7606.0, i32 0, i32 0), align 1
16+
%tag_len2 = zext i8 %tag_len to i64
17+
%1 = alloca i64, i32 2, align 8
18+
%2 = bitcast i64* %1 to [2 x i64]*
19+
store [2 x i64] %0, [2 x i64]* %2, align 4
20+
%out_arr_alloca = alloca <{ i32, i32, i64*, i1* }>, align 8
21+
%x_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 0
22+
%y_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 1
23+
%arr_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 2
24+
%mask_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 3
25+
%3 = alloca i1, i32 2, align 1
26+
%4 = bitcast i1* %3 to [2 x i1]*
27+
store [2 x i1] zeroinitializer, [2 x i1]* %4, align 1
28+
store i32 2, i32* %x_ptr, align 4
29+
store i32 1, i32* %y_ptr, align 4
30+
store i64* %1, i64** %arr_ptr, align 8
31+
store i1* %3, i1** %mask_ptr, align 8
32+
%5 = load <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, align 1
33+
call void @print_state_result(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @res_test_state.900F7606.0, i32 0, i32 0), i64 %tag_len2, <{ i32, i32, i64*, i1* }>* %out_arr_alloca)
34+
ret [2 x i64] %0
35+
}
36+
37+
declare void @print_state_result(i8*, i64, <{ i32, i32, i64*, i1* }>*)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
source: tket2-hseries/src/llvm/debug.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@res_test_state.900F7606.0 = private constant [29 x i8] c"\1CUSER:STATE:test_state_result"
9+
10+
define [2 x i64] @_hl.main.1([2 x i64] %0) {
11+
alloca_block:
12+
%"0" = alloca [2 x i64], align 8
13+
%"2_0" = alloca [2 x i64], align 8
14+
%"4_0" = alloca [2 x i64], align 8
15+
br label %entry_block
16+
17+
entry_block: ; preds = %alloca_block
18+
store [2 x i64] %0, [2 x i64]* %"2_0", align 4
19+
%"2_01" = load [2 x i64], [2 x i64]* %"2_0", align 4
20+
%tag_len = load i8, i8* getelementptr inbounds ([29 x i8], [29 x i8]* @res_test_state.900F7606.0, i32 0, i32 0), align 1
21+
%tag_len2 = zext i8 %tag_len to i64
22+
%1 = alloca i64, i32 2, align 8
23+
%2 = bitcast i64* %1 to [2 x i64]*
24+
store [2 x i64] %"2_01", [2 x i64]* %2, align 4
25+
%out_arr_alloca = alloca <{ i32, i32, i64*, i1* }>, align 8
26+
%x_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 0
27+
%y_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 1
28+
%arr_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 2
29+
%mask_ptr = getelementptr inbounds <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, i32 0, i32 3
30+
%3 = alloca i1, i32 2, align 1
31+
%4 = bitcast i1* %3 to [2 x i1]*
32+
store [2 x i1] zeroinitializer, [2 x i1]* %4, align 1
33+
store i32 2, i32* %x_ptr, align 4
34+
store i32 1, i32* %y_ptr, align 4
35+
store i64* %1, i64** %arr_ptr, align 8
36+
store i1* %3, i1** %mask_ptr, align 8
37+
%5 = load <{ i32, i32, i64*, i1* }>, <{ i32, i32, i64*, i1* }>* %out_arr_alloca, align 1
38+
call void @print_state_result(i8* getelementptr inbounds ([29 x i8], [29 x i8]* @res_test_state.900F7606.0, i32 0, i32 0), i64 %tag_len2, <{ i32, i32, i64*, i1* }>* %out_arr_alloca)
39+
store [2 x i64] %"2_01", [2 x i64]* %"4_0", align 4
40+
%"4_03" = load [2 x i64], [2 x i64]* %"4_0", align 4
41+
store [2 x i64] %"4_03", [2 x i64]* %"0", align 4
42+
%"04" = load [2 x i64], [2 x i64]* %"0", align 4
43+
ret [2 x i64] %"04"
44+
}
45+
46+
declare void @print_state_result(i8*, i64, <{ i32, i32, i64*, i1* }>*)

tket2/src/llvm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
//! `hugr-llvm` codegen extensions for extensions defined in `tket2`..
1+
//! `hugr-llvm` codegen extensions for extensions defined in `tket2`.
22
pub mod rotation;

0 commit comments

Comments
 (0)