Skip to content

Commit 909a794

Browse files
authored
feat: Add MakeError op (#2377)
closes #1863
1 parent 5a0a520 commit 909a794

8 files changed

+321
-5
lines changed

hugr-core/src/extension/prelude.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub mod generic;
3939
/// Name of prelude extension.
4040
pub const PRELUDE_ID: ExtensionId = ExtensionId::new_unchecked("prelude");
4141
/// Extension version.
42-
pub const VERSION: semver::Version = semver::Version::new(0, 2, 0);
42+
pub const VERSION: semver::Version = semver::Version::new(0, 2, 1);
4343
lazy_static! {
4444
/// Prelude extension, containing common types and operations.
4545
pub static ref PRELUDE: Arc<Extension> = {
@@ -52,6 +52,7 @@ lazy_static! {
5252
// would try to access the `PRELUDE` lazy static recursively,
5353
// causing a deadlock.
5454
let string_type: Type = string_custom_type(extension_ref).into();
55+
let usize_type: Type = usize_custom_t(extension_ref).into();
5556
let error_type: CustomType = error_custom_type(extension_ref);
5657

5758
prelude
@@ -74,7 +75,7 @@ lazy_static! {
7475
prelude.add_op(
7576
PRINT_OP_ID,
7677
"Print the string to standard output".to_string(),
77-
Signature::new(vec![string_type], type_row![]),
78+
Signature::new(vec![string_type.clone()], type_row![]),
7879
extension_ref,
7980
)
8081
.unwrap();
@@ -96,6 +97,14 @@ lazy_static! {
9697
extension_ref,
9798
)
9899
.unwrap();
100+
prelude
101+
.add_op(
102+
MAKE_ERROR_OP_ID,
103+
"Create an error value".to_string(),
104+
Signature::new(vec![usize_type, string_type], vec![error_type.clone().into()]),
105+
extension_ref,
106+
)
107+
.unwrap();
99108
prelude
100109
.add_op(
101110
PANIC_OP_ID,
@@ -172,6 +181,11 @@ pub fn bool_t() -> Type {
172181
Type::new_unit_sum(2)
173182
}
174183

184+
/// Name of the prelude `MakeError` operation.
185+
///
186+
/// This operation can be used to dynamically create error values.
187+
pub const MAKE_ERROR_OP_ID: OpName = OpName::new_inline("MakeError");
188+
175189
/// Name of the prelude panic operation.
176190
///
177191
/// This operation can have any input and any output wires; it is instantiated
@@ -1006,6 +1020,8 @@ mod test {
10061020
type_row,
10071021
};
10081022

1023+
use crate::hugr::views::HugrView;
1024+
10091025
#[test]
10101026
fn test_make_tuple() {
10111027
let op = MakeTuple::new(type_row![Type::UNIT]);
@@ -1126,6 +1142,26 @@ mod test {
11261142
b.finish_hugr_with_outputs([]).unwrap();
11271143
}
11281144

1145+
#[test]
1146+
/// test the prelude make error op with the panic op.
1147+
fn test_make_error() {
1148+
let err_op = PRELUDE
1149+
.instantiate_extension_op(&MAKE_ERROR_OP_ID, [])
1150+
.unwrap();
1151+
let panic_op = PRELUDE
1152+
.instantiate_extension_op(&EXIT_OP_ID, [Term::new_list([]), Term::new_list([])])
1153+
.unwrap();
1154+
1155+
let mut b =
1156+
DFGBuilder::new(Signature::new(vec![usize_t(), string_type()], type_row![])).unwrap();
1157+
let [signal, message] = b.input_wires_arr();
1158+
let err_value = b.add_dataflow_op(err_op, [signal, message]).unwrap();
1159+
b.add_dataflow_op(panic_op, err_value.outputs()).unwrap();
1160+
1161+
let h = b.finish_hugr_with_outputs([]).unwrap();
1162+
h.validate().unwrap();
1163+
}
1164+
11291165
#[test]
11301166
/// test the panic operation with input and output wires
11311167
fn test_panic_with_io() {

hugr-llvm/src/extension/prelude.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,37 @@ pub trait PreludeCodegen: Clone {
117117
Ok(err.into())
118118
}
119119

120+
/// Emit instructions to construct an error value from a signal and message.
121+
///
122+
/// The type of the returned value must match [`Self::error_type`].
123+
///
124+
/// The default implementation constructs a struct with the given signal and message.
125+
fn emit_make_error<'c, H: HugrView<Node = Node>>(
126+
&self,
127+
ctx: &mut EmitFuncContext<'c, '_, H>,
128+
signal: BasicValueEnum<'c>,
129+
message: BasicValueEnum<'c>,
130+
) -> Result<BasicValueEnum<'c>> {
131+
let builder = ctx.builder();
132+
133+
// The usize signal is an i64 but error struct stores an i32.
134+
let i32_type = ctx.typing_session().iw_context().i32_type();
135+
let signal_int = signal.into_int_value();
136+
let signal_truncated = builder.build_int_truncate(signal_int, i32_type, "")?;
137+
138+
// Construct the error struct as runtime value.
139+
let err_ty = ctx.llvm_type(&error_type())?.into_struct_type();
140+
let undef = err_ty.get_undef();
141+
let err_with_sig = builder
142+
.build_insert_value(undef, signal_truncated, 0, "")?
143+
.into_struct_value();
144+
let err_complete = builder
145+
.build_insert_value(err_with_sig, message, 1, "")?
146+
.into_struct_value();
147+
148+
Ok(err_complete.into())
149+
}
150+
120151
/// Emit instructions to halt execution with the error `err`.
121152
///
122153
/// The type of `err` must match that returned from [`Self::error_type`].
@@ -345,6 +376,22 @@ pub fn add_prelude_extensions<'a, H: HugrView<Node = Node> + 'a>(
345376
args.outputs.finish(context.builder(), [])
346377
}
347378
})
379+
.extension_op(prelude::PRELUDE_ID, prelude::MAKE_ERROR_OP_ID, {
380+
let pcg = pcg.clone();
381+
move |context, args| {
382+
let signal = args.inputs[0];
383+
let message = args.inputs[1];
384+
ensure!(
385+
message.get_type()
386+
== pcg
387+
.string_type(&context.typing_session())?
388+
.as_basic_type_enum(),
389+
signal.get_type() == pcg.usize_type(&context.typing_session()).into()
390+
);
391+
let err = pcg.emit_make_error(context, signal, message)?;
392+
args.outputs.finish(context.builder(), [err])
393+
}
394+
})
348395
.extension_op(prelude::PRELUDE_ID, prelude::PANIC_OP_ID, {
349396
let pcg = pcg.clone();
350397
move |context, args| {
@@ -407,7 +454,7 @@ pub fn add_prelude_extensions<'a, H: HugrView<Node = Node> + 'a>(
407454
mod test {
408455
use hugr_core::builder::{Dataflow, DataflowHugr};
409456
use hugr_core::extension::PRELUDE;
410-
use hugr_core::extension::prelude::{EXIT_OP_ID, Noop};
457+
use hugr_core::extension::prelude::{EXIT_OP_ID, MAKE_ERROR_OP_ID, Noop};
411458
use hugr_core::types::{Term, Type};
412459
use hugr_core::{Hugr, type_row};
413460
use prelude::{PANIC_OP_ID, PRINT_OP_ID, bool_t, qb_t, usize_t};
@@ -624,6 +671,60 @@ mod test {
624671
check_emission!(hugr, prelude_llvm_ctx);
625672
}
626673

674+
#[rstest]
675+
fn prelude_make_error(prelude_llvm_ctx: TestContext) {
676+
let sig: ConstUsize = ConstUsize::new(100);
677+
let msg: ConstString = ConstString::new("Error!".into());
678+
679+
let make_error_op = PRELUDE
680+
.instantiate_extension_op(&MAKE_ERROR_OP_ID, [])
681+
.unwrap();
682+
683+
let hugr = SimpleHugrConfig::new()
684+
.with_extensions(prelude::PRELUDE_REGISTRY.to_owned())
685+
.with_outs(error_type())
686+
.finish(|mut builder| {
687+
let sig_out = builder.add_load_value(sig);
688+
let msg_out = builder.add_load_value(msg);
689+
let [err] = builder
690+
.add_dataflow_op(make_error_op, [sig_out, msg_out])
691+
.unwrap()
692+
.outputs_arr();
693+
builder.finish_hugr_with_outputs([err]).unwrap()
694+
});
695+
696+
check_emission!(hugr, prelude_llvm_ctx);
697+
}
698+
699+
#[rstest]
700+
fn prelude_make_error_and_panic(prelude_llvm_ctx: TestContext) {
701+
let sig: ConstUsize = ConstUsize::new(100);
702+
let msg: ConstString = ConstString::new("Error!".into());
703+
704+
let make_error_op = PRELUDE
705+
.instantiate_extension_op(&MAKE_ERROR_OP_ID, [])
706+
.unwrap();
707+
708+
let panic_op = PRELUDE
709+
.instantiate_extension_op(&PANIC_OP_ID, [Term::new_list([]), Term::new_list([])])
710+
.unwrap();
711+
712+
let hugr = SimpleHugrConfig::new()
713+
.with_extensions(prelude::PRELUDE_REGISTRY.to_owned())
714+
.finish(|mut builder| {
715+
let sig_out = builder.add_load_value(sig);
716+
let msg_out = builder.add_load_value(msg);
717+
let [err] = builder
718+
.add_dataflow_op(make_error_op, [sig_out, msg_out])
719+
.unwrap()
720+
.outputs_arr();
721+
builder.add_dataflow_op(panic_op, [err]).unwrap();
722+
builder.finish_hugr_with_outputs([]).unwrap()
723+
});
724+
725+
check_emission!(hugr, prelude_llvm_ctx);
726+
}
727+
627728
#[rstest]
628729
fn prelude_load_nat(prelude_llvm_ctx: TestContext) {
629730
let hugr = SimpleHugrConfig::new()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
source: hugr-llvm/src/extension/prelude.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@0 = private unnamed_addr constant [7 x i8] c"Error!\00", align 1
9+
10+
define { i32, i8* } @_hl.main.1() {
11+
alloca_block:
12+
br label %entry_block
13+
14+
entry_block: ; preds = %alloca_block
15+
%0 = trunc i64 100 to i32
16+
%1 = insertvalue { i32, i8* } undef, i32 %0, 0
17+
%2 = insertvalue { i32, i8* } %1, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i32 0, i32 0), 1
18+
ret { i32, i8* } %2
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
source: hugr-llvm/src/extension/prelude.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@0 = private unnamed_addr constant [7 x i8] c"Error!\00", align 1
9+
10+
define { i32, i8* } @_hl.main.1() {
11+
alloca_block:
12+
%"0" = alloca { i32, i8* }, align 8
13+
%"7_0" = alloca i8*, align 8
14+
%"5_0" = alloca i64, align 8
15+
%"8_0" = alloca { i32, i8* }, align 8
16+
br label %entry_block
17+
18+
entry_block: ; preds = %alloca_block
19+
store i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i32 0, i32 0), i8** %"7_0", align 8
20+
store i64 100, i64* %"5_0", align 4
21+
%"5_01" = load i64, i64* %"5_0", align 4
22+
%"7_02" = load i8*, i8** %"7_0", align 8
23+
%0 = trunc i64 %"5_01" to i32
24+
%1 = insertvalue { i32, i8* } undef, i32 %0, 0
25+
%2 = insertvalue { i32, i8* } %1, i8* %"7_02", 1
26+
store { i32, i8* } %2, { i32, i8* }* %"8_0", align 8
27+
%"8_03" = load { i32, i8* }, { i32, i8* }* %"8_0", align 8
28+
store { i32, i8* } %"8_03", { i32, i8* }* %"0", align 8
29+
%"04" = load { i32, i8* }, { i32, i8* }* %"0", align 8
30+
ret { i32, i8* } %"04"
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: hugr-llvm/src/extension/prelude.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@0 = private unnamed_addr constant [7 x i8] c"Error!\00", align 1
9+
@prelude.panic_template = private unnamed_addr constant [34 x i8] c"Program panicked (signal %i): %s\0A\00", align 1
10+
11+
define void @_hl.main.1() {
12+
alloca_block:
13+
br label %entry_block
14+
15+
entry_block: ; preds = %alloca_block
16+
%0 = trunc i64 100 to i32
17+
%1 = insertvalue { i32, i8* } undef, i32 %0, 0
18+
%2 = insertvalue { i32, i8* } %1, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i32 0, i32 0), 1
19+
%3 = extractvalue { i32, i8* } %2, 0
20+
%4 = extractvalue { i32, i8* } %2, 1
21+
%5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %3, i8* %4)
22+
call void @abort()
23+
ret void
24+
}
25+
26+
declare i32 @printf(i8*, ...)
27+
28+
declare void @abort()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
source: hugr-llvm/src/extension/prelude.rs
3+
expression: mod_str
4+
---
5+
; ModuleID = 'test_context'
6+
source_filename = "test_context"
7+
8+
@0 = private unnamed_addr constant [7 x i8] c"Error!\00", align 1
9+
@prelude.panic_template = private unnamed_addr constant [34 x i8] c"Program panicked (signal %i): %s\0A\00", align 1
10+
11+
define void @_hl.main.1() {
12+
alloca_block:
13+
%"7_0" = alloca i8*, align 8
14+
%"5_0" = alloca i64, align 8
15+
%"8_0" = alloca { i32, i8* }, align 8
16+
br label %entry_block
17+
18+
entry_block: ; preds = %alloca_block
19+
store i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i32 0, i32 0), i8** %"7_0", align 8
20+
store i64 100, i64* %"5_0", align 4
21+
%"5_01" = load i64, i64* %"5_0", align 4
22+
%"7_02" = load i8*, i8** %"7_0", align 8
23+
%0 = trunc i64 %"5_01" to i32
24+
%1 = insertvalue { i32, i8* } undef, i32 %0, 0
25+
%2 = insertvalue { i32, i8* } %1, i8* %"7_02", 1
26+
store { i32, i8* } %2, { i32, i8* }* %"8_0", align 8
27+
%"8_03" = load { i32, i8* }, { i32, i8* }* %"8_0", align 8
28+
%3 = extractvalue { i32, i8* } %"8_03", 0
29+
%4 = extractvalue { i32, i8* } %"8_03", 1
30+
%5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %3, i8* %4)
31+
call void @abort()
32+
ret void
33+
}
34+
35+
declare i32 @printf(i8*, ...)
36+
37+
declare void @abort()

hugr-py/src/hugr/std/_json_defs/prelude.json

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.2.0",
2+
"version": "0.2.1",
33
"name": "prelude",
44
"types": {
55
"error": {
@@ -77,6 +77,38 @@
7777
},
7878
"binary": false
7979
},
80+
"MakeError": {
81+
"extension": "prelude",
82+
"name": "MakeError",
83+
"description": "Create an error value",
84+
"signature": {
85+
"params": [],
86+
"body": {
87+
"input": [
88+
{
89+
"t": "I"
90+
},
91+
{
92+
"t": "Opaque",
93+
"extension": "prelude",
94+
"id": "string",
95+
"args": [],
96+
"bound": "C"
97+
}
98+
],
99+
"output": [
100+
{
101+
"t": "Opaque",
102+
"extension": "prelude",
103+
"id": "error",
104+
"args": [],
105+
"bound": "C"
106+
}
107+
]
108+
}
109+
},
110+
"binary": false
111+
},
80112
"MakeTuple": {
81113
"extension": "prelude",
82114
"name": "MakeTuple",

0 commit comments

Comments
 (0)