From 5b42d8772852b22d17bb9966febf53f0dc48c666 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Tue, 10 Sep 2024 13:40:48 +0100 Subject: [PATCH 01/16] init angle extension --- Cargo.lock | 437 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- src/custom.rs | 1 + src/custom/angle.rs | 501 ++++++++++++++++++++++++++++++++++++++++++++ src/emit/args.rs | 3 +- 5 files changed, 934 insertions(+), 12 deletions(-) create mode 100644 src/custom/angle.rs diff --git a/Cargo.lock b/Cargo.lock index 70c2ebe..67557a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -45,12 +60,32 @@ dependencies = [ "wyz", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "cc" version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ + "jobserver", + "libc", "shlex", ] @@ -71,6 +106,20 @@ dependencies = [ "serde", ] +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "console" version = "0.15.8" @@ -89,6 +138,73 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07ec5b69614ae4dbce88efe2684f4526e3a5aeb0a68326d1424f69c98f3040d2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "delegate" version = "0.12.0" @@ -100,6 +216,30 @@ dependencies = [ "syn", ] +[[package]] +name = "delegate" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5060bb0febb73fa907273f8a7ed17ab4bf831d585eac835b28ec24a1e2460956" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "derive_more" version = "1.0.0" @@ -274,6 +414,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "glob" version = "0.3.1" @@ -303,9 +452,9 @@ dependencies = [ [[package]] name = "hugr" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41def210e277099199acba8a9f5d74938612607a0309cf695dc147b0f5870c74" +checksum = "34ee4f66c9add4abc4b1ed5895b8f4ca1ee3727a0aacbb011696bb0a5946be01" dependencies = [ "hugr-core", "hugr-passes", @@ -313,15 +462,15 @@ dependencies = [ [[package]] name = "hugr-core" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce2072c663e82ec9cc43282ff555c90fa139f3e5396a693f2b2a14d72e694e0" +checksum = "b9cadea7900319ff43c7ee211a28e7de26a3d3f3b1d1bdd4c3de3dfee1199d3e" dependencies = [ "bitvec", "cgmath", "context-iterators", - "delegate", - "derive_more", + "delegate 0.13.0", + "derive_more 1.0.0", "downcast-rs", "enum_dispatch", "html-escape", @@ -347,7 +496,7 @@ name = "hugr-llvm" version = "0.4.0" dependencies = [ "anyhow", - "delegate", + "delegate 0.12.0", "downcast-rs", "hugr", "inkwell", @@ -360,13 +509,14 @@ dependencies = [ "portgraph", "rstest", "serde_json", + "tket2", ] [[package]] name = "hugr-passes" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47d41300b40e1dfd53f17382b2fe4dc815c9707d7507dc68846562ea7ee78b9" +checksum = "6636bd4e828751880354ea8000bcc0be0d753aed9062783b72c78b668b813aa8" dependencies = [ "hugr-core", "itertools 0.13.0", @@ -376,6 +526,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.5.0" @@ -453,6 +626,24 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -484,6 +675,12 @@ dependencies = [ "semver", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -574,6 +771,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "portgraph" version = "0.12.2" @@ -582,13 +785,24 @@ checksum = "4791aa897c125c0f9e606c9a26092f1a6ca50af86f7e37de54ab7e5a7673bdb0" dependencies = [ "bitvec", "context-iterators", - "delegate", + "delegate 0.12.0", "itertools 0.13.0", "petgraph", "serde", "thiserror", ] +[[package]] +name = "priority-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +dependencies = [ + "autocfg", + "equivalent", + "indexmap", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -613,6 +827,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex" version = "1.10.6" @@ -774,6 +1008,9 @@ name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] [[package]] name = "strum_macros" @@ -825,6 +1062,85 @@ dependencies = [ "syn", ] +[[package]] +name = "tket-json-rs" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2609f8a0343065937000d8aa537a473aaab8591f7da1788d4d1bc3e792b3f293" +dependencies = [ + "serde", + "serde_json", + "strum", + "uuid", +] + +[[package]] +name = "tket2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57fb2e60f70575331945d7c23d98b893ded2228d23156fd50da44b5d759a016d" +dependencies = [ + "bytemuck", + "cgmath", + "chrono", + "crossbeam-channel", + "csv", + "delegate 0.13.0", + "derive_more 0.99.18", + "downcast-rs", + "fxhash", + "hugr", + "hugr-core", + "itertools 0.13.0", + "lazy_static", + "num-rational", + "petgraph", + "portgraph", + "priority-queue", + "rayon", + "serde", + "serde_json", + "smol_str", + "strum", + "strum_macros", + "thiserror", + "tket-json-rs", + "tracing", + "typetag", + "zstd", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "typeid" version = "1.0.2" @@ -873,6 +1189,79 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "serde", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -954,3 +1343,31 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zstd" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.13+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index cf8baf8..34ccea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,15 @@ keywords = ["Quantum", "Quantinuum"] categories = ["compilers"] [features] -default = ["llvm14-0"] +default = ["llvm14-0", "tket2"] llvm14-0 = ["dep:llvm-sys-140", "inkwell/llvm14-0"] +tket2 = ["dep:tket2"] [dependencies] inkwell = { version = "0.4.0", default-features=false } llvm-sys-140 = { package = "llvm-sys", version = "140.1.3", optional = true} hugr = "0.12.0" +tket2 = { version = "0.3.0", optional = true } anyhow = "1.0.83" itertools = "0.12.1" delegate = "0.12.0" diff --git a/src/custom.rs b/src/custom.rs index 9a626dd..91f9faa 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -21,6 +21,7 @@ use crate::{ use super::emit::EmitOp; +pub mod angle; pub mod conversions; pub mod float; pub mod int; diff --git a/src/custom/angle.rs b/src/custom/angle.rs new file mode 100644 index 0000000..70dd398 --- /dev/null +++ b/src/custom/angle.rs @@ -0,0 +1,501 @@ +use anyhow::{anyhow, bail, ensure, Result}; +use std::{any::TypeId, char::decode_utf16, f64::consts::PI}; + +use hugr::{ + extension::{prelude::ConstUsize, simple_op::MakeOpDef, ExtensionId}, + ops::{constant::CustomConst, ExtensionOp, Value}, + std_extensions::arithmetic::int_types::{self, int_type, ConstInt}, + types::{CustomType, SumType}, + HugrView, +}; +use inkwell::{ + builder::Builder, + context::Context, + intrinsics::Intrinsic, + types::{AnyType, AsTypeRef, BasicType, BasicTypeEnum, IntType, StructType}, + values::{AnyValue, AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue, StructValue}, + FloatPredicate, IntPredicate, +}; +use llvm_sys_140::prelude::{LLVMTypeRef, LLVMValueRef}; + +use crate::{ + emit::{emit_value, EmitFuncContext, EmitOp, EmitOpArgs}, + sum::LLVMSumType, + types::TypingSession, +}; + +use super::CodegenExtension; + +use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID}; + +#[derive(Debug, Clone, Copy)] +struct LLVMAngleType<'c>(StructType<'c>); + +impl<'c> LLVMAngleType<'c> { + pub fn new(context: &'c Context, usize_type: IntType<'c>) -> Self { + Self(context.struct_type(&[usize_type.into(), usize_type.into()], false)) + } + + fn value_field_type(&self) -> IntType<'c> { + assert_eq!(2, self.0.get_field_types().len()); + unsafe { self.0.get_field_type_at_index_unchecked(0) }.into_int_type() + } + + fn log_denom_field_type(&self) -> IntType<'c> { + assert_eq!(2, self.0.get_field_types().len()); + unsafe { self.0.get_field_type_at_index_unchecked(1) }.into_int_type() + } + + pub fn const_angle(&self, value: u64, log_denom: u8) -> LLVMAngleValue<'c> { + assert_eq!(2, self.0.get_field_types().len()); + let v = self.0.get_undef(); + v.set_field_at_index(0, self.value_field_type().const_int(value, false)); + v.set_field_at_index( + 1, + self.log_denom_field_type() + .const_int(log_denom as u64, false), + ); + LLVMAngleValue(v, *self) + } + + pub fn build_value( + &self, + builder: &Builder<'c>, + value: impl BasicValue<'c>, + log_denom: impl BasicValue<'c>, + ) -> Result> { + let (value, log_denom) = (value.as_basic_value_enum(), log_denom.as_basic_value_enum()); + ensure!(value.get_type() == self.value_field_type().as_basic_type_enum()); + ensure!(log_denom.get_type() == self.log_denom_field_type().as_basic_type_enum()); + + let r = self.0.get_undef(); + let r = builder.build_insert_value(r, value, 0, "")?; + let r = builder.build_insert_value(r, log_denom, 1, "")?; + Ok(LLVMAngleValue(r.into_struct_value(), *self)) + } +} + +unsafe impl<'c> AsTypeRef for LLVMAngleType<'c> { + fn as_type_ref(&self) -> LLVMTypeRef { + self.0.as_type_ref() + } +} + +unsafe impl<'c> AnyType<'c> for LLVMAngleType<'c> {} +unsafe impl<'c> BasicType<'c> for LLVMAngleType<'c> {} + +#[derive(Debug, Clone, Copy)] +struct LLVMAngleValue<'c>(StructValue<'c>, LLVMAngleType<'c>); + +impl<'c> LLVMAngleValue<'c> { + fn try_new(typ: LLVMAngleType<'c>, value: impl BasicValue<'c>) -> Result { + let value = value.as_basic_value_enum(); + ensure!(typ.as_basic_type_enum() == value.get_type()); + Ok(Self(value.into_struct_value(), typ)) + } + + fn build_get_value(&self, builder: &Builder<'c>) -> Result> { + Ok(builder.build_extract_value(self.0, 0, "")?.into_int_value()) + } + + fn build_get_log_denom(&self, builder: &Builder<'c>) -> Result> { + Ok(builder.build_extract_value(self.0, 1, "")?.into_int_value()) + } + + fn build_unmax_denom( + &self, + builder: &Builder<'c>, + max_denom_value: IntValue<'c>, + ) -> Result> { + let log_denom = self.build_get_log_denom(builder)?; + let shift = builder.build_int_sub(self.1.value_field_type().size_of(), log_denom, "")?; + Ok(builder.build_right_shift(max_denom_value, shift, false, "")?) + } + fn build_get_value_max_denom(&self, builder: &Builder<'c>) -> Result> { + let value = self.build_get_value(builder)?; + let log_denom = self.build_get_log_denom(builder)?; + let shift = builder.build_int_sub(self.1.value_field_type().size_of(), log_denom, "")?; + Ok(builder.build_left_shift(value, shift, "")?) + } +} + +impl<'c> From> for BasicValueEnum<'c> { + fn from(value: LLVMAngleValue<'c>) -> Self { + value.as_basic_value_enum() + } +} + +unsafe impl<'c> AsValueRef for LLVMAngleValue<'c> { + fn as_value_ref(&self) -> LLVMValueRef { + self.0.as_value_ref() + } +} + +unsafe impl<'c> AnyValue<'c> for LLVMAngleValue<'c> {} +unsafe impl<'c> BasicValue<'c> for LLVMAngleValue<'c> {} + +pub struct AngleCodegenExtension<'c> { + usize_type: IntType<'c>, +} + +impl<'c> AngleCodegenExtension<'c> { + fn angle_type(&self, context: &'c Context) -> LLVMAngleType<'c> { + LLVMAngleType::new(context, self.usize_type) + } +} + +impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { + fn extension(&self) -> ExtensionId { + ANGLE_EXTENSION_ID + } + + fn llvm_type( + &self, + context: &TypingSession<'c, H>, + hugr_type: &CustomType, + ) -> Result> { + if hugr_type == &ANGLE_CUSTOM_TYPE { + Ok(self.angle_type(context.iw_context()).as_basic_type_enum()) + } else { + bail!("Unsupported type: {hugr_type}") + } + } + + fn emitter<'a>( + &'a self, + context: &'a mut EmitFuncContext<'c, H>, + ) -> Box + 'a> { + Box::new(AngleOpEmitter( + context, + self.angle_type(context.iw_context()), + )) + } + + fn supported_consts(&self) -> std::collections::HashSet { + let of = TypeId::of::(); + [of].into_iter().collect() + } + + fn load_constant( + &self, + context: &mut EmitFuncContext<'c, H>, + konst: &dyn CustomConst, + ) -> Result>> { + let Some(angle) = konst.downcast_ref::() else { + return Ok(None); + }; + let angle_type = self.angle_type(context.iw_context()); + Ok(Some( + angle_type + .const_angle(angle.value(), angle.log_denom()) + .as_basic_value_enum(), + )) + } +} + +struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>, LLVMAngleType<'c>); + +impl<'c, 'd, H: HugrView> AngleOpEmitter<'c, 'd, H> { + fn binary_angle_op( + &self, + lhs: LLVMAngleValue<'c>, + rhs: LLVMAngleValue<'c>, + go: impl FnOnce(IntValue<'c>, IntValue<'c>) -> Result, E>, + ) -> Result> + where + anyhow::Error: From, + { + let angle_ty = self.1; + let builder = self.0.builder(); + let lhs_value = lhs.build_get_value_max_denom(builder)?; + let rhs_value = lhs.build_get_value_max_denom(builder)?; + let new_value = go(lhs_value, rhs_value)?; + + let lhs_log_denom = lhs.build_get_log_denom(builder)?; + let rhs_log_denom = lhs.build_get_log_denom(builder)?; + + let lhs_log_denom_larger = + builder.build_int_compare(IntPredicate::UGT, lhs_log_denom, rhs_log_denom, "")?; + let lhs_larger_r = { + let v = lhs.build_unmax_denom(builder, new_value)?; + angle_ty.build_value(builder, v, lhs_log_denom)? + }; + let rhs_larger_r = { + let v = rhs.build_unmax_denom(builder, new_value)?; + angle_ty.build_value(builder, v, rhs_log_denom)? + }; + let r = builder.build_select(lhs_log_denom_larger, lhs_larger_r, rhs_larger_r, "")?; + LLVMAngleValue::try_new(angle_ty, r) + } +} + +impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { + fn emit(&mut self, args: EmitOpArgs<'c, ExtensionOp, H>) -> Result<()> { + let module = self.0.get_current_module(); + let float_ty = self.0.iw_context().f64_type(); + let i32_ty = self.0.iw_context().i32_type(); + let builder = self.0.builder(); + let angle_ty = self.1; + + match AngleOp::from_op(&args.node())? { + AngleOp::atrunc => { + let [angle, new_log_denom] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::atrunc expects two arguments"))?; + let new_log_denom = new_log_denom.into_int_value(); + let angle = LLVMAngleValue::try_new(angle_ty, angle)?; + let (value, old_log_denom) = ( + angle.build_get_value(builder)?, + angle.build_get_log_denom(builder)?, + ); + + let denom_increasing = builder.build_int_compare( + inkwell::IntPredicate::UGT, + new_log_denom, + old_log_denom, + "", + )?; + + let increasing_new_value = { + let denom_increase = builder.build_int_sub(new_log_denom, old_log_denom, "")?; + builder.build_left_shift(value, denom_increase, "")? + }; + + let decreasing_new_value = { + let denom_decrease = builder.build_int_sub(old_log_denom, new_log_denom, "")?; + builder.build_right_shift(value, denom_decrease, false, "")? + }; + + let value = builder + .build_select( + denom_increasing, + increasing_new_value, + decreasing_new_value, + "", + )? + .into_int_value(); + let r = angle_ty.build_value(builder, value, new_log_denom)?; + + args.outputs.finish(builder, [r.into()]) + } + AngleOp::aadd => { + let [lhs, rhs] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::aadd expects two arguments"))?; + let r = self.binary_angle_op( + LLVMAngleValue::try_new(angle_ty, lhs)?, + LLVMAngleValue::try_new(angle_ty, rhs)?, + |lhs, rhs| builder.build_int_add(lhs, rhs, ""), + )?; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::asub => { + let [lhs, rhs] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::asub expects one arguments"))?; + let r = self.binary_angle_op( + LLVMAngleValue::try_new(angle_ty, lhs)?, + LLVMAngleValue::try_new(angle_ty, rhs)?, + |lhs, rhs| builder.build_int_sub(lhs, rhs, ""), + )?; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::aneg => { + let [angle] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::aparts expects one arguments"))?; + let angle = LLVMAngleValue::try_new(angle_ty, angle)?; + let log_denom = angle.build_get_log_denom(builder)?; + let value = { + let v = angle.build_get_value_max_denom(builder)?; + let v = builder.build_int_neg(v, "")?; + angle.build_unmax_denom(builder, v)? + }; + let r = angle_ty.build_value(builder, value, log_denom)?; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::anew => { + let [value, log_denom] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::anew expects two arguments"))?; + let r = self.1.build_value(builder, value, log_denom)?; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::aparts => { + let [angle] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::aparts expects one argument"))?; + let angle = LLVMAngleValue::try_new(self.1, angle)?; + let value = angle.build_get_value(builder)?; + let log_denom = angle.build_get_log_denom(builder)?; + args.outputs + .finish(builder, [value.into(), log_denom.into()]) + } + AngleOp::afromrad => { + let [log_denom, rads] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::afromrad expects two arguments"))?; + let log_denom = log_denom.into_int_value(); + let rads: FloatValue<'c> = rads + .try_into() + .map_err(|_| anyhow!("afromrad expects a float argument"))?; + let float_ty = rads.get_type(); + let two_pi = float_ty.const_float(PI * 2.0); + let normalised_rads = { + let normalised_rads = { + let rads_ok = { + let is_fpclass = { + let intrinsic = Intrinsic::find("llvm.is.fpclass") + .ok_or(anyhow!("failed to find 'llvm.is.fpclass' intrinsic"))?; + intrinsic.get_declaration(module, &[float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()]) + .ok_or(anyhow!("failed to get_delcaration 'llvm.is.fpclass' intrinsic for {float_ty}"))? + }; + // bit 0: Signalling Nan + // bit 3: Negative normal + // bit 8: Positive normal + let test = i32_ty.const_int((1 << 0) | (1 << 3) | (1 << 8), false); + builder + .build_call(is_fpclass, &[rads.into(), test.into()], "")? + .try_as_basic_value() + .left() + .ok_or(anyhow!("llvm.is.fpclass has no return value"))? + .into_int_value() + }; + let zero = float_ty.const_zero(); + let ok_rads = builder.build_float_rem(rads, two_pi, "")?; + builder + .build_select(rads_ok, ok_rads, zero, "")? + .into_float_value() + }; + let is_negative = builder.build_float_compare( + FloatPredicate::OLT, + normalised_rads, + rads.get_type().const_zero(), + "", + )?; + let is_negative_r = builder.build_float_add(two_pi, normalised_rads, "")?; + let is_positive_r = normalised_rads; + builder + .build_select(is_negative, is_negative_r, is_positive_r, "")? + .into_float_value() + }; + let value = { + let denom = { + let log_denom = + builder.build_unsigned_int_to_float(log_denom, float_ty, "")?; + let exp2 = { + let intrinsic = Intrinsic::find("llvm.exp2") + .ok_or(anyhow!("failed to find 'llvm.exp2' intrinsic"))?; + intrinsic + .get_declaration(module, &[float_ty.as_basic_type_enum()]) + .ok_or(anyhow!( + "failed to get_delcaration 'llvm.exp2' intrinsic for {float_ty}" + ))? + }; + builder + .build_call(exp2, &[log_denom.into()], "")? + .try_as_basic_value() + .left() + .ok_or(anyhow!("exp2 intrinsic had no return value"))? + .into_float_value() + }; + let value = builder.build_float_mul(normalised_rads, denom, "")?; + builder.build_float_to_unsigned_int(value, angle_ty.value_field_type(), "")? + }; + args.outputs.finish( + builder, + [angle_ty.build_value(builder, value, log_denom)?.into()], + ) + } + AngleOp::atorad => { + let [angle] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::atorad expects one arguments"))?; + let angle = LLVMAngleValue::try_new(angle_ty, angle)?; + let value = angle.build_get_value(builder)?; + let log_denom = angle.build_get_log_denom(builder)?; + let r = { + let value = builder.build_unsigned_int_to_float(value, float_ty, "")?; + let denom = { + let log_denom = + builder.build_unsigned_int_to_float(log_denom, float_ty, "")?; + let exp2 = { + let intrinsic = Intrinsic::find("exp2") + .ok_or(anyhow!("failed to find 'exp2' intrinsic"))?; + intrinsic + .get_declaration(module, &[float_ty.as_basic_type_enum()]) + .ok_or(anyhow!( + "failed to get_delcaration 'exp2' intrinsic for {float_ty}" + ))? + }; + builder + .build_call(exp2, &[log_denom.into()], "")? + .try_as_basic_value() + .left() + .ok_or(anyhow!("exp2 intrinsic had no return value"))? + .into_float_value() + }; + let value = + builder.build_float_mul(value, float_ty.const_float(PI * 2.0), "")?; + builder.build_float_div(value, denom, "")? + }; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::aeq => { + let [lhs, rhs] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::aeq expects two arguments"))?; + let (lhs, rhs) = ( + LLVMAngleValue::try_new(angle_ty, lhs)?, + LLVMAngleValue::try_new(angle_ty, rhs)?, + ); + let lhs_value = lhs.build_get_value_max_denom(builder)?; + let rhs_value = rhs.build_get_value_max_denom(builder)?; + let r = { + let r_i1 = + builder.build_int_compare(IntPredicate::EQ, lhs_value, rhs_value, "")?; + let true_val = emit_value(self.0, &Value::true_val())?; + let false_val = emit_value(self.0, &Value::false_val())?; + self.0 + .builder() + .build_select(r_i1, true_val, false_val, "")? + }; + args.outputs.finish(self.0.builder(), [r.into()]) + } + AngleOp::amul => { + let [lhs, rhs] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::amul expects two arguments"))?; + let r = self.binary_angle_op( + LLVMAngleValue::try_new(angle_ty, lhs)?, + LLVMAngleValue::try_new(angle_ty, rhs)?, + |lhs, rhs| builder.build_int_mul(lhs, rhs, ""), + )?; + args.outputs.finish(builder, [r.into()]) + } + AngleOp::adiv => { + let [lhs, rhs] = args + .inputs + .try_into() + .map_err(|_| anyhow!("AngleOp::adiv expects two arguments"))?; + let r = self.binary_angle_op( + LLVMAngleValue::try_new(angle_ty, lhs)?, + LLVMAngleValue::try_new(angle_ty, rhs)?, + |lhs, rhs| builder.build_int_mul(lhs, rhs, ""), + )?; + args.outputs.finish(builder, [r.into()]) + } + _ => todo!(), + } + } +} diff --git a/src/emit/args.rs b/src/emit/args.rs index ea311a6..963db45 100644 --- a/src/emit/args.rs +++ b/src/emit/args.rs @@ -1,5 +1,5 @@ use hugr::{ops::OpType, HugrView}; -use inkwell::values::BasicValueEnum; +use inkwell::{builder::Builder, values::BasicValueEnum}; use crate::fat::FatNode; @@ -67,4 +67,5 @@ impl<'c, H: HugrView> EmitOpArgs<'c, OpType, H> { outputs, } } + } From 3f7c8bb6904ae87a2b328551f2863a76fcdd40c1 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Tue, 10 Sep 2024 15:44:06 +0100 Subject: [PATCH 02/16] wip --- src/custom/angle.rs | 356 +++++++++++++++++++------------------------- 1 file changed, 153 insertions(+), 203 deletions(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 70dd398..ddc78b3 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -16,7 +16,7 @@ use inkwell::{ values::{AnyValue, AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue, StructValue}, FloatPredicate, IntPredicate, }; -use llvm_sys_140::prelude::{LLVMTypeRef, LLVMValueRef}; +use llvm_sys_140::{core::LLVMBuildFreeze, prelude::{LLVMTypeRef, LLVMValueRef}}; use crate::{ emit::{emit_value, EmitFuncContext, EmitOp, EmitOpArgs}, @@ -26,121 +26,91 @@ use crate::{ use super::CodegenExtension; -use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID}; - -#[derive(Debug, Clone, Copy)] -struct LLVMAngleType<'c>(StructType<'c>); - -impl<'c> LLVMAngleType<'c> { - pub fn new(context: &'c Context, usize_type: IntType<'c>) -> Self { - Self(context.struct_type(&[usize_type.into(), usize_type.into()], false)) - } - - fn value_field_type(&self) -> IntType<'c> { - assert_eq!(2, self.0.get_field_types().len()); - unsafe { self.0.get_field_type_at_index_unchecked(0) }.into_int_type() - } - - fn log_denom_field_type(&self) -> IntType<'c> { - assert_eq!(2, self.0.get_field_types().len()); - unsafe { self.0.get_field_type_at_index_unchecked(1) }.into_int_type() - } - - pub fn const_angle(&self, value: u64, log_denom: u8) -> LLVMAngleValue<'c> { - assert_eq!(2, self.0.get_field_types().len()); - let v = self.0.get_undef(); - v.set_field_at_index(0, self.value_field_type().const_int(value, false)); - v.set_field_at_index( - 1, - self.log_denom_field_type() - .const_int(log_denom as u64, false), - ); - LLVMAngleValue(v, *self) - } - - pub fn build_value( - &self, - builder: &Builder<'c>, - value: impl BasicValue<'c>, - log_denom: impl BasicValue<'c>, - ) -> Result> { - let (value, log_denom) = (value.as_basic_value_enum(), log_denom.as_basic_value_enum()); - ensure!(value.get_type() == self.value_field_type().as_basic_type_enum()); - ensure!(log_denom.get_type() == self.log_denom_field_type().as_basic_type_enum()); - - let r = self.0.get_undef(); - let r = builder.build_insert_value(r, value, 0, "")?; - let r = builder.build_insert_value(r, log_denom, 1, "")?; - Ok(LLVMAngleValue(r.into_struct_value(), *self)) - } -} - -unsafe impl<'c> AsTypeRef for LLVMAngleType<'c> { - fn as_type_ref(&self) -> LLVMTypeRef { - self.0.as_type_ref() - } -} - -unsafe impl<'c> AnyType<'c> for LLVMAngleType<'c> {} -unsafe impl<'c> BasicType<'c> for LLVMAngleType<'c> {} - -#[derive(Debug, Clone, Copy)] -struct LLVMAngleValue<'c>(StructValue<'c>, LLVMAngleType<'c>); - -impl<'c> LLVMAngleValue<'c> { - fn try_new(typ: LLVMAngleType<'c>, value: impl BasicValue<'c>) -> Result { - let value = value.as_basic_value_enum(); - ensure!(typ.as_basic_type_enum() == value.get_type()); - Ok(Self(value.into_struct_value(), typ)) - } - - fn build_get_value(&self, builder: &Builder<'c>) -> Result> { - Ok(builder.build_extract_value(self.0, 0, "")?.into_int_value()) - } - - fn build_get_log_denom(&self, builder: &Builder<'c>) -> Result> { - Ok(builder.build_extract_value(self.0, 1, "")?.into_int_value()) - } - - fn build_unmax_denom( - &self, - builder: &Builder<'c>, - max_denom_value: IntValue<'c>, - ) -> Result> { - let log_denom = self.build_get_log_denom(builder)?; - let shift = builder.build_int_sub(self.1.value_field_type().size_of(), log_denom, "")?; - Ok(builder.build_right_shift(max_denom_value, shift, false, "")?) - } - fn build_get_value_max_denom(&self, builder: &Builder<'c>) -> Result> { - let value = self.build_get_value(builder)?; - let log_denom = self.build_get_log_denom(builder)?; - let shift = builder.build_int_sub(self.1.value_field_type().size_of(), log_denom, "")?; - Ok(builder.build_left_shift(value, shift, "")?) - } -} - -impl<'c> From> for BasicValueEnum<'c> { - fn from(value: LLVMAngleValue<'c>) -> Self { - value.as_basic_value_enum() - } -} - -unsafe impl<'c> AsValueRef for LLVMAngleValue<'c> { - fn as_value_ref(&self) -> LLVMValueRef { - self.0.as_value_ref() - } -} - -unsafe impl<'c> AnyValue<'c> for LLVMAngleValue<'c> {} -unsafe impl<'c> BasicValue<'c> for LLVMAngleValue<'c> {} +use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX}; + +// #[derive(Debug, Clone, Copy)] +// struct LLVMAngleType<'c>(IntType<'c>); + +// impl<'c> LLVMAngleType<'c> { +// pub fn new(usize_type: IntType<'c>) -> Self { +// Self(usize_type) +// } + +// fn value_field_type(&self) -> IntType<'c> { +// self.0 +// } + +// pub fn const_angle(&self, value: u64, log_denom: u8) -> Result> { +// let log_denom = log_denom as u64; +// let width = +// self.0.size_of().get_zero_extended_constant().ok_or(anyhow!("Width of +// usize is not a constant"))?; +// ensure!(width >= log_denom, "log_denom is greater than width of usize: {log_denom} > {width}"); +// Ok(LLVMAngleValue(self.0.const_int(value << (width - log_denom), false), *self)) +// } + +// // pub fn build_value( +// // &self, +// // builder: &Builder<'c>, +// // value: impl BasicValue<'c>, +// // log_denom: impl BasicValue<'c>, +// // ) -> Result> { +// // let (value, log_denom) = (value.as_basic_value_enum(), log_denom.as_basic_value_enum()); +// // ensure!(value.get_type() == self.value_field_type().as_basic_type_enum()); + +// // let r = self.0.get_undef(); +// // let r = builder.build_insert_value(r, value, 0, "")?; +// // let r = builder.build_insert_value(r, log_denom, 1, "")?; +// // Ok(LLVMAngleValue(r.into_struct_value(), *self)) +// // } +// } + +// unsafe impl<'c> AsTypeRef for LLVMAngleType<'c> { +// fn as_type_ref(&self) -> LLVMTypeRef { +// self.0.as_type_ref() +// } +// } + +// unsafe impl<'c> AnyType<'c> for LLVMAngleType<'c> {} +// unsafe impl<'c> BasicType<'c> for LLVMAngleType<'c> {} + +// #[derive(Debug, Clone, Copy)] +// struct LLVMAngleValue<'c>(IntValue<'c>, LLVMAngleType<'c>); + +// impl<'c> LLVMAngleValue<'c> { +// fn try_new(typ: LLVMAngleType<'c>, value: impl BasicValue<'c>) -> Result { +// let value = value.as_basic_value_enum(); +// ensure!(typ.as_basic_type_enum() == value.get_type()); +// Ok(Self(value.into_int_value(), typ)) +// } + +// fn build_get_value(&self, _builder: &Builder<'c>) -> Result> { +// Ok(self.0) +// } +// } + +// impl<'c> From> for BasicValueEnum<'c> { +// fn from(value: LLVMAngleValue<'c>) -> Self { +// value.as_basic_value_enum() +// } +// } + +// unsafe impl<'c> AsValueRef for LLVMAngleValue<'c> { +// fn as_value_ref(&self) -> LLVMValueRef { +// self.0.as_value_ref() +// } +// } + +// unsafe impl<'c> AnyValue<'c> for LLVMAngleValue<'c> {} +// unsafe impl<'c> BasicValue<'c> for LLVMAngleValue<'c> {} pub struct AngleCodegenExtension<'c> { usize_type: IntType<'c>, } impl<'c> AngleCodegenExtension<'c> { - fn angle_type(&self, context: &'c Context) -> LLVMAngleType<'c> { - LLVMAngleType::new(context, self.usize_type) + fn angle_type(&self) -> IntType<'c> { + self.usize_type } } @@ -155,7 +125,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { hugr_type: &CustomType, ) -> Result> { if hugr_type == &ANGLE_CUSTOM_TYPE { - Ok(self.angle_type(context.iw_context()).as_basic_type_enum()) + Ok(self.angle_type().as_basic_type_enum()) } else { bail!("Unsupported type: {hugr_type}") } @@ -167,7 +137,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { ) -> Box + 'a> { Box::new(AngleOpEmitter( context, - self.angle_type(context.iw_context()), + self.angle_type(), )) } @@ -184,10 +154,15 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { let Some(angle) = konst.downcast_ref::() else { return Ok(None); }; - let angle_type = self.angle_type(context.iw_context()); - Ok(Some( - angle_type - .const_angle(angle.value(), angle.log_denom()) + let angle_type = self.angle_type(); + let log_denom = angle.log_denom() as u64; + let width = + angle_type.size_of().get_zero_extended_constant().ok_or(anyhow!("Width of + usize is not a constant"))?; +// ensure!(width >= log_denom, "log_denom is greater than width of usize: {log_denom} > {width}"); +// Ok(LLVMAngleValue(self.0.const_int(value << (width - log_denom), false), *self)) + Ok(Some(angle_type + .const_int(angle.value() << (width - log_denom), false) .as_basic_value_enum(), )) } @@ -195,39 +170,39 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>, LLVMAngleType<'c>); -impl<'c, 'd, H: HugrView> AngleOpEmitter<'c, 'd, H> { - fn binary_angle_op( - &self, - lhs: LLVMAngleValue<'c>, - rhs: LLVMAngleValue<'c>, - go: impl FnOnce(IntValue<'c>, IntValue<'c>) -> Result, E>, - ) -> Result> - where - anyhow::Error: From, - { - let angle_ty = self.1; - let builder = self.0.builder(); - let lhs_value = lhs.build_get_value_max_denom(builder)?; - let rhs_value = lhs.build_get_value_max_denom(builder)?; - let new_value = go(lhs_value, rhs_value)?; - - let lhs_log_denom = lhs.build_get_log_denom(builder)?; - let rhs_log_denom = lhs.build_get_log_denom(builder)?; - - let lhs_log_denom_larger = - builder.build_int_compare(IntPredicate::UGT, lhs_log_denom, rhs_log_denom, "")?; - let lhs_larger_r = { - let v = lhs.build_unmax_denom(builder, new_value)?; - angle_ty.build_value(builder, v, lhs_log_denom)? - }; - let rhs_larger_r = { - let v = rhs.build_unmax_denom(builder, new_value)?; - angle_ty.build_value(builder, v, rhs_log_denom)? - }; - let r = builder.build_select(lhs_log_denom_larger, lhs_larger_r, rhs_larger_r, "")?; - LLVMAngleValue::try_new(angle_ty, r) - } -} +// impl<'c, 'd, H: HugrView> AngleOpEmitter<'c, 'd, H> { +// fn binary_angle_op( +// &self, +// lhs: LLVMAngleValue<'c>, +// rhs: LLVMAngleValue<'c>, +// go: impl FnOnce(IntValue<'c>, IntValue<'c>) -> Result, E>, +// ) -> Result> +// where +// anyhow::Error: From, +// { +// let angle_ty = self.1; +// let builder = self.0.builder(); +// let lhs_value = lhs.build_get_value(builder)?; +// let rhs_value = lhs.build_get_value(builder)?; +// let new_value = go(lhs_value, rhs_value)?; + +// let lhs_log_denom = lhs.build_get_log_denom(builder)?; +// let rhs_log_denom = lhs.build_get_log_denom(builder)?; + +// let lhs_log_denom_larger = +// builder.build_int_compare(IntPredicate::UGT, lhs_log_denom, rhs_log_denom, "")?; +// let lhs_larger_r = { +// let v = lhs.build_unmax_denom(builder, new_value)?; +// angle_ty.build_value(builder, v, lhs_log_denom)? +// }; +// let rhs_larger_r = { +// let v = rhs.build_unmax_denom(builder, new_value)?; +// angle_ty.build_value(builder, v, rhs_log_denom)? +// }; +// let r = builder.build_select(lhs_log_denom_larger, lhs_larger_r, rhs_larger_r, "")?; +// LLVMAngleValue::try_new(angle_ty, r) +// } +// } impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { fn emit(&mut self, args: EmitOpArgs<'c, ExtensionOp, H>) -> Result<()> { @@ -239,56 +214,19 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { match AngleOp::from_op(&args.node())? { AngleOp::atrunc => { - let [angle, new_log_denom] = args + let [angle, _] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::atrunc expects two arguments"))?; - let new_log_denom = new_log_denom.into_int_value(); - let angle = LLVMAngleValue::try_new(angle_ty, angle)?; - let (value, old_log_denom) = ( - angle.build_get_value(builder)?, - angle.build_get_log_denom(builder)?, - ); - - let denom_increasing = builder.build_int_compare( - inkwell::IntPredicate::UGT, - new_log_denom, - old_log_denom, - "", - )?; - - let increasing_new_value = { - let denom_increase = builder.build_int_sub(new_log_denom, old_log_denom, "")?; - builder.build_left_shift(value, denom_increase, "")? - }; - - let decreasing_new_value = { - let denom_decrease = builder.build_int_sub(old_log_denom, new_log_denom, "")?; - builder.build_right_shift(value, denom_decrease, false, "")? - }; - - let value = builder - .build_select( - denom_increasing, - increasing_new_value, - decreasing_new_value, - "", - )? - .into_int_value(); - let r = angle_ty.build_value(builder, value, new_log_denom)?; - - args.outputs.finish(builder, [r.into()]) + args.outputs.finish(builder, [angle.into()]) } AngleOp::aadd => { let [lhs, rhs] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aadd expects two arguments"))?; - let r = self.binary_angle_op( - LLVMAngleValue::try_new(angle_ty, lhs)?, - LLVMAngleValue::try_new(angle_ty, rhs)?, - |lhs, rhs| builder.build_int_add(lhs, rhs, ""), - )?; + let (lhs,rhs) = (lhs.into_int_value(), rhs.into_int_value()); + let r = builder.build_int_add(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } AngleOp::asub => { @@ -296,11 +234,8 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::asub expects one arguments"))?; - let r = self.binary_angle_op( - LLVMAngleValue::try_new(angle_ty, lhs)?, - LLVMAngleValue::try_new(angle_ty, rhs)?, - |lhs, rhs| builder.build_int_sub(lhs, rhs, ""), - )?; + let (lhs,rhs) = (lhs.into_int_value(), rhs.into_int_value()); + let r = builder.build_int_sub(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } AngleOp::aneg => { @@ -308,14 +243,8 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aparts expects one arguments"))?; - let angle = LLVMAngleValue::try_new(angle_ty, angle)?; - let log_denom = angle.build_get_log_denom(builder)?; - let value = { - let v = angle.build_get_value_max_denom(builder)?; - let v = builder.build_int_neg(v, "")?; - angle.build_unmax_denom(builder, v)? - }; - let r = angle_ty.build_value(builder, value, log_denom)?; + let angle = angle.into_int_value(); + let r = builder.build_int_neg(angle, "")?; args.outputs.finish(builder, [r.into()]) } AngleOp::anew => { @@ -323,6 +252,27 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::anew expects two arguments"))?; + let value = value.into_int_value(); + let log_denom = log_denom.into_int_value(); + let denom = builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; + let is_ok = { + let log_denom_ok = { + let log_denom_in_range = builder.build_int_compare(IntPredicate::ULE, log_denom, log_denom.get_type().const_int(LOG_DENOM_MAX as u64, false), "")?; + let width_large_enough = builder.build_int_compare(IntPredicate::ULE, log_denom, angle_ty.size_of(), "")?; + builder.build_and(log_denom_in_range, width_large_enough, "")? + }; + + let value_ok = { + let ok = builder.build_int_compare(IntPredicate::ULT, value, denom, "")?; + // if `log_denom_ok` is false, denom will be poison and so will `ok`. + // We freeze `ok` here since `log_denom_ok` is false and so + // the `and` below does not depend on this value. + unsafe { + IntValue::new(LLVMBuildFreeze(builder.as_mut_ptr(), ok.as_value_ref(), "".as_ref() as *const i8)) + } + }; + builder.build_and(log_denom_ok, value_ok, "")? + }; let r = self.1.build_value(builder, value, log_denom)?; args.outputs.finish(builder, [r.into()]) } From b6e805edee61cfa99c77b0d2edf1bfe13e640cc0 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Wed, 11 Sep 2024 11:16:46 +0100 Subject: [PATCH 03/16] lowerings + emission test --- src/custom/angle.rs | 275 +++++++++--------- ...tom__angle__test__emit_all_ops@llvm14.snap | 53 ++++ ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 100 +++++++ src/emit.rs | 14 +- src/emit/args.rs | 2 +- 5 files changed, 301 insertions(+), 143 deletions(-) create mode 100644 src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap create mode 100644 src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap diff --git a/src/custom/angle.rs b/src/custom/angle.rs index ddc78b3..8d63a68 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -1,30 +1,24 @@ use anyhow::{anyhow, bail, ensure, Result}; -use std::{any::TypeId, char::decode_utf16, f64::consts::PI}; +use std::{any::TypeId, f64::consts::PI, ffi::CString, ptr}; use hugr::{ - extension::{prelude::ConstUsize, simple_op::MakeOpDef, ExtensionId}, + extension::{prelude::{option_type, sum_with_error, ConstError, USIZE_T}, simple_op::MakeOpDef, ExtensionId}, ops::{constant::CustomConst, ExtensionOp, Value}, - std_extensions::arithmetic::int_types::{self, int_type, ConstInt}, - types::{CustomType, SumType}, + types::CustomType, HugrView, }; use inkwell::{ - builder::Builder, - context::Context, - intrinsics::Intrinsic, - types::{AnyType, AsTypeRef, BasicType, BasicTypeEnum, IntType, StructType}, - values::{AnyValue, AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue, StructValue}, - FloatPredicate, IntPredicate, + types::{BasicType, BasicTypeEnum}, + values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, IntPredicate, }; -use llvm_sys_140::{core::LLVMBuildFreeze, prelude::{LLVMTypeRef, LLVMValueRef}}; +use llvm_sys_140::core::LLVMBuildFreeze; use crate::{ - emit::{emit_value, EmitFuncContext, EmitOp, EmitOpArgs}, - sum::LLVMSumType, + emit::{emit_value, get_intrinsic, EmitFuncContext, EmitOp, EmitOpArgs}, types::TypingSession, }; -use super::CodegenExtension; +use super::{CodegenExtension, CodegenExtsMap}; use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX}; @@ -104,17 +98,9 @@ use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTE // unsafe impl<'c> AnyValue<'c> for LLVMAngleValue<'c> {} // unsafe impl<'c> BasicValue<'c> for LLVMAngleValue<'c> {} -pub struct AngleCodegenExtension<'c> { - usize_type: IntType<'c>, -} - -impl<'c> AngleCodegenExtension<'c> { - fn angle_type(&self) -> IntType<'c> { - self.usize_type - } -} +pub struct AngleCodegenExtension; -impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { +impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { fn extension(&self) -> ExtensionId { ANGLE_EXTENSION_ID } @@ -125,7 +111,9 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { hugr_type: &CustomType, ) -> Result> { if hugr_type == &ANGLE_CUSTOM_TYPE { - Ok(self.angle_type().as_basic_type_enum()) + let r = context.llvm_type(&USIZE_T.into())?; + ensure!(r.is_int_type(), "USIZE_T is not an int type"); + Ok(r) } else { bail!("Unsupported type: {hugr_type}") } @@ -137,7 +125,6 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { ) -> Box + 'a> { Box::new(AngleOpEmitter( context, - self.angle_type(), )) } @@ -154,13 +141,10 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { let Some(angle) = konst.downcast_ref::() else { return Ok(None); }; - let angle_type = self.angle_type(); + let angle_type = context.llvm_type(&USIZE_T.into())?.into_int_type(); let log_denom = angle.log_denom() as u64; - let width = - angle_type.size_of().get_zero_extended_constant().ok_or(anyhow!("Width of - usize is not a constant"))?; -// ensure!(width >= log_denom, "log_denom is greater than width of usize: {log_denom} > {width}"); -// Ok(LLVMAngleValue(self.0.const_int(value << (width - log_denom), false), *self)) + let width = angle_type.get_bit_width() as u64; + ensure!(log_denom <= width, "log_denom is greater than width of usize: {log_denom} > {width}"); Ok(Some(angle_type .const_int(angle.value() << (width - log_denom), false) .as_basic_value_enum(), @@ -168,7 +152,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension<'c> { } } -struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>, LLVMAngleType<'c>); +struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>); // impl<'c, 'd, H: HugrView> AngleOpEmitter<'c, 'd, H> { // fn binary_angle_op( @@ -206,11 +190,13 @@ struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>, LLVMAngleType<' impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { fn emit(&mut self, args: EmitOpArgs<'c, ExtensionOp, H>) -> Result<()> { + let ts = self.0.typing_session(); let module = self.0.get_current_module(); let float_ty = self.0.iw_context().f64_type(); let i32_ty = self.0.iw_context().i32_type(); let builder = self.0.builder(); - let angle_ty = self.1; + let angle_ty = self.0.llvm_type(&USIZE_T.into())?.into_int_type(); + let angle_width = angle_ty.get_bit_width() as u64; match AngleOp::from_op(&args.node())? { AngleOp::atrunc => { @@ -255,113 +241,99 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let value = value.into_int_value(); let log_denom = log_denom.into_int_value(); let denom = builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; - let is_ok = { + let ok = { let log_denom_ok = { let log_denom_in_range = builder.build_int_compare(IntPredicate::ULE, log_denom, log_denom.get_type().const_int(LOG_DENOM_MAX as u64, false), "")?; - let width_large_enough = builder.build_int_compare(IntPredicate::ULE, log_denom, angle_ty.size_of(), "")?; + let width_large_enough = builder.build_int_compare(IntPredicate::ULE, log_denom, angle_ty.const_int(angle_width, false), "")?; builder.build_and(log_denom_in_range, width_large_enough, "")? }; let value_ok = { let ok = builder.build_int_compare(IntPredicate::ULT, value, denom, "")?; - // if `log_denom_ok` is false, denom will be poison and so will `ok`. + // if `log_denom_ok` is false, denom may be poison and so may `ok`. // We freeze `ok` here since `log_denom_ok` is false and so - // the `and` below does not depend on this value. + // the `and` below will be false independently of `value_ok''s value. unsafe { - IntValue::new(LLVMBuildFreeze(builder.as_mut_ptr(), ok.as_value_ref(), "".as_ref() as *const i8)) + let str = CString::new("")?; + let r = LLVMBuildFreeze(builder.as_mut_ptr(), ok.as_value_ref(), str.as_ptr()); + assert!(r != ptr::null_mut()); + IntValue::new(r) } }; builder.build_and(log_denom_ok, value_ok, "")? }; - let r = self.1.build_value(builder, value, log_denom)?; - args.outputs.finish(builder, [r.into()]) + let shift = builder.build_int_sub(angle_ty.const_int(angle_width, false), log_denom, "")?; + let value = builder.build_left_shift(value, shift, "")?; + + let ret_sum_ty = ts.llvm_sum_type(sum_with_error(USIZE_T))?; + let success_v = ret_sum_ty.build_tag(builder, 1, vec![value.into()])?; + let error_v = emit_value(self.0, &ConstError::new(3, "Invalid angle").into())?; + let builder = self.0.builder(); + let failure_v = ret_sum_ty.build_tag(builder, 0, vec![error_v])?; + let r = builder.build_select(ok, success_v, failure_v, "")?; + + args.outputs.finish(builder, [r]) } AngleOp::aparts => { let [angle] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aparts expects one argument"))?; - let angle = LLVMAngleValue::try_new(self.1, angle)?; - let value = angle.build_get_value(builder)?; - let log_denom = angle.build_get_log_denom(builder)?; args.outputs - .finish(builder, [value.into(), log_denom.into()]) + .finish(builder, [angle, angle_ty.const_int(angle_width, false).into()]) } AngleOp::afromrad => { - let [log_denom, rads] = args + let [_log_denom, rads] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::afromrad expects two arguments"))?; - let log_denom = log_denom.into_int_value(); let rads: FloatValue<'c> = rads .try_into() .map_err(|_| anyhow!("afromrad expects a float argument"))?; let float_ty = rads.get_type(); let two_pi = float_ty.const_float(PI * 2.0); + // normalised_rads will be in the interval 0..1 let normalised_rads = { - let normalised_rads = { - let rads_ok = { - let is_fpclass = { - let intrinsic = Intrinsic::find("llvm.is.fpclass") - .ok_or(anyhow!("failed to find 'llvm.is.fpclass' intrinsic"))?; - intrinsic.get_declaration(module, &[float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()]) - .ok_or(anyhow!("failed to get_delcaration 'llvm.is.fpclass' intrinsic for {float_ty}"))? - }; - // bit 0: Signalling Nan - // bit 3: Negative normal - // bit 8: Positive normal - let test = i32_ty.const_int((1 << 0) | (1 << 3) | (1 << 8), false); - builder - .build_call(is_fpclass, &[rads.into(), test.into()], "")? - .try_as_basic_value() - .left() - .ok_or(anyhow!("llvm.is.fpclass has no return value"))? - .into_int_value() - }; - let zero = float_ty.const_zero(); - let ok_rads = builder.build_float_rem(rads, two_pi, "")?; - builder - .build_select(rads_ok, ok_rads, zero, "")? - .into_float_value() + let rads_by_2pi = builder.build_float_div(rads, two_pi, "")?; + let floor_rads_by_2pi = { + let floor = get_intrinsic(module, "llvm.floor", [float_ty.into()])?; + builder.build_call(floor, &[rads_by_2pi.into()], "")?.try_as_basic_value().left().ok_or(anyhow!("llvm.floor has no return value"))?.into_float_value() }; - let is_negative = builder.build_float_compare( - FloatPredicate::OLT, - normalised_rads, - rads.get_type().const_zero(), - "", - )?; - let is_negative_r = builder.build_float_add(two_pi, normalised_rads, "")?; - let is_positive_r = normalised_rads; - builder - .build_select(is_negative, is_negative_r, is_positive_r, "")? - .into_float_value() + let normalised_rads = builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")?; + normalised_rads + // let rads_ok = { + // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; + // // We choose to treat {Quiet NaNs, infinities, subnormal values} as zero. + // // Here we pick out the following floats: + // // - bit 0: Signalling Nan + // // - bit 3: Negative normal + // // - bit 8: Positive normal + // let test = i32_ty.const_int((1 << 0) | (1 << 3) | (1 << 8), false); + // builder + // .build_call(is_fpclass, &[rads.into(), test.into()], "")? + // .try_as_basic_value() + // .left() + // .ok_or(anyhow!("llvm.is.fpclass has no return value"))? + // .into_int_value() + // }; + // let zero = float_ty.const_zero(); + // builder.build_select(rads_ok, normalised_rads, zero, "")?.into_float_value() }; + let value = { - let denom = { - let log_denom = - builder.build_unsigned_int_to_float(log_denom, float_ty, "")?; - let exp2 = { - let intrinsic = Intrinsic::find("llvm.exp2") - .ok_or(anyhow!("failed to find 'llvm.exp2' intrinsic"))?; - intrinsic - .get_declaration(module, &[float_ty.as_basic_type_enum()]) - .ok_or(anyhow!( - "failed to get_delcaration 'llvm.exp2' intrinsic for {float_ty}" - ))? - }; - builder - .build_call(exp2, &[log_denom.into()], "")? - .try_as_basic_value() - .left() - .ok_or(anyhow!("exp2 intrinsic had no return value"))? - .into_float_value() - }; - let value = builder.build_float_mul(normalised_rads, denom, "")?; - builder.build_float_to_unsigned_int(value, angle_ty.value_field_type(), "")? + let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; + let log_denom = float_ty.const_float(angle_width as f64); + let denom = builder + .build_call(exp2, &[log_denom.into()], "")? + .try_as_basic_value() + .left() + .ok_or(anyhow!("exp2 intrinsic had no return value"))? + .into_float_value(); + builder.build_float_to_unsigned_int(builder.build_float_add(builder.build_float_mul(normalised_rads, denom, "")?, float_ty.const_float(0.5),"")?, angle_ty, "")? }; args.outputs.finish( builder, - [angle_ty.build_value(builder, value, log_denom)?.into()], + [value.into()], ) } AngleOp::atorad => { @@ -369,25 +341,13 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::atorad expects one arguments"))?; - let angle = LLVMAngleValue::try_new(angle_ty, angle)?; - let value = angle.build_get_value(builder)?; - let log_denom = angle.build_get_log_denom(builder)?; + let angle = angle.into_int_value(); let r = { - let value = builder.build_unsigned_int_to_float(value, float_ty, "")?; + let value = builder.build_unsigned_int_to_float(angle, float_ty, "")?; let denom = { - let log_denom = - builder.build_unsigned_int_to_float(log_denom, float_ty, "")?; - let exp2 = { - let intrinsic = Intrinsic::find("exp2") - .ok_or(anyhow!("failed to find 'exp2' intrinsic"))?; - intrinsic - .get_declaration(module, &[float_ty.as_basic_type_enum()]) - .ok_or(anyhow!( - "failed to get_delcaration 'exp2' intrinsic for {float_ty}" - ))? - }; + let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; builder - .build_call(exp2, &[log_denom.into()], "")? + .build_call(exp2, &[float_ty.const_float(angle_width as f64).into()], "")? .try_as_basic_value() .left() .ok_or(anyhow!("exp2 intrinsic had no return value"))? @@ -404,15 +364,10 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aeq expects two arguments"))?; - let (lhs, rhs) = ( - LLVMAngleValue::try_new(angle_ty, lhs)?, - LLVMAngleValue::try_new(angle_ty, rhs)?, - ); - let lhs_value = lhs.build_get_value_max_denom(builder)?; - let rhs_value = rhs.build_get_value_max_denom(builder)?; + let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); let r = { let r_i1 = - builder.build_int_compare(IntPredicate::EQ, lhs_value, rhs_value, "")?; + builder.build_int_compare(IntPredicate::EQ, lhs, rhs, "")?; let true_val = emit_value(self.0, &Value::true_val())?; let false_val = emit_value(self.0, &Value::false_val())?; self.0 @@ -426,11 +381,8 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::amul expects two arguments"))?; - let r = self.binary_angle_op( - LLVMAngleValue::try_new(angle_ty, lhs)?, - LLVMAngleValue::try_new(angle_ty, rhs)?, - |lhs, rhs| builder.build_int_mul(lhs, rhs, ""), - )?; + let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); + let r = builder.build_int_mul(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } AngleOp::adiv => { @@ -438,14 +390,63 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::adiv expects two arguments"))?; - let r = self.binary_angle_op( - LLVMAngleValue::try_new(angle_ty, lhs)?, - LLVMAngleValue::try_new(angle_ty, rhs)?, - |lhs, rhs| builder.build_int_mul(lhs, rhs, ""), - )?; + let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); + // Division by zero is undefined behaviour in LLVM. Should we: + // - leave this as is. I.e. it is undefined behaviour in HUGR + // - check for zero and branch, then in the is-zero branch: + // - panic + // - return poison. I.e. it is fine in HUGR if you never look at the result + let r = builder.build_int_unsigned_div(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } _ => todo!(), } } } + +pub fn add_angle_extensions<'c,H: HugrView>(cge: CodegenExtsMap<'c,H>) -> CodegenExtsMap<'c,H> { + cge.add_cge(AngleCodegenExtension) +} + +impl<'c,H: HugrView> CodegenExtsMap<'c,H> { + pub fn add_angle_extensions(self) -> Self { + add_angle_extensions(self) + } +} + +#[cfg(test)] +mod test { + use hugr::{builder::{Dataflow as _, DataflowSubContainer as _}, extension::prelude::BOOL_T}; + use rstest::rstest; + use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; + + use crate::{check_emission, emit::test::SimpleHugrConfig, test::{TestContext, llvm_ctx}}; + + use super::*; + + #[rstest] + fn emit_all_ops(mut llvm_ctx: TestContext) { + let hugr = SimpleHugrConfig::new() + .with_ins(vec![ANGLE_TYPE, USIZE_T]) + .with_outs(BOOL_T) + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .finish(|mut builder| { + let [angle, scalar] = builder.input_wires_arr(); + let radians = builder.add_atorad(angle).unwrap(); + let angle = builder.add_afromrad(scalar, radians).unwrap(); + let angle = builder.add_amul(angle, scalar).unwrap(); + // let angle = builder.add_adiv(angle, scalar).unwrap(); + let angle = builder.add_aadd(angle, angle).unwrap(); + let angle = builder.add_asub(angle, angle).unwrap(); + let [num, log_denom] = builder.add_aparts(angle).unwrap(); + let _angle_sum = builder.add_anew(num, log_denom).unwrap(); + let angle = builder.add_aneg(angle).unwrap(); + let angle = builder.add_atrunc(angle, log_denom).unwrap(); + let bool = builder.add_aeq(angle, angle).unwrap(); + builder.finish_with_outputs([bool]).unwrap() + }); + llvm_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions().add_float_extensions()); + check_emission!(hugr, llvm_ctx); + } +} + diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap new file mode 100644 index 0000000..d45c789 --- /dev/null +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap @@ -0,0 +1,53 @@ +--- +source: src/custom/angle.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +@0 = private unnamed_addr constant [14 x i8] c"Invalid angle\00", align 1 + +define { i32, {}, {} } @_hl.main.1(i64 %0, i64 %1) { +alloca_block: + br label %entry_block + +entry_block: ; preds = %alloca_block + %2 = uitofp i64 %0 to double + %3 = call double @llvm.exp2.f64(double 6.400000e+01) + %4 = fmul double %2, 0x401921FB54442D18 + %5 = fdiv double %4, %3 + %6 = fdiv double %5, 0x401921FB54442D18 + %7 = call double @llvm.floor.f64(double %6) + %8 = fsub double %6, %7 + %9 = call double @llvm.exp2.f64(double 6.400000e+01) + %10 = fmul double %8, %9 + %11 = fadd double %10, 5.000000e-01 + %12 = fptoui double %11 to i64 + %13 = mul i64 %12, %1 + %14 = add i64 %13, %13 + %15 = sub i64 %14, %14 + %16 = sub i64 0, %15 + %17 = icmp eq i64 %16, %16 + %18 = select i1 %17, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } + %19 = shl i64 1, 64 + %20 = icmp ule i64 64, 53 + %21 = icmp ule i64 64, 64 + %22 = and i1 %20, %21 + %23 = icmp ult i64 %15, %19 + %24 = freeze i1 %23 + %25 = and i1 %22, %24 + %26 = sub i64 64, 64 + %27 = shl i64 %15, %26 + %28 = insertvalue { i64 } undef, i64 %27, 0 + %29 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %28, 2 + %30 = select i1 %25, { i32, { { i32, i8* } }, { i64 } } %29, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + ret { i32, {}, {} } %18 +} + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.exp2.f64(double) #0 + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.floor.f64(double) #0 + +attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap new file mode 100644 index 0000000..144e588 --- /dev/null +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -0,0 +1,100 @@ +--- +source: src/custom/angle.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +@0 = private unnamed_addr constant [14 x i8] c"Invalid angle\00", align 1 + +define { i32, {}, {} } @_hl.main.1(i64 %0, i64 %1) { +alloca_block: + %"0" = alloca { i32, {}, {} }, align 8 + %"2_0" = alloca i64, align 8 + %"2_1" = alloca i64, align 8 + %"4_0" = alloca double, align 8 + %"5_0" = alloca i64, align 8 + %"6_0" = alloca i64, align 8 + %"8_0" = alloca i64, align 8 + %"10_0" = alloca i64, align 8 + %"14_0" = alloca i64, align 8 + %"12_0" = alloca i64, align 8 + %"12_1" = alloca i64, align 8 + %"16_0" = alloca i64, align 8 + %"18_0" = alloca { i32, {}, {} }, align 8 + %"13_0" = alloca { i32, { { i32, i8* } }, { i64 } }, align 8 + br label %entry_block + +entry_block: ; preds = %alloca_block + store i64 %0, i64* %"2_0", align 4 + store i64 %1, i64* %"2_1", align 4 + %"2_01" = load i64, i64* %"2_0", align 4 + %2 = uitofp i64 %"2_01" to double + %3 = call double @llvm.exp2.f64(double 6.400000e+01) + %4 = fmul double %2, 0x401921FB54442D18 + %5 = fdiv double %4, %3 + store double %5, double* %"4_0", align 8 + %"2_12" = load i64, i64* %"2_1", align 4 + %"4_03" = load double, double* %"4_0", align 8 + %6 = fdiv double %"4_03", 0x401921FB54442D18 + %7 = call double @llvm.floor.f64(double %6) + %8 = fsub double %6, %7 + %9 = call double @llvm.exp2.f64(double 6.400000e+01) + %10 = fmul double %8, %9 + %11 = fadd double %10, 5.000000e-01 + %12 = fptoui double %11 to i64 + store i64 %12, i64* %"5_0", align 4 + %"5_04" = load i64, i64* %"5_0", align 4 + %"2_15" = load i64, i64* %"2_1", align 4 + %13 = mul i64 %"5_04", %"2_15" + store i64 %13, i64* %"6_0", align 4 + %"6_06" = load i64, i64* %"6_0", align 4 + %"6_07" = load i64, i64* %"6_0", align 4 + %14 = add i64 %"6_06", %"6_07" + store i64 %14, i64* %"8_0", align 4 + %"8_08" = load i64, i64* %"8_0", align 4 + %"8_09" = load i64, i64* %"8_0", align 4 + %15 = sub i64 %"8_08", %"8_09" + store i64 %15, i64* %"10_0", align 4 + %"10_010" = load i64, i64* %"10_0", align 4 + %16 = sub i64 0, %"10_010" + store i64 %16, i64* %"14_0", align 4 + %"10_011" = load i64, i64* %"10_0", align 4 + store i64 %"10_011", i64* %"12_0", align 4 + store i64 64, i64* %"12_1", align 4 + %"14_012" = load i64, i64* %"14_0", align 4 + %"12_113" = load i64, i64* %"12_1", align 4 + store i64 %"14_012", i64* %"16_0", align 4 + %"16_014" = load i64, i64* %"16_0", align 4 + %"16_015" = load i64, i64* %"16_0", align 4 + %17 = icmp eq i64 %"16_014", %"16_015" + %18 = select i1 %17, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } + store { i32, {}, {} } %18, { i32, {}, {} }* %"18_0", align 4 + %"18_016" = load { i32, {}, {} }, { i32, {}, {} }* %"18_0", align 4 + store { i32, {}, {} } %"18_016", { i32, {}, {} }* %"0", align 4 + %"12_017" = load i64, i64* %"12_0", align 4 + %"12_118" = load i64, i64* %"12_1", align 4 + %19 = shl i64 1, %"12_118" + %20 = icmp ule i64 %"12_118", 53 + %21 = icmp ule i64 %"12_118", 64 + %22 = and i1 %20, %21 + %23 = icmp ult i64 %"12_017", %19 + %24 = freeze i1 %23 + %25 = and i1 %22, %24 + %26 = sub i64 64, %"12_118" + %27 = shl i64 %"12_017", %26 + %28 = insertvalue { i64 } undef, i64 %27, 0 + %29 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %28, 2 + %30 = select i1 %25, { i32, { { i32, i8* } }, { i64 } } %29, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + store { i32, { { i32, i8* } }, { i64 } } %30, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 + %"019" = load { i32, {}, {} }, { i32, {}, {} }* %"0", align 4 + ret { i32, {}, {} } %"019" +} + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.exp2.f64(double) #0 + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.floor.f64(double) #0 + +attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } diff --git a/src/emit.rs b/src/emit.rs index 715ee42..17d34c5 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -6,11 +6,7 @@ use hugr::{ HugrView, Node, }; use inkwell::{ - builder::Builder, - context::Context, - module::{Linkage, Module}, - types::{AnyType, BasicType, BasicTypeEnum, FunctionType}, - values::{BasicValueEnum, CallSiteValue, FunctionValue, GlobalValue}, + builder::Builder, context::Context, intrinsics::Intrinsic, module::{Linkage, Module}, types::{AnyType, BasicType, BasicTypeEnum, FunctionType}, values::{BasicValueEnum, CallSiteValue, FunctionValue, GlobalValue} }; use std::{collections::HashSet, rc::Rc}; @@ -396,5 +392,13 @@ pub fn deaggregate_call_result<'c>( }) } +pub fn get_intrinsic<'c>(module: &Module<'c>, name: impl AsRef, args: impl AsRef<[BasicTypeEnum<'c>]>) -> Result> { + let (name, args) = (name.as_ref(), args.as_ref()); + let intrinsic = Intrinsic::find(name) + .ok_or(anyhow!("Failed to find intrinsic: '{name}'"))?; + intrinsic.get_declaration(module, args.as_ref()) + .ok_or(anyhow!("failed to get_delcaration for intrisic '{name}' with args '{args:?}'")) +} + #[cfg(test)] pub mod test; diff --git a/src/emit/args.rs b/src/emit/args.rs index 963db45..9e76134 100644 --- a/src/emit/args.rs +++ b/src/emit/args.rs @@ -1,5 +1,5 @@ use hugr::{ops::OpType, HugrView}; -use inkwell::{builder::Builder, values::BasicValueEnum}; +use inkwell::values::BasicValueEnum; use crate::fat::FatNode; From 10f5d57aad0260fc0e3246cabeb482b848c2a84a Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Wed, 11 Sep 2024 11:29:05 +0100 Subject: [PATCH 04/16] lints --- src/custom/angle.rs | 140 +++++++++++++++++++++++++++++--------------- src/emit.rs | 23 ++++++-- src/emit/args.rs | 1 - 3 files changed, 110 insertions(+), 54 deletions(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 8d63a68..c5fc4ba 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -1,15 +1,20 @@ use anyhow::{anyhow, bail, ensure, Result}; -use std::{any::TypeId, f64::consts::PI, ffi::CString, ptr}; +use std::{any::TypeId, f64::consts::PI, ffi::CString}; use hugr::{ - extension::{prelude::{option_type, sum_with_error, ConstError, USIZE_T}, simple_op::MakeOpDef, ExtensionId}, + extension::{ + prelude::{sum_with_error, ConstError, USIZE_T}, + simple_op::MakeOpDef, + ExtensionId, + }, ops::{constant::CustomConst, ExtensionOp, Value}, types::CustomType, HugrView, }; use inkwell::{ - types::{BasicType, BasicTypeEnum}, - values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, IntPredicate, + types::BasicTypeEnum, + values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, + IntPredicate, }; use llvm_sys_140::core::LLVMBuildFreeze; @@ -20,7 +25,9 @@ use crate::{ use super::{CodegenExtension, CodegenExtsMap}; -use tket2::extension::angle::{AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX}; +use tket2::extension::angle::{ + AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX, +}; // #[derive(Debug, Clone, Copy)] // struct LLVMAngleType<'c>(IntType<'c>); @@ -111,7 +118,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { hugr_type: &CustomType, ) -> Result> { if hugr_type == &ANGLE_CUSTOM_TYPE { - let r = context.llvm_type(&USIZE_T.into())?; + let r = context.llvm_type(&USIZE_T)?; ensure!(r.is_int_type(), "USIZE_T is not an int type"); Ok(r) } else { @@ -123,9 +130,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { &'a self, context: &'a mut EmitFuncContext<'c, H>, ) -> Box + 'a> { - Box::new(AngleOpEmitter( - context, - )) + Box::new(AngleOpEmitter(context)) } fn supported_consts(&self) -> std::collections::HashSet { @@ -141,11 +146,15 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { let Some(angle) = konst.downcast_ref::() else { return Ok(None); }; - let angle_type = context.llvm_type(&USIZE_T.into())?.into_int_type(); + let angle_type = context.llvm_type(&USIZE_T)?.into_int_type(); let log_denom = angle.log_denom() as u64; let width = angle_type.get_bit_width() as u64; - ensure!(log_denom <= width, "log_denom is greater than width of usize: {log_denom} > {width}"); - Ok(Some(angle_type + ensure!( + log_denom <= width, + "log_denom is greater than width of usize: {log_denom} > {width}" + ); + Ok(Some( + angle_type .const_int(angle.value() << (width - log_denom), false) .as_basic_value_enum(), )) @@ -193,9 +202,8 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let ts = self.0.typing_session(); let module = self.0.get_current_module(); let float_ty = self.0.iw_context().f64_type(); - let i32_ty = self.0.iw_context().i32_type(); let builder = self.0.builder(); - let angle_ty = self.0.llvm_type(&USIZE_T.into())?.into_int_type(); + let angle_ty = self.0.llvm_type(&USIZE_T)?.into_int_type(); let angle_width = angle_ty.get_bit_width() as u64; match AngleOp::from_op(&args.node())? { @@ -204,14 +212,14 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::atrunc expects two arguments"))?; - args.outputs.finish(builder, [angle.into()]) + args.outputs.finish(builder, [angle]) } AngleOp::aadd => { let [lhs, rhs] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aadd expects two arguments"))?; - let (lhs,rhs) = (lhs.into_int_value(), rhs.into_int_value()); + let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); let r = builder.build_int_add(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } @@ -220,7 +228,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::asub expects one arguments"))?; - let (lhs,rhs) = (lhs.into_int_value(), rhs.into_int_value()); + let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); let r = builder.build_int_sub(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } @@ -240,11 +248,22 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .map_err(|_| anyhow!("AngleOp::anew expects two arguments"))?; let value = value.into_int_value(); let log_denom = log_denom.into_int_value(); - let denom = builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; + let denom = + builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; let ok = { let log_denom_ok = { - let log_denom_in_range = builder.build_int_compare(IntPredicate::ULE, log_denom, log_denom.get_type().const_int(LOG_DENOM_MAX as u64, false), "")?; - let width_large_enough = builder.build_int_compare(IntPredicate::ULE, log_denom, angle_ty.const_int(angle_width, false), "")?; + let log_denom_in_range = builder.build_int_compare( + IntPredicate::ULE, + log_denom, + log_denom.get_type().const_int(LOG_DENOM_MAX as u64, false), + "", + )?; + let width_large_enough = builder.build_int_compare( + IntPredicate::ULE, + log_denom, + angle_ty.const_int(angle_width, false), + "", + )?; builder.build_and(log_denom_in_range, width_large_enough, "")? }; @@ -254,15 +273,17 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { // We freeze `ok` here since `log_denom_ok` is false and so // the `and` below will be false independently of `value_ok''s value. unsafe { - let str = CString::new("")?; - let r = LLVMBuildFreeze(builder.as_mut_ptr(), ok.as_value_ref(), str.as_ptr()); - assert!(r != ptr::null_mut()); - IntValue::new(r) + IntValue::new(LLVMBuildFreeze( + builder.as_mut_ptr(), + ok.as_value_ref(), + CString::default().as_ptr(), + )) } }; builder.build_and(log_denom_ok, value_ok, "")? }; - let shift = builder.build_int_sub(angle_ty.const_int(angle_width, false), log_denom, "")?; + let shift = + builder.build_int_sub(angle_ty.const_int(angle_width, false), log_denom, "")?; let value = builder.build_left_shift(value, shift, "")?; let ret_sum_ty = ts.llvm_sum_type(sum_with_error(USIZE_T))?; @@ -279,8 +300,10 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .inputs .try_into() .map_err(|_| anyhow!("AngleOp::aparts expects one argument"))?; - args.outputs - .finish(builder, [angle, angle_ty.const_int(angle_width, false).into()]) + args.outputs.finish( + builder, + [angle, angle_ty.const_int(angle_width, false).into()], + ) } AngleOp::afromrad => { let [_log_denom, rads] = args @@ -297,10 +320,15 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let rads_by_2pi = builder.build_float_div(rads, two_pi, "")?; let floor_rads_by_2pi = { let floor = get_intrinsic(module, "llvm.floor", [float_ty.into()])?; - builder.build_call(floor, &[rads_by_2pi.into()], "")?.try_as_basic_value().left().ok_or(anyhow!("llvm.floor has no return value"))?.into_float_value() + builder + .build_call(floor, &[rads_by_2pi.into()], "")? + .try_as_basic_value() + .left() + .ok_or(anyhow!("llvm.floor has no return value"))? + .into_float_value() }; - let normalised_rads = builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")?; - normalised_rads + + builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")? // let rads_ok = { // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; // // We choose to treat {Quiet NaNs, infinities, subnormal values} as zero. @@ -329,12 +357,17 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .left() .ok_or(anyhow!("exp2 intrinsic had no return value"))? .into_float_value(); - builder.build_float_to_unsigned_int(builder.build_float_add(builder.build_float_mul(normalised_rads, denom, "")?, float_ty.const_float(0.5),"")?, angle_ty, "")? + builder.build_float_to_unsigned_int( + builder.build_float_add( + builder.build_float_mul(normalised_rads, denom, "")?, + float_ty.const_float(0.5), + "", + )?, + angle_ty, + "", + )? }; - args.outputs.finish( - builder, - [value.into()], - ) + args.outputs.finish(builder, [value.into()]) } AngleOp::atorad => { let [angle] = args @@ -347,7 +380,11 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let denom = { let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; builder - .build_call(exp2, &[float_ty.const_float(angle_width as f64).into()], "")? + .build_call( + exp2, + &[float_ty.const_float(angle_width as f64).into()], + "", + )? .try_as_basic_value() .left() .ok_or(anyhow!("exp2 intrinsic had no return value"))? @@ -366,15 +403,14 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .map_err(|_| anyhow!("AngleOp::aeq expects two arguments"))?; let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); let r = { - let r_i1 = - builder.build_int_compare(IntPredicate::EQ, lhs, rhs, "")?; + let r_i1 = builder.build_int_compare(IntPredicate::EQ, lhs, rhs, "")?; let true_val = emit_value(self.0, &Value::true_val())?; let false_val = emit_value(self.0, &Value::false_val())?; self.0 .builder() .build_select(r_i1, true_val, false_val, "")? }; - args.outputs.finish(self.0.builder(), [r.into()]) + args.outputs.finish(self.0.builder(), [r]) } AngleOp::amul => { let [lhs, rhs] = args @@ -382,7 +418,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .try_into() .map_err(|_| anyhow!("AngleOp::amul expects two arguments"))?; let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - let r = builder.build_int_mul(lhs, rhs, "")?; + let r = builder.build_int_mul(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } AngleOp::adiv => { @@ -396,7 +432,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { // - check for zero and branch, then in the is-zero branch: // - panic // - return poison. I.e. it is fine in HUGR if you never look at the result - let r = builder.build_int_unsigned_div(lhs, rhs, "")?; + let r = builder.build_int_unsigned_div(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } _ => todo!(), @@ -404,11 +440,11 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { } } -pub fn add_angle_extensions<'c,H: HugrView>(cge: CodegenExtsMap<'c,H>) -> CodegenExtsMap<'c,H> { +pub fn add_angle_extensions(cge: CodegenExtsMap<'_, H>) -> CodegenExtsMap<'_, H> { cge.add_cge(AngleCodegenExtension) } -impl<'c,H: HugrView> CodegenExtsMap<'c,H> { +impl<'c, H: HugrView> CodegenExtsMap<'c, H> { pub fn add_angle_extensions(self) -> Self { add_angle_extensions(self) } @@ -416,11 +452,18 @@ impl<'c,H: HugrView> CodegenExtsMap<'c,H> { #[cfg(test)] mod test { - use hugr::{builder::{Dataflow as _, DataflowSubContainer as _}, extension::prelude::BOOL_T}; + use hugr::{ + builder::{Dataflow as _, DataflowSubContainer as _}, + extension::prelude::BOOL_T, + }; use rstest::rstest; use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; - use crate::{check_emission, emit::test::SimpleHugrConfig, test::{TestContext, llvm_ctx}}; + use crate::{ + check_emission, + emit::test::SimpleHugrConfig, + test::{llvm_ctx, TestContext}, + }; use super::*; @@ -445,8 +488,11 @@ mod test { let bool = builder.add_aeq(angle, angle).unwrap(); builder.finish_with_outputs([bool]).unwrap() }); - llvm_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions().add_float_extensions()); + llvm_ctx.add_extensions(|cge| { + cge.add_angle_extensions() + .add_default_prelude_extensions() + .add_float_extensions() + }); check_emission!(hugr, llvm_ctx); } } - diff --git a/src/emit.rs b/src/emit.rs index 17d34c5..7e7f3f7 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -6,7 +6,12 @@ use hugr::{ HugrView, Node, }; use inkwell::{ - builder::Builder, context::Context, intrinsics::Intrinsic, module::{Linkage, Module}, types::{AnyType, BasicType, BasicTypeEnum, FunctionType}, values::{BasicValueEnum, CallSiteValue, FunctionValue, GlobalValue} + builder::Builder, + context::Context, + intrinsics::Intrinsic, + module::{Linkage, Module}, + types::{AnyType, BasicType, BasicTypeEnum, FunctionType}, + values::{BasicValueEnum, CallSiteValue, FunctionValue, GlobalValue}, }; use std::{collections::HashSet, rc::Rc}; @@ -392,12 +397,18 @@ pub fn deaggregate_call_result<'c>( }) } -pub fn get_intrinsic<'c>(module: &Module<'c>, name: impl AsRef, args: impl AsRef<[BasicTypeEnum<'c>]>) -> Result> { +pub fn get_intrinsic<'c>( + module: &Module<'c>, + name: impl AsRef, + args: impl AsRef<[BasicTypeEnum<'c>]>, +) -> Result> { let (name, args) = (name.as_ref(), args.as_ref()); - let intrinsic = Intrinsic::find(name) - .ok_or(anyhow!("Failed to find intrinsic: '{name}'"))?; - intrinsic.get_declaration(module, args.as_ref()) - .ok_or(anyhow!("failed to get_delcaration for intrisic '{name}' with args '{args:?}'")) + let intrinsic = Intrinsic::find(name).ok_or(anyhow!("Failed to find intrinsic: '{name}'"))?; + intrinsic + .get_declaration(module, args.as_ref()) + .ok_or(anyhow!( + "failed to get_delcaration for intrisic '{name}' with args '{args:?}'" + )) } #[cfg(test)] diff --git a/src/emit/args.rs b/src/emit/args.rs index 9e76134..ea311a6 100644 --- a/src/emit/args.rs +++ b/src/emit/args.rs @@ -67,5 +67,4 @@ impl<'c, H: HugrView> EmitOpArgs<'c, OpType, H> { outputs, } } - } From 9fac1e90eb9f6a2b263d32f19aa0d5d4472178e9 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Wed, 11 Sep 2024 13:37:50 +0100 Subject: [PATCH 05/16] exec tests --- src/custom/angle.rs | 170 +++++++++++++++++- ...tom__angle__test__emit_all_ops@llvm14.snap | 4 +- ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 4 +- src/emit/test.rs | 16 +- src/test.rs | 8 +- 5 files changed, 190 insertions(+), 12 deletions(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index c5fc4ba..8f938f8 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -25,6 +25,7 @@ use crate::{ use super::{CodegenExtension, CodegenExtsMap}; + use tket2::extension::angle::{ AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX, }; @@ -382,7 +383,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { builder .build_call( exp2, - &[float_ty.const_float(angle_width as f64).into()], + &[float_ty.const_float(-(angle_width as f64)).into()], "", )? .try_as_basic_value() @@ -392,7 +393,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { }; let value = builder.build_float_mul(value, float_ty.const_float(PI * 2.0), "")?; - builder.build_float_div(value, denom, "")? + builder.build_float_mul(value, denom, "")? }; args.outputs.finish(builder, [r.into()]) } @@ -453,16 +454,17 @@ impl<'c, H: HugrView> CodegenExtsMap<'c, H> { #[cfg(test)] mod test { use hugr::{ - builder::{Dataflow as _, DataflowSubContainer as _}, - extension::prelude::BOOL_T, + builder::{Dataflow, DataflowSubContainer as _, SubContainer}, + extension::prelude::BOOL_T, std_extensions::arithmetic::float_types::FLOAT64_TYPE, }; + use hugr::extension::prelude::ConstUsize; use rstest::rstest; use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; use crate::{ check_emission, emit::test::SimpleHugrConfig, - test::{llvm_ctx, TestContext}, + test::{llvm_ctx, exec_ctx, TestContext}, }; use super::*; @@ -495,4 +497,162 @@ mod test { }); check_emission!(hugr, llvm_ctx); } + + #[rstest] + #[case(1,1, 1 << 63)] + #[case(0,1, 0)] + #[case(3,1, 0)] + #[case(8,4, 1 << 63)] + fn exec_anew(mut exec_ctx: TestContext, #[case] value: u64, #[case] log_denom: u8, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let value = builder.add_load_value(ConstUsize::new(value)); + let log_denom = builder.add_load_value(ConstUsize::new(log_denom as u64)); + let mb_angle = builder.add_anew(value, log_denom).unwrap(); + let r = { + let variants = { + let et = sum_with_error(ANGLE_TYPE); + (0..2).map(|i| et.get_variant(i).unwrap().clone().try_into().unwrap()).collect::>() + }; + let mut conditional = builder.conditional_builder((variants, mb_angle), [], USIZE_T.into()).unwrap(); + { + let mut case = conditional.case_builder(0).unwrap(); + let us0 = case.add_load_value(ConstUsize::new(0)); + case.finish_with_outputs([us0]).unwrap(); + } + { + let mut case = conditional.case_builder(1).unwrap(); + let [angle] = case.input_wires_arr(); + let [value, _log_denom] = case.add_aparts(angle).unwrap(); + case.finish_with_outputs([value]).unwrap(); + } + conditional.finish_sub_container().unwrap().out_wire(0) + }; + builder.finish_with_outputs([r]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::PI, 1, 1 << 63)] + #[case(ConstAngle::PI, LOG_DENOM_MAX, 1 << 63)] + fn exec_atrunc(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case]log_denom: u8, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let angle = builder.add_load_value(angle); + let log_denom = builder.add_load_value(ConstUsize::new(log_denom as u64)); + let angle = builder.add_atrunc(angle, log_denom).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 3 << 62)] + #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] + fn exec_aadd(mut exec_ctx: TestContext, #[case] angle1: ConstAngle, #[case] angle2: ConstAngle, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let angle1 = builder.add_load_value(angle1); + let angle2 = builder.add_load_value(angle2); + let angle = builder.add_aadd(angle1, angle2).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 1 << 62)] + #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] + fn exec_asub(mut exec_ctx: TestContext, #[case] angle1: ConstAngle, #[case] angle2: ConstAngle, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let angle1 = builder.add_load_value(angle1); + let angle2 = builder.add_load_value(angle2); + let angle = builder.add_asub(angle1, angle2).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::PI, 2, 0)] + #[case(ConstAngle::PI, 3, 1 << 63)] + #[case(ConstAngle::PI, 11, 1 << 63)] + fn exec_amul(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] factor: u64, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let angle = builder.add_load_value(angle); + let factor = builder.add_load_value(ConstUsize::new(factor)); + let angle = builder.add_amul(angle, factor).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::PI, 1 << 63)] + #[case(ConstAngle::PI_2, 3 << 62)] + #[case(ConstAngle::PI_4, 7 << 61)] + fn exec_aneg(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] expected_aparts_value: u64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let angle = builder.add_load_value(angle); + let angle = builder.add_aneg(angle).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); + + assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + } + + #[rstest] + #[case(ConstAngle::PI, PI)] + // #[case(ConstAngle::TAU, 2.0 * PI)] + #[case(ConstAngle::PI_2, PI / 2.0)] + #[case(ConstAngle::PI_4, PI / 4.0)] + fn exec_atorad(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] expected_rads: f64) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(FLOAT64_TYPE) + .finish(|mut builder| { + let angle = builder.add_load_value(angle); + let rads = builder.add_atorad(angle).unwrap(); + builder.finish_with_outputs([rads]).unwrap() + }); + exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions().add_float_extensions()); + + let rads = exec_ctx.exec_hugr_f64(hugr, "main"); + dbg!(rads); + assert!(f64::abs(expected_rads - rads) < 0.0000000001); + } + + } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap index d45c789..595c54c 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap @@ -13,9 +13,9 @@ alloca_block: entry_block: ; preds = %alloca_block %2 = uitofp i64 %0 to double - %3 = call double @llvm.exp2.f64(double 6.400000e+01) + %3 = call double @llvm.exp2.f64(double -6.400000e+01) %4 = fmul double %2, 0x401921FB54442D18 - %5 = fdiv double %4, %3 + %5 = fmul double %4, %3 %6 = fdiv double %5, 0x401921FB54442D18 %7 = call double @llvm.floor.f64(double %6) %8 = fsub double %6, %7 diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap index 144e588..f942eba 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -30,9 +30,9 @@ entry_block: ; preds = %alloca_block store i64 %1, i64* %"2_1", align 4 %"2_01" = load i64, i64* %"2_0", align 4 %2 = uitofp i64 %"2_01" to double - %3 = call double @llvm.exp2.f64(double 6.400000e+01) + %3 = call double @llvm.exp2.f64(double -6.400000e+01) %4 = fmul double %2, 0x401921FB54442D18 - %5 = fdiv double %4, %3 + %5 = fmul double %4, %3 store double %5, double* %"4_0", align 8 %"2_12" = load i64, i64* %"2_1", align 4 %"4_03" = load double, double* %"4_0", align 8 diff --git a/src/emit/test.rs b/src/emit/test.rs index 6813e5d..c10160c 100644 --- a/src/emit/test.rs +++ b/src/emit/test.rs @@ -20,6 +20,7 @@ use hugr::types::{Signature, Type, TypeRow}; use hugr::{type_row, Hugr, HugrView}; use inkwell::module::Module; use inkwell::passes::PassManager; +use inkwell::values::GenericValue; use itertools::Itertools; use rstest::rstest; @@ -46,7 +47,7 @@ impl<'c> Emission<'c> { pub fn emit_hugr( hugr: FatNode<'c, hugr::ops::Module, H>, eh: EmitHugr<'c, H>, - ) -> Result { + ) -> Result where { let module = eh.emit_module(hugr)?.finish(); Ok(Self { module }) } @@ -82,6 +83,17 @@ impl<'c> Emission<'c> { /// /// That function must take no arguments and return an `i64`. pub fn exec_u64(&self, entry: impl AsRef) -> Result { + let gv = self.exec_impl(entry)?; + Ok(gv.as_int(false)) + } + + /// TODO + pub fn exec_f64(&self, entry: impl AsRef) -> Result { + let gv = self.exec_impl(entry)?; + Ok(gv.as_float(&self.module.get_context().f64_type())) + } + + pub(crate) fn exec_impl (&self, entry: impl AsRef) -> Result> { let entry_fv = self .module .get_function(entry.as_ref()) @@ -94,7 +106,7 @@ impl<'c> Emission<'c> { .create_jit_execution_engine(inkwell::OptimizationLevel::None) .map_err(|err| anyhow!("Failed to create execution engine: {err}"))?; let fv = ee.get_function_value(entry.as_ref())?; - Ok(unsafe { ee.run_function(fv, &[]).as_int(false) }) + Ok(unsafe { ee.run_function(fv, &[]) }) } } diff --git a/src/test.rs b/src/test.rs index 42daaec..808f506 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use hugr::Hugr; use inkwell::{ context::Context, - types::{BasicType, BasicTypeEnum}, + types::{BasicType, BasicTypeEnum}, values::GenericValue, }; use itertools::Itertools as _; use rstest::fixture; @@ -163,6 +163,12 @@ impl TestContext { emission.exec_u64(entry_point).unwrap() } + + pub fn exec_hugr_f64(&self, hugr: THugrView, entry_point: impl AsRef) -> f64 { + let emission = Emission::emit_hugr(hugr.fat_root().unwrap(), self.get_emit_hugr()).unwrap(); + + emission.exec_f64(entry_point).unwrap() + } } #[fixture] From d396f0525c7e3b308d36efcb4fadf4e352019ed3 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Thu, 12 Sep 2024 09:53:18 +0100 Subject: [PATCH 06/16] lints --- Cargo.lock | 2 + Cargo.toml | 2 + src/custom/angle.rs | 210 ++++++++++++++++-- ...tom__angle__test__emit_all_ops@llvm14.snap | 53 +++-- ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 65 +++--- src/test.rs | 2 +- 6 files changed, 260 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67557a2..f114ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,8 +508,10 @@ dependencies = [ "petgraph", "portgraph", "rstest", + "serde", "serde_json", "tket2", + "typetag", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 34ccea7..4bf70aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ rstest = "0.19.0" portgraph = "0.12.1" pathsearch = "0.2.0" serde_json = "1.0.117" +serde = "*" +typetag = "*" [profile.dev.package] insta.opt-level = 3 diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 8f938f8..5fe642c 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -14,7 +14,7 @@ use hugr::{ use inkwell::{ types::BasicTypeEnum, values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, - IntPredicate, + FloatPredicate, IntPredicate, }; use llvm_sys_140::core::LLVMBuildFreeze; @@ -25,7 +25,6 @@ use crate::{ use super::{CodegenExtension, CodegenExtsMap}; - use tket2::extension::angle::{ AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX, }; @@ -329,8 +328,35 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .into_float_value() }; - builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")? + let normalised_rads = + builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")?; + #[cfg(feature = "llvm14-0")] + let rads_ok = { + let is_pos_inf = builder.build_float_compare( + FloatPredicate::OEQ, + rads, + float_ty.const_float(f64::INFINITY), + "", + )?; + let is_neg_inf = builder.build_float_compare( + FloatPredicate::OEQ, + rads, + float_ty.const_float(f64::NEG_INFINITY), + "", + )?; + let is_nan = builder.build_float_compare( + FloatPredicate::UNO, + rads, + float_ty.const_zero(), + "", + )?; + let r = builder.build_or(is_pos_inf, is_neg_inf, "")?; + let r = builder.build_or(r, is_nan, "")?; + builder.build_not(r, "")? + }; // let rads_ok = { + // let i32_ty = self.0.iw_context().i32_type(); + // let builder = self.0.builder(); // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; // // We choose to treat {Quiet NaNs, infinities, subnormal values} as zero. // // Here we pick out the following floats: @@ -345,8 +371,10 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { // .ok_or(anyhow!("llvm.is.fpclass has no return value"))? // .into_int_value() // }; - // let zero = float_ty.const_zero(); - // builder.build_select(rads_ok, normalised_rads, zero, "")?.into_float_value() + let zero = float_ty.const_zero(); + builder + .build_select(rads_ok, normalised_rads, zero, "")? + .into_float_value() }; let value = { @@ -453,18 +481,23 @@ impl<'c, H: HugrView> CodegenExtsMap<'c, H> { #[cfg(test)] mod test { + use std::collections::HashSet; + + use hugr::extension::prelude::ConstUsize; use hugr::{ builder::{Dataflow, DataflowSubContainer as _, SubContainer}, - extension::prelude::BOOL_T, std_extensions::arithmetic::float_types::FLOAT64_TYPE, + extension::{prelude::BOOL_T, ExtensionSet}, + ops::OpName, + std_extensions::arithmetic::float_types::{self, FLOAT64_TYPE, ConstF64}, }; - use hugr::extension::prelude::ConstUsize; use rstest::rstest; use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; use crate::{ check_emission, emit::test::SimpleHugrConfig, - test::{llvm_ctx, exec_ctx, TestContext}, + test::{exec_ctx, llvm_ctx, TestContext}, + types::HugrType, }; use super::*; @@ -500,10 +533,15 @@ mod test { #[rstest] #[case(1,1, 1 << 63)] - #[case(0,1, 0)] - #[case(3,1, 0)] + #[case(0, 1, 0)] + #[case(3, 1, 0)] #[case(8,4, 1 << 63)] - fn exec_anew(mut exec_ctx: TestContext, #[case] value: u64, #[case] log_denom: u8, #[case] expected_aparts_value: u64) { + fn exec_anew( + mut exec_ctx: TestContext, + #[case] value: u64, + #[case] log_denom: u8, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -514,9 +552,13 @@ mod test { let r = { let variants = { let et = sum_with_error(ANGLE_TYPE); - (0..2).map(|i| et.get_variant(i).unwrap().clone().try_into().unwrap()).collect::>() + (0..2) + .map(|i| et.get_variant(i).unwrap().clone().try_into().unwrap()) + .collect::>() }; - let mut conditional = builder.conditional_builder((variants, mb_angle), [], USIZE_T.into()).unwrap(); + let mut conditional = builder + .conditional_builder((variants, mb_angle), [], USIZE_T.into()) + .unwrap(); { let mut case = conditional.case_builder(0).unwrap(); let us0 = case.add_load_value(ConstUsize::new(0)); @@ -540,7 +582,12 @@ mod test { #[rstest] #[case(ConstAngle::PI, 1, 1 << 63)] #[case(ConstAngle::PI, LOG_DENOM_MAX, 1 << 63)] - fn exec_atrunc(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case]log_denom: u8, #[case] expected_aparts_value: u64) { + fn exec_atrunc( + mut exec_ctx: TestContext, + #[case] angle: ConstAngle, + #[case] log_denom: u8, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -559,7 +606,12 @@ mod test { #[rstest] #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 3 << 62)] #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] - fn exec_aadd(mut exec_ctx: TestContext, #[case] angle1: ConstAngle, #[case] angle2: ConstAngle, #[case] expected_aparts_value: u64) { + fn exec_aadd( + mut exec_ctx: TestContext, + #[case] angle1: ConstAngle, + #[case] angle2: ConstAngle, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -578,7 +630,12 @@ mod test { #[rstest] #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 1 << 62)] #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] - fn exec_asub(mut exec_ctx: TestContext, #[case] angle1: ConstAngle, #[case] angle2: ConstAngle, #[case] expected_aparts_value: u64) { + fn exec_asub( + mut exec_ctx: TestContext, + #[case] angle1: ConstAngle, + #[case] angle2: ConstAngle, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -598,7 +655,12 @@ mod test { #[case(ConstAngle::PI, 2, 0)] #[case(ConstAngle::PI, 3, 1 << 63)] #[case(ConstAngle::PI, 11, 1 << 63)] - fn exec_amul(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] factor: u64, #[case] expected_aparts_value: u64) { + fn exec_amul( + mut exec_ctx: TestContext, + #[case] angle: ConstAngle, + #[case] factor: u64, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -618,7 +680,11 @@ mod test { #[case(ConstAngle::PI, 1 << 63)] #[case(ConstAngle::PI_2, 3 << 62)] #[case(ConstAngle::PI_4, 7 << 61)] - fn exec_aneg(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] expected_aparts_value: u64) { + fn exec_aneg( + mut exec_ctx: TestContext, + #[case] angle: ConstAngle, + #[case] expected_aparts_value: u64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(USIZE_T) @@ -638,7 +704,11 @@ mod test { // #[case(ConstAngle::TAU, 2.0 * PI)] #[case(ConstAngle::PI_2, PI / 2.0)] #[case(ConstAngle::PI_4, PI / 4.0)] - fn exec_atorad(mut exec_ctx: TestContext, #[case] angle: ConstAngle, #[case] expected_rads: f64) { + fn exec_atorad( + mut exec_ctx: TestContext, + #[case] angle: ConstAngle, + #[case] expected_rads: f64, + ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(FLOAT64_TYPE) @@ -647,12 +717,110 @@ mod test { let rads = builder.add_atorad(angle).unwrap(); builder.finish_with_outputs([rads]).unwrap() }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions().add_float_extensions()); + exec_ctx.add_extensions(|cge| { + cge.add_angle_extensions() + .add_default_prelude_extensions() + .add_float_extensions() + }); let rads = exec_ctx.exec_hugr_f64(hugr, "main"); - dbg!(rads); assert!(f64::abs(expected_rads - rads) < 0.0000000001); } + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + struct NonFiniteConst64(f64); + + #[typetag::serde] + impl CustomConst for NonFiniteConst64 { + fn name(&self) -> OpName { + "NonFiniteConst64".into() + } + + fn extension_reqs(&self) -> ExtensionSet { + float_types::EXTENSION_ID.into() + } + + fn get_type(&self) -> HugrType { + FLOAT64_TYPE + } + } + + struct NonFiniteConst64CodegenExtension; + + impl<'c, H: HugrView> CodegenExtension<'c, H> for NonFiniteConst64CodegenExtension { + fn extension(&self) -> ExtensionId { + ExtensionId::new_unchecked("NonFiniteConst64") + } + + fn llvm_type(&self, _: &TypingSession<'c, H>, _: &CustomType) -> Result> { + panic!("no types") + } + + fn emitter<'a>( + &'a self, + _: &'a mut EmitFuncContext<'c, H>, + ) -> Box + 'a> { + panic!("no ops") + } + + fn supported_consts(&self) -> HashSet { + let of = TypeId::of::(); + [of].into_iter().collect() + } + + fn load_constant( + &self, + context: &mut EmitFuncContext<'c, H>, + konst: &dyn CustomConst, + ) -> Result>> { + let Some(NonFiniteConst64(f)) = konst.downcast_ref::() else { + panic!("load_constant") + }; + Ok(Some(context.iw_context().f64_type().const_float(*f).into())) + } + } + + #[rstest] + #[case(PI, 1<<63)] + #[case(-PI, 1<<63)] + // #[case(ConstAngle::TAU, 2.0 * PI)] + #[case(PI / 2.0, 1 << 62)] + #[case(-PI / 2.0, 3 << 62)] + #[case(PI / 4.0, 1 << 61)] + #[case(-PI / 4.0, 7 << 61)] + #[case(13.0 * PI, 1 << 63)] + #[case(-13.0 * PI, 1 << 63)] + #[case(f64::NAN, 0)] + #[case(f64::INFINITY, 0)] + #[case(f64::NEG_INFINITY, 0)] + fn exec_afromrad( + mut exec_ctx: TestContext, + #[case] rads: f64, + #[case] expected_aparts_value: u64, + ) { + let hugr = SimpleHugrConfig::new() + .with_extensions(tket2::extension::REGISTRY.to_owned()) + .with_outs(USIZE_T) + .finish(|mut builder| { + let konst: Value = if rads.is_finite() { + ConstF64::new(rads).into() + } else { + NonFiniteConst64(rads).into() + }; + let rads = builder.add_load_value(konst); + let us4 = builder.add_load_value(ConstUsize::new(4)); + let angle = builder.add_afromrad(us4, rads).unwrap(); + let [value, _log_denom] = builder.add_aparts(angle).unwrap(); + builder.finish_with_outputs([value]).unwrap() + }); + exec_ctx.add_extensions(|cge| { + cge.add_angle_extensions() + .add_default_prelude_extensions() + .add_float_extensions() + .add_cge(NonFiniteConst64CodegenExtension) + }); + let r = exec_ctx.exec_hugr_u64(hugr, "main"); + assert!((expected_aparts_value.wrapping_sub(r) as i64).abs() < 1 << 15); + } } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap index 595c54c..16d794c 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap @@ -19,29 +19,36 @@ entry_block: ; preds = %alloca_block %6 = fdiv double %5, 0x401921FB54442D18 %7 = call double @llvm.floor.f64(double %6) %8 = fsub double %6, %7 - %9 = call double @llvm.exp2.f64(double 6.400000e+01) - %10 = fmul double %8, %9 - %11 = fadd double %10, 5.000000e-01 - %12 = fptoui double %11 to i64 - %13 = mul i64 %12, %1 - %14 = add i64 %13, %13 - %15 = sub i64 %14, %14 - %16 = sub i64 0, %15 - %17 = icmp eq i64 %16, %16 - %18 = select i1 %17, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } - %19 = shl i64 1, 64 - %20 = icmp ule i64 64, 53 - %21 = icmp ule i64 64, 64 - %22 = and i1 %20, %21 - %23 = icmp ult i64 %15, %19 - %24 = freeze i1 %23 - %25 = and i1 %22, %24 - %26 = sub i64 64, 64 - %27 = shl i64 %15, %26 - %28 = insertvalue { i64 } undef, i64 %27, 0 - %29 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %28, 2 - %30 = select i1 %25, { i32, { { i32, i8* } }, { i64 } } %29, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } - ret { i32, {}, {} } %18 + %9 = fcmp oeq double %5, 0x7FF0000000000000 + %10 = fcmp oeq double %5, 0xFFF0000000000000 + %11 = fcmp uno double %5, 0.000000e+00 + %12 = or i1 %9, %10 + %13 = or i1 %12, %11 + %14 = xor i1 %13, true + %15 = select i1 %14, double %8, double 0.000000e+00 + %16 = call double @llvm.exp2.f64(double 6.400000e+01) + %17 = fmul double %15, %16 + %18 = fadd double %17, 5.000000e-01 + %19 = fptoui double %18 to i64 + %20 = mul i64 %19, %1 + %21 = add i64 %20, %20 + %22 = sub i64 %21, %21 + %23 = sub i64 0, %22 + %24 = icmp eq i64 %23, %23 + %25 = select i1 %24, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } + %26 = shl i64 1, 64 + %27 = icmp ule i64 64, 53 + %28 = icmp ule i64 64, 64 + %29 = and i1 %27, %28 + %30 = icmp ult i64 %22, %26 + %31 = freeze i1 %30 + %32 = and i1 %29, %31 + %33 = sub i64 64, 64 + %34 = shl i64 %22, %33 + %35 = insertvalue { i64 } undef, i64 %34, 0 + %36 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %35, 2 + %37 = select i1 %32, { i32, { { i32, i8* } }, { i64 } } %36, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + ret { i32, {}, {} } %25 } ; Function Attrs: nofree nosync nounwind readnone speculatable willreturn diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap index f942eba..1388bc8 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -39,26 +39,33 @@ entry_block: ; preds = %alloca_block %6 = fdiv double %"4_03", 0x401921FB54442D18 %7 = call double @llvm.floor.f64(double %6) %8 = fsub double %6, %7 - %9 = call double @llvm.exp2.f64(double 6.400000e+01) - %10 = fmul double %8, %9 - %11 = fadd double %10, 5.000000e-01 - %12 = fptoui double %11 to i64 - store i64 %12, i64* %"5_0", align 4 + %9 = fcmp oeq double %"4_03", 0x7FF0000000000000 + %10 = fcmp oeq double %"4_03", 0xFFF0000000000000 + %11 = fcmp uno double %"4_03", 0.000000e+00 + %12 = or i1 %9, %10 + %13 = or i1 %12, %11 + %14 = xor i1 %13, true + %15 = select i1 %14, double %8, double 0.000000e+00 + %16 = call double @llvm.exp2.f64(double 6.400000e+01) + %17 = fmul double %15, %16 + %18 = fadd double %17, 5.000000e-01 + %19 = fptoui double %18 to i64 + store i64 %19, i64* %"5_0", align 4 %"5_04" = load i64, i64* %"5_0", align 4 %"2_15" = load i64, i64* %"2_1", align 4 - %13 = mul i64 %"5_04", %"2_15" - store i64 %13, i64* %"6_0", align 4 + %20 = mul i64 %"5_04", %"2_15" + store i64 %20, i64* %"6_0", align 4 %"6_06" = load i64, i64* %"6_0", align 4 %"6_07" = load i64, i64* %"6_0", align 4 - %14 = add i64 %"6_06", %"6_07" - store i64 %14, i64* %"8_0", align 4 + %21 = add i64 %"6_06", %"6_07" + store i64 %21, i64* %"8_0", align 4 %"8_08" = load i64, i64* %"8_0", align 4 %"8_09" = load i64, i64* %"8_0", align 4 - %15 = sub i64 %"8_08", %"8_09" - store i64 %15, i64* %"10_0", align 4 + %22 = sub i64 %"8_08", %"8_09" + store i64 %22, i64* %"10_0", align 4 %"10_010" = load i64, i64* %"10_0", align 4 - %16 = sub i64 0, %"10_010" - store i64 %16, i64* %"14_0", align 4 + %23 = sub i64 0, %"10_010" + store i64 %23, i64* %"14_0", align 4 %"10_011" = load i64, i64* %"10_0", align 4 store i64 %"10_011", i64* %"12_0", align 4 store i64 64, i64* %"12_1", align 4 @@ -67,26 +74,26 @@ entry_block: ; preds = %alloca_block store i64 %"14_012", i64* %"16_0", align 4 %"16_014" = load i64, i64* %"16_0", align 4 %"16_015" = load i64, i64* %"16_0", align 4 - %17 = icmp eq i64 %"16_014", %"16_015" - %18 = select i1 %17, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } - store { i32, {}, {} } %18, { i32, {}, {} }* %"18_0", align 4 + %24 = icmp eq i64 %"16_014", %"16_015" + %25 = select i1 %24, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } + store { i32, {}, {} } %25, { i32, {}, {} }* %"18_0", align 4 %"18_016" = load { i32, {}, {} }, { i32, {}, {} }* %"18_0", align 4 store { i32, {}, {} } %"18_016", { i32, {}, {} }* %"0", align 4 %"12_017" = load i64, i64* %"12_0", align 4 %"12_118" = load i64, i64* %"12_1", align 4 - %19 = shl i64 1, %"12_118" - %20 = icmp ule i64 %"12_118", 53 - %21 = icmp ule i64 %"12_118", 64 - %22 = and i1 %20, %21 - %23 = icmp ult i64 %"12_017", %19 - %24 = freeze i1 %23 - %25 = and i1 %22, %24 - %26 = sub i64 64, %"12_118" - %27 = shl i64 %"12_017", %26 - %28 = insertvalue { i64 } undef, i64 %27, 0 - %29 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %28, 2 - %30 = select i1 %25, { i32, { { i32, i8* } }, { i64 } } %29, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } - store { i32, { { i32, i8* } }, { i64 } } %30, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 + %26 = shl i64 1, %"12_118" + %27 = icmp ule i64 %"12_118", 53 + %28 = icmp ule i64 %"12_118", 64 + %29 = and i1 %27, %28 + %30 = icmp ult i64 %"12_017", %26 + %31 = freeze i1 %30 + %32 = and i1 %29, %31 + %33 = sub i64 64, %"12_118" + %34 = shl i64 %"12_017", %33 + %35 = insertvalue { i64 } undef, i64 %34, 0 + %36 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %35, 2 + %37 = select i1 %32, { i32, { { i32, i8* } }, { i64 } } %36, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + store { i32, { { i32, i8* } }, { i64 } } %37, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 %"019" = load { i32, {}, {} }, { i32, {}, {} }* %"0", align 4 ret { i32, {}, {} } %"019" } diff --git a/src/test.rs b/src/test.rs index 808f506..46cc625 100644 --- a/src/test.rs +++ b/src/test.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use hugr::Hugr; use inkwell::{ context::Context, - types::{BasicType, BasicTypeEnum}, values::GenericValue, + types::{BasicType, BasicTypeEnum}, }; use itertools::Itertools as _; use rstest::fixture; From 34396686ce5bd934b2bd6e2f99fed17795482af0 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Thu, 12 Sep 2024 10:39:30 +0100 Subject: [PATCH 07/16] tidy --- src/custom/angle.rs | 213 ++++++------------ ...tom__angle__test__emit_all_ops@llvm14.snap | 22 +- ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 24 +- src/emit/test.rs | 2 +- 4 files changed, 87 insertions(+), 174 deletions(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 5fe642c..2cf76af 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -12,7 +12,7 @@ use hugr::{ HugrView, }; use inkwell::{ - types::BasicTypeEnum, + types::{BasicTypeEnum, IntType}, values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, FloatPredicate, IntPredicate, }; @@ -29,84 +29,32 @@ use tket2::extension::angle::{ AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX, }; -// #[derive(Debug, Clone, Copy)] -// struct LLVMAngleType<'c>(IntType<'c>); - -// impl<'c> LLVMAngleType<'c> { -// pub fn new(usize_type: IntType<'c>) -> Self { -// Self(usize_type) -// } - -// fn value_field_type(&self) -> IntType<'c> { -// self.0 -// } - -// pub fn const_angle(&self, value: u64, log_denom: u8) -> Result> { -// let log_denom = log_denom as u64; -// let width = -// self.0.size_of().get_zero_extended_constant().ok_or(anyhow!("Width of -// usize is not a constant"))?; -// ensure!(width >= log_denom, "log_denom is greater than width of usize: {log_denom} > {width}"); -// Ok(LLVMAngleValue(self.0.const_int(value << (width - log_denom), false), *self)) -// } - -// // pub fn build_value( -// // &self, -// // builder: &Builder<'c>, -// // value: impl BasicValue<'c>, -// // log_denom: impl BasicValue<'c>, -// // ) -> Result> { -// // let (value, log_denom) = (value.as_basic_value_enum(), log_denom.as_basic_value_enum()); -// // ensure!(value.get_type() == self.value_field_type().as_basic_type_enum()); - -// // let r = self.0.get_undef(); -// // let r = builder.build_insert_value(r, value, 0, "")?; -// // let r = builder.build_insert_value(r, log_denom, 1, "")?; -// // Ok(LLVMAngleValue(r.into_struct_value(), *self)) -// // } -// } - -// unsafe impl<'c> AsTypeRef for LLVMAngleType<'c> { -// fn as_type_ref(&self) -> LLVMTypeRef { -// self.0.as_type_ref() -// } -// } - -// unsafe impl<'c> AnyType<'c> for LLVMAngleType<'c> {} -// unsafe impl<'c> BasicType<'c> for LLVMAngleType<'c> {} - -// #[derive(Debug, Clone, Copy)] -// struct LLVMAngleValue<'c>(IntValue<'c>, LLVMAngleType<'c>); - -// impl<'c> LLVMAngleValue<'c> { -// fn try_new(typ: LLVMAngleType<'c>, value: impl BasicValue<'c>) -> Result { -// let value = value.as_basic_value_enum(); -// ensure!(typ.as_basic_type_enum() == value.get_type()); -// Ok(Self(value.into_int_value(), typ)) -// } - -// fn build_get_value(&self, _builder: &Builder<'c>) -> Result> { -// Ok(self.0) -// } -// } - -// impl<'c> From> for BasicValueEnum<'c> { -// fn from(value: LLVMAngleValue<'c>) -> Self { -// value.as_basic_value_enum() -// } -// } - -// unsafe impl<'c> AsValueRef for LLVMAngleValue<'c> { -// fn as_value_ref(&self) -> LLVMValueRef { -// self.0.as_value_ref() -// } -// } - -// unsafe impl<'c> AnyValue<'c> for LLVMAngleValue<'c> {} -// unsafe impl<'c> BasicValue<'c> for LLVMAngleValue<'c> {} - +/// A codegen extension for the `tket2.angle` extension. +/// +/// We lower [ANGLE_CUSTOM_TYPE] to the same [IntType] to which [USIZE_T] is +/// lowered. We choose to normalise all such values to have a log_denom equal +/// to the width of this type. This makes many operations simple to lower: +/// - `atrunc` becomes a no-op +/// - `aadd`, `asub`, `amul`, `aeq`, `aneg` are simply the equivalent unsigned int operations, +/// which give us the wrapping semantics we require. +/// +/// As a consequence of this choice, `aparts` will always return a `log_denom` +/// of the width of [USIZE_T]. In particular this may be larger than +/// [LOG_DENOM_MAX]. +/// +/// The lowering of `afromrad` arbitrarily treats non-finite input (quiet NaNs, +/// +/- infinity) as zero. +/// +/// We choose not to lower `adiv` as we expect it to be removed: +/// pub struct AngleCodegenExtension; +fn llvm_angle_type<'c, H: HugrView>(ts: &TypingSession<'c, H>) -> Result> { + let usize_t = ts.llvm_type(&USIZE_T)?; + ensure!(usize_t.is_int_type(), "USIZE_T is not an int type"); + Ok(usize_t.into_int_type()) +} + impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { fn extension(&self) -> ExtensionId { ANGLE_EXTENSION_ID @@ -146,7 +94,7 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { let Some(angle) = konst.downcast_ref::() else { return Ok(None); }; - let angle_type = context.llvm_type(&USIZE_T)?.into_int_type(); + let angle_type = llvm_angle_type(&context.typing_session())?; let log_denom = angle.log_denom() as u64; let width = angle_type.get_bit_width() as u64; ensure!( @@ -163,51 +111,20 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>); -// impl<'c, 'd, H: HugrView> AngleOpEmitter<'c, 'd, H> { -// fn binary_angle_op( -// &self, -// lhs: LLVMAngleValue<'c>, -// rhs: LLVMAngleValue<'c>, -// go: impl FnOnce(IntValue<'c>, IntValue<'c>) -> Result, E>, -// ) -> Result> -// where -// anyhow::Error: From, -// { -// let angle_ty = self.1; -// let builder = self.0.builder(); -// let lhs_value = lhs.build_get_value(builder)?; -// let rhs_value = lhs.build_get_value(builder)?; -// let new_value = go(lhs_value, rhs_value)?; - -// let lhs_log_denom = lhs.build_get_log_denom(builder)?; -// let rhs_log_denom = lhs.build_get_log_denom(builder)?; - -// let lhs_log_denom_larger = -// builder.build_int_compare(IntPredicate::UGT, lhs_log_denom, rhs_log_denom, "")?; -// let lhs_larger_r = { -// let v = lhs.build_unmax_denom(builder, new_value)?; -// angle_ty.build_value(builder, v, lhs_log_denom)? -// }; -// let rhs_larger_r = { -// let v = rhs.build_unmax_denom(builder, new_value)?; -// angle_ty.build_value(builder, v, rhs_log_denom)? -// }; -// let r = builder.build_select(lhs_log_denom_larger, lhs_larger_r, rhs_larger_r, "")?; -// LLVMAngleValue::try_new(angle_ty, r) -// } -// } - impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { fn emit(&mut self, args: EmitOpArgs<'c, ExtensionOp, H>) -> Result<()> { let ts = self.0.typing_session(); let module = self.0.get_current_module(); let float_ty = self.0.iw_context().f64_type(); let builder = self.0.builder(); - let angle_ty = self.0.llvm_type(&USIZE_T)?.into_int_type(); + let angle_ty = llvm_angle_type(&ts)?; let angle_width = angle_ty.get_bit_width() as u64; match AngleOp::from_op(&args.node())? { AngleOp::atrunc => { + // As we always normalise angles to have a log_denom of + // angle_width, this is a no-op, and we do not need the + // log_denom. let [angle, _] = args .inputs .try_into() @@ -248,29 +165,26 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .map_err(|_| anyhow!("AngleOp::anew expects two arguments"))?; let value = value.into_int_value(); let log_denom = log_denom.into_int_value(); + // this value may be poison if log_denom is too large. This is + // accounted for below. let denom = builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; let ok = { let log_denom_ok = { - let log_denom_in_range = builder.build_int_compare( + builder.build_int_compare( IntPredicate::ULE, log_denom, - log_denom.get_type().const_int(LOG_DENOM_MAX as u64, false), + log_denom + .get_type() + .const_int(angle_width.min(LOG_DENOM_MAX as u64), false), "", - )?; - let width_large_enough = builder.build_int_compare( - IntPredicate::ULE, - log_denom, - angle_ty.const_int(angle_width, false), - "", - )?; - builder.build_and(log_denom_in_range, width_large_enough, "")? + )? }; let value_ok = { let ok = builder.build_int_compare(IntPredicate::ULT, value, denom, "")?; - // if `log_denom_ok` is false, denom may be poison and so may `ok`. - // We freeze `ok` here since `log_denom_ok` is false and so + // if `log_denom_ok` is false, denom may be poison and hense so may `ok`. + // We freeze `ok` here since in this case `log_denom_ok` is false and so // the `and` below will be false independently of `value_ok''s value. unsafe { IntValue::new(LLVMBuildFreeze( @@ -306,6 +220,8 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { ) } AngleOp::afromrad => { + // As we always normalise angles to have a log_denom of + // angle_width, we do not need the log_denom. let [_log_denom, rads] = args .inputs .try_into() @@ -315,8 +231,12 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .map_err(|_| anyhow!("afromrad expects a float argument"))?; let float_ty = rads.get_type(); let two_pi = float_ty.const_float(PI * 2.0); - // normalised_rads will be in the interval 0..1 + // normalised_rads is in the interval 0..1 let normalised_rads = { + // normalised_rads = (rads / (2 * PI)) - floor(rads / (2 * PI)) + // note that floor(x) gives the smallest integral value less than x + // so this deals with both positive and negative rads + let rads_by_2pi = builder.build_float_div(rads, two_pi, "")?; let floor_rads_by_2pi = { let floor = get_intrinsic(module, "llvm.floor", [float_ty.into()])?; @@ -330,6 +250,13 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let normalised_rads = builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")?; + + // We choose to treat {Quiet NaNs, infinities} as zero. + // the `llvm.is.fpclass` intrinsic was introduced in llvm 15 + // and is the best way to distinguish these float values. + // For now we are using llvm 14, and so we use 3 `feq`s. + // Below is commented code that we can use once we support + // llvm 15. #[cfg(feature = "llvm14-0")] let rads_ok = { let is_pos_inf = builder.build_float_compare( @@ -358,7 +285,6 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { // let i32_ty = self.0.iw_context().i32_type(); // let builder = self.0.builder(); // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; - // // We choose to treat {Quiet NaNs, infinities, subnormal values} as zero. // // Here we pick out the following floats: // // - bit 0: Signalling Nan // // - bit 3: Negative normal @@ -378,6 +304,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { }; let value = { + // value = int(normalised_value * 2 ^ angle_width + .5) let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; let log_denom = float_ty.const_float(angle_width as f64); let denom = builder @@ -405,13 +332,14 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .map_err(|_| anyhow!("AngleOp::atorad expects one arguments"))?; let angle = angle.into_int_value(); let r = { + // r = angle * 2 * PI / 2 ^ angle_width = angle * PI * 2 ^ -(angle_width - 1) let value = builder.build_unsigned_int_to_float(angle, float_ty, "")?; let denom = { let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; builder .build_call( exp2, - &[float_ty.const_float(-(angle_width as f64)).into()], + &[float_ty.const_float(-((angle_width - 1) as f64)).into()], "", )? .try_as_basic_value() @@ -419,8 +347,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { .ok_or(anyhow!("exp2 intrinsic had no return value"))? .into_float_value() }; - let value = - builder.build_float_mul(value, float_ty.const_float(PI * 2.0), "")?; + let value = builder.build_float_mul(value, float_ty.const_float(PI), "")?; builder.build_float_mul(value, denom, "")? }; args.outputs.finish(builder, [r.into()]) @@ -450,21 +377,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let r = builder.build_int_mul(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } - AngleOp::adiv => { - let [lhs, rhs] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::adiv expects two arguments"))?; - let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - // Division by zero is undefined behaviour in LLVM. Should we: - // - leave this as is. I.e. it is undefined behaviour in HUGR - // - check for zero and branch, then in the is-zero branch: - // - panic - // - return poison. I.e. it is fine in HUGR if you never look at the result - let r = builder.build_int_unsigned_div(lhs, rhs, "")?; - args.outputs.finish(builder, [r.into()]) - } - _ => todo!(), + op => bail!("Unsupported op: {op:?}"), } } } @@ -488,7 +401,7 @@ mod test { builder::{Dataflow, DataflowSubContainer as _, SubContainer}, extension::{prelude::BOOL_T, ExtensionSet}, ops::OpName, - std_extensions::arithmetic::float_types::{self, FLOAT64_TYPE, ConstF64}, + std_extensions::arithmetic::float_types::{self, ConstF64, FLOAT64_TYPE}, }; use rstest::rstest; use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; @@ -724,7 +637,8 @@ mod test { }); let rads = exec_ctx.exec_hugr_f64(hugr, "main"); - assert!(f64::abs(expected_rads - rads) < 0.0000000001); + let epsilon = 0.0000000000001; // chosen without too much thought + assert!(f64::abs(expected_rads - rads) < epsilon); } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -821,6 +735,9 @@ mod test { }); let r = exec_ctx.exec_hugr_u64(hugr, "main"); - assert!((expected_aparts_value.wrapping_sub(r) as i64).abs() < 1 << 15); + // chosen without too much thought, except that a f64 has 53 bits of + // precision so 1 << 11 is the lowest reasonable value. + let epsilon = 1 << 15; + assert!((expected_aparts_value.wrapping_sub(r) as i64).abs() < epsilon); } } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap index 16d794c..4c16ccd 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap @@ -13,8 +13,8 @@ alloca_block: entry_block: ; preds = %alloca_block %2 = uitofp i64 %0 to double - %3 = call double @llvm.exp2.f64(double -6.400000e+01) - %4 = fmul double %2, 0x401921FB54442D18 + %3 = call double @llvm.exp2.f64(double -6.300000e+01) + %4 = fmul double %2, 0x400921FB54442D18 %5 = fmul double %4, %3 %6 = fdiv double %5, 0x401921FB54442D18 %7 = call double @llvm.floor.f64(double %6) @@ -38,16 +38,14 @@ entry_block: ; preds = %alloca_block %25 = select i1 %24, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } %26 = shl i64 1, 64 %27 = icmp ule i64 64, 53 - %28 = icmp ule i64 64, 64 - %29 = and i1 %27, %28 - %30 = icmp ult i64 %22, %26 - %31 = freeze i1 %30 - %32 = and i1 %29, %31 - %33 = sub i64 64, 64 - %34 = shl i64 %22, %33 - %35 = insertvalue { i64 } undef, i64 %34, 0 - %36 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %35, 2 - %37 = select i1 %32, { i32, { { i32, i8* } }, { i64 } } %36, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + %28 = icmp ult i64 %22, %26 + %29 = freeze i1 %28 + %30 = and i1 %27, %29 + %31 = sub i64 64, 64 + %32 = shl i64 %22, %31 + %33 = insertvalue { i64 } undef, i64 %32, 0 + %34 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %33, 2 + %35 = select i1 %30, { i32, { { i32, i8* } }, { i64 } } %34, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } ret { i32, {}, {} } %25 } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap index 1388bc8..b437ed9 100644 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap +++ b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -30,8 +30,8 @@ entry_block: ; preds = %alloca_block store i64 %1, i64* %"2_1", align 4 %"2_01" = load i64, i64* %"2_0", align 4 %2 = uitofp i64 %"2_01" to double - %3 = call double @llvm.exp2.f64(double -6.400000e+01) - %4 = fmul double %2, 0x401921FB54442D18 + %3 = call double @llvm.exp2.f64(double -6.300000e+01) + %4 = fmul double %2, 0x400921FB54442D18 %5 = fmul double %4, %3 store double %5, double* %"4_0", align 8 %"2_12" = load i64, i64* %"2_1", align 4 @@ -83,17 +83,15 @@ entry_block: ; preds = %alloca_block %"12_118" = load i64, i64* %"12_1", align 4 %26 = shl i64 1, %"12_118" %27 = icmp ule i64 %"12_118", 53 - %28 = icmp ule i64 %"12_118", 64 - %29 = and i1 %27, %28 - %30 = icmp ult i64 %"12_017", %26 - %31 = freeze i1 %30 - %32 = and i1 %29, %31 - %33 = sub i64 64, %"12_118" - %34 = shl i64 %"12_017", %33 - %35 = insertvalue { i64 } undef, i64 %34, 0 - %36 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %35, 2 - %37 = select i1 %32, { i32, { { i32, i8* } }, { i64 } } %36, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } - store { i32, { { i32, i8* } }, { i64 } } %37, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 + %28 = icmp ult i64 %"12_017", %26 + %29 = freeze i1 %28 + %30 = and i1 %27, %29 + %31 = sub i64 64, %"12_118" + %32 = shl i64 %"12_017", %31 + %33 = insertvalue { i64 } undef, i64 %32, 0 + %34 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %33, 2 + %35 = select i1 %30, { i32, { { i32, i8* } }, { i64 } } %34, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } + store { i32, { { i32, i8* } }, { i64 } } %35, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 %"019" = load { i32, {}, {} }, { i32, {}, {} }* %"0", align 4 ret { i32, {}, {} } %"019" } diff --git a/src/emit/test.rs b/src/emit/test.rs index c10160c..856ff06 100644 --- a/src/emit/test.rs +++ b/src/emit/test.rs @@ -93,7 +93,7 @@ impl<'c> Emission<'c> { Ok(gv.as_float(&self.module.get_context().f64_type())) } - pub(crate) fn exec_impl (&self, entry: impl AsRef) -> Result> { + pub(crate) fn exec_impl(&self, entry: impl AsRef) -> Result> { let entry_fv = self .module .get_function(entry.as_ref()) From 1b653343e217cbf02bdd5d5f487d79e894cb47d1 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:50:56 +0100 Subject: [PATCH 08/16] Update src/emit.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- src/emit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emit.rs b/src/emit.rs index 7e7f3f7..13ce32c 100644 --- a/src/emit.rs +++ b/src/emit.rs @@ -407,7 +407,7 @@ pub fn get_intrinsic<'c>( intrinsic .get_declaration(module, args.as_ref()) .ok_or(anyhow!( - "failed to get_delcaration for intrisic '{name}' with args '{args:?}'" + "failed to get_declaration for intrinsic '{name}' with args '{args:?}'" )) } From 95f9e68f215dad91f2b00195c1a581e65d4820e8 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:54:30 +0100 Subject: [PATCH 09/16] Update src/custom/angle.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- src/custom/angle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 2cf76af..3c8d84e 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -144,7 +144,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let [lhs, rhs] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::asub expects one arguments"))?; + .map_err(|_| anyhow!("AngleOp::asub expects two arguments"))?; let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); let r = builder.build_int_sub(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) From df0bd16c01ae200b0c3596836634551bb7cbb410 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:54:55 +0100 Subject: [PATCH 10/16] Update src/custom/angle.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- src/custom/angle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 3c8d84e..923f78c 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -153,7 +153,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let [angle] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::aparts expects one arguments"))?; + .map_err(|_| anyhow!("AngleOp::aneg expects one argument"))?; let angle = angle.into_int_value(); let r = builder.build_int_neg(angle, "")?; args.outputs.finish(builder, [r.into()]) From 5f4a98302147d0a95abfdd0888e823d80b1e0ac6 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:56:37 +0100 Subject: [PATCH 11/16] Update src/custom/angle.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- src/custom/angle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 923f78c..75a954e 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -329,7 +329,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { let [angle] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::atorad expects one arguments"))?; + .map_err(|_| anyhow!("AngleOp::atorad expects one argument"))?; let angle = angle.into_int_value(); let r = { // r = angle * 2 * PI / 2 ^ angle_width = angle * PI * 2 ^ -(angle_width - 1) From d9cfb4b442954315aa329b38b00d005934bbd419 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Thu, 12 Sep 2024 15:59:43 +0100 Subject: [PATCH 12/16] address review --- src/custom/angle.rs | 9 +++++---- src/emit/test.rs | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/custom/angle.rs b/src/custom/angle.rs index 75a954e..d4904b6 100644 --- a/src/custom/angle.rs +++ b/src/custom/angle.rs @@ -234,8 +234,9 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { // normalised_rads is in the interval 0..1 let normalised_rads = { // normalised_rads = (rads / (2 * PI)) - floor(rads / (2 * PI)) - // note that floor(x) gives the smallest integral value less than x - // so this deals with both positive and negative rads + // note that floor(x) gives the largest integral value less + // than or equal to x so this deals with both positive and + // negative rads. let rads_by_2pi = builder.build_float_div(rads, two_pi, "")?; let floor_rads_by_2pi = { @@ -614,7 +615,8 @@ mod test { #[rstest] #[case(ConstAngle::PI, PI)] - // #[case(ConstAngle::TAU, 2.0 * PI)] + #[ignore = "Fails due to a bug in tket2, fixed by https://github.com/CQCL/tket2/pull/609"] + #[case(ConstAngle::TAU, 2.0 * PI)] #[case(ConstAngle::PI_2, PI / 2.0)] #[case(ConstAngle::PI_4, PI / 4.0)] fn exec_atorad( @@ -697,7 +699,6 @@ mod test { #[rstest] #[case(PI, 1<<63)] #[case(-PI, 1<<63)] - // #[case(ConstAngle::TAU, 2.0 * PI)] #[case(PI / 2.0, 1 << 62)] #[case(-PI / 2.0, 3 << 62)] #[case(PI / 4.0, 1 << 61)] diff --git a/src/emit/test.rs b/src/emit/test.rs index 856ff06..66b1b82 100644 --- a/src/emit/test.rs +++ b/src/emit/test.rs @@ -87,7 +87,9 @@ impl<'c> Emission<'c> { Ok(gv.as_int(false)) } - /// TODO + /// JIT and execute the function named `entry` in the inner module. + /// + /// That function must take no arguments and return an `f64`. pub fn exec_f64(&self, entry: impl AsRef) -> Result { let gv = self.exec_impl(entry)?; Ok(gv.as_float(&self.module.get_context().f64_type())) From 4b2ba1279614afeda299ebae96b5dc6f8c7098bf Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 16 Sep 2024 13:55:15 +0100 Subject: [PATCH 13/16] rename angle module to rotation --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/custom.rs | 2 +- src/custom/{angle.rs => rotation.rs} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename src/custom/{angle.rs => rotation.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index f114ede..1546f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1078,9 +1078,9 @@ dependencies = [ [[package]] name = "tket2" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fb2e60f70575331945d7c23d98b893ded2228d23156fd50da44b5d759a016d" +checksum = "0c35c849d8926181b596794db80286e74f9c40f6798a99514635254fb1e1a4d3" dependencies = [ "bytemuck", "cgmath", diff --git a/Cargo.toml b/Cargo.toml index 4bf70aa..ded6e12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ tket2 = ["dep:tket2"] inkwell = { version = "0.4.0", default-features=false } llvm-sys-140 = { package = "llvm-sys", version = "140.1.3", optional = true} hugr = "0.12.0" -tket2 = { version = "0.3.0", optional = true } +tket2 = { version = "0.4.0", optional = true } anyhow = "1.0.83" itertools = "0.12.1" delegate = "0.12.0" diff --git a/src/custom.rs b/src/custom.rs index 91f9faa..81442c5 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -21,7 +21,7 @@ use crate::{ use super::emit::EmitOp; -pub mod angle; +pub mod rotation; pub mod conversions; pub mod float; pub mod int; diff --git a/src/custom/angle.rs b/src/custom/rotation.rs similarity index 100% rename from src/custom/angle.rs rename to src/custom/rotation.rs From 5a69c83ca2e2e6613c1accc18e3c121fbf16f50c Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 16 Sep 2024 15:16:01 +0100 Subject: [PATCH 14/16] implement codegen extension for tket2.rotation --- src/custom.rs | 2 +- src/custom/rotation.rs | 741 ++++++------------ ...tom__angle__test__emit_all_ops@llvm14.snap | 58 -- ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 105 --- ...__rotation__test__emit_all_ops@llvm14.snap | 66 ++ ...test__emit_all_ops@pre-mem2reg@llvm14.snap | 96 +++ 6 files changed, 385 insertions(+), 683 deletions(-) delete mode 100644 src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap delete mode 100644 src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap create mode 100644 src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@llvm14.snap create mode 100644 src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@pre-mem2reg@llvm14.snap diff --git a/src/custom.rs b/src/custom.rs index 81442c5..6d66e47 100644 --- a/src/custom.rs +++ b/src/custom.rs @@ -21,12 +21,12 @@ use crate::{ use super::emit::EmitOp; -pub mod rotation; pub mod conversions; pub mod float; pub mod int; pub mod logic; pub mod prelude; +pub mod rotation; /// The extension point for lowering HUGR Extensions to LLVM. pub trait CodegenExtension<'c, H> { diff --git a/src/custom/rotation.rs b/src/custom/rotation.rs index d4904b6..5af04f7 100644 --- a/src/custom/rotation.rs +++ b/src/custom/rotation.rs @@ -1,63 +1,41 @@ -use anyhow::{anyhow, bail, ensure, Result}; -use std::{any::TypeId, f64::consts::PI, ffi::CString}; +use anyhow::{anyhow, bail, Result}; +use std::any::TypeId; use hugr::{ - extension::{ - prelude::{sum_with_error, ConstError, USIZE_T}, - simple_op::MakeOpDef, - ExtensionId, - }, - ops::{constant::CustomConst, ExtensionOp, Value}, + extension::{prelude::option_type, simple_op::MakeOpDef, ExtensionId}, + ops::{constant::CustomConst, ExtensionOp}, types::CustomType, HugrView, }; use inkwell::{ - types::{BasicTypeEnum, IntType}, - values::{AsValueRef, BasicValue, BasicValueEnum, FloatValue, IntValue}, - FloatPredicate, IntPredicate, + types::{BasicTypeEnum, FloatType}, + values::BasicValueEnum, + FloatPredicate, }; -use llvm_sys_140::core::LLVMBuildFreeze; use crate::{ - emit::{emit_value, get_intrinsic, EmitFuncContext, EmitOp, EmitOpArgs}, + emit::{get_intrinsic, EmitFuncContext, EmitOp, EmitOpArgs}, types::TypingSession, }; use super::{CodegenExtension, CodegenExtsMap}; -use tket2::extension::angle::{ - AngleOp, ConstAngle, ANGLE_CUSTOM_TYPE, ANGLE_EXTENSION_ID, LOG_DENOM_MAX, +use tket2::extension::rotation::{ + ConstRotation, RotationOp, ROTATION_CUSTOM_TYPE, ROTATION_EXTENSION_ID, ROTATION_TYPE, }; -/// A codegen extension for the `tket2.angle` extension. +/// A codegen extension for the `tket2.rotation` extension. /// -/// We lower [ANGLE_CUSTOM_TYPE] to the same [IntType] to which [USIZE_T] is -/// lowered. We choose to normalise all such values to have a log_denom equal -/// to the width of this type. This makes many operations simple to lower: -/// - `atrunc` becomes a no-op -/// - `aadd`, `asub`, `amul`, `aeq`, `aneg` are simply the equivalent unsigned int operations, -/// which give us the wrapping semantics we require. -/// -/// As a consequence of this choice, `aparts` will always return a `log_denom` -/// of the width of [USIZE_T]. In particular this may be larger than -/// [LOG_DENOM_MAX]. -/// -/// The lowering of `afromrad` arbitrarily treats non-finite input (quiet NaNs, -/// +/- infinity) as zero. -/// -/// We choose not to lower `adiv` as we expect it to be removed: -/// -pub struct AngleCodegenExtension; - -fn llvm_angle_type<'c, H: HugrView>(ts: &TypingSession<'c, H>) -> Result> { - let usize_t = ts.llvm_type(&USIZE_T)?; - ensure!(usize_t.is_int_type(), "USIZE_T is not an int type"); - Ok(usize_t.into_int_type()) +/// We lower [ROTATION_CUSTOM_TYPE] to an `f64`, representing a number of half-turns. +pub struct RotationCodegenExtension; + +fn llvm_angle_type<'c, H: HugrView>(ts: &TypingSession<'c, H>) -> FloatType<'c> { + ts.iw_context().f64_type() } -impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { +impl<'c, H: HugrView> CodegenExtension<'c, H> for RotationCodegenExtension { fn extension(&self) -> ExtensionId { - ANGLE_EXTENSION_ID + ROTATION_EXTENSION_ID } fn llvm_type( @@ -65,10 +43,8 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { context: &TypingSession<'c, H>, hugr_type: &CustomType, ) -> Result> { - if hugr_type == &ANGLE_CUSTOM_TYPE { - let r = context.llvm_type(&USIZE_T)?; - ensure!(r.is_int_type(), "USIZE_T is not an int type"); - Ok(r) + if hugr_type == &ROTATION_CUSTOM_TYPE { + Ok(llvm_angle_type(context).into()) } else { bail!("Unsupported type: {hugr_type}") } @@ -78,11 +54,11 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { &'a self, context: &'a mut EmitFuncContext<'c, H>, ) -> Box + 'a> { - Box::new(AngleOpEmitter(context)) + Box::new(RotationOpEmitter(context)) } fn supported_consts(&self) -> std::collections::HashSet { - let of = TypeId::of::(); + let of = TypeId::of::(); [of].into_iter().collect() } @@ -91,305 +67,141 @@ impl<'c, H: HugrView> CodegenExtension<'c, H> for AngleCodegenExtension { context: &mut EmitFuncContext<'c, H>, konst: &dyn CustomConst, ) -> Result>> { - let Some(angle) = konst.downcast_ref::() else { + let Some(rotation) = konst.downcast_ref::() else { return Ok(None); }; - let angle_type = llvm_angle_type(&context.typing_session())?; - let log_denom = angle.log_denom() as u64; - let width = angle_type.get_bit_width() as u64; - ensure!( - log_denom <= width, - "log_denom is greater than width of usize: {log_denom} > {width}" - ); - Ok(Some( - angle_type - .const_int(angle.value() << (width - log_denom), false) - .as_basic_value_enum(), - )) + let angle_type = llvm_angle_type(&context.typing_session()); + Ok(Some(angle_type.const_float(rotation.half_turns()).into())) } } -struct AngleOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>); +struct RotationOpEmitter<'c, 'd, H>(&'d mut EmitFuncContext<'c, H>); -impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for AngleOpEmitter<'c, '_, H> { +impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for RotationOpEmitter<'c, '_, H> { fn emit(&mut self, args: EmitOpArgs<'c, ExtensionOp, H>) -> Result<()> { let ts = self.0.typing_session(); let module = self.0.get_current_module(); - let float_ty = self.0.iw_context().f64_type(); let builder = self.0.builder(); - let angle_ty = llvm_angle_type(&ts)?; - let angle_width = angle_ty.get_bit_width() as u64; + let angle_ty = llvm_angle_type(&ts); - match AngleOp::from_op(&args.node())? { - AngleOp::atrunc => { - // As we always normalise angles to have a log_denom of - // angle_width, this is a no-op, and we do not need the - // log_denom. - let [angle, _] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::atrunc expects two arguments"))?; - args.outputs.finish(builder, [angle]) - } - AngleOp::aadd => { - let [lhs, rhs] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::aadd expects two arguments"))?; - let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - let r = builder.build_int_add(lhs, rhs, "")?; - args.outputs.finish(builder, [r.into()]) - } - AngleOp::asub => { + match RotationOp::from_op(&args.node())? { + RotationOp::radd => { let [lhs, rhs] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::asub expects two arguments"))?; - let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - let r = builder.build_int_sub(lhs, rhs, "")?; - args.outputs.finish(builder, [r.into()]) - } - AngleOp::aneg => { - let [angle] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::aneg expects one argument"))?; - let angle = angle.into_int_value(); - let r = builder.build_int_neg(angle, "")?; + .map_err(|_| anyhow!("RotationOp::radd expects two arguments"))?; + let (lhs, rhs) = (lhs.into_float_value(), rhs.into_float_value()); + let r = builder.build_float_add(lhs, rhs, "")?; args.outputs.finish(builder, [r.into()]) } - AngleOp::anew => { - let [value, log_denom] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::anew expects two arguments"))?; - let value = value.into_int_value(); - let log_denom = log_denom.into_int_value(); - // this value may be poison if log_denom is too large. This is - // accounted for below. - let denom = - builder.build_left_shift(angle_ty.const_int(1, false), log_denom, "")?; - let ok = { - let log_denom_ok = { - builder.build_int_compare( - IntPredicate::ULE, - log_denom, - log_denom - .get_type() - .const_int(angle_width.min(LOG_DENOM_MAX as u64), false), - "", - )? - }; - - let value_ok = { - let ok = builder.build_int_compare(IntPredicate::ULT, value, denom, "")?; - // if `log_denom_ok` is false, denom may be poison and hense so may `ok`. - // We freeze `ok` here since in this case `log_denom_ok` is false and so - // the `and` below will be false independently of `value_ok''s value. - unsafe { - IntValue::new(LLVMBuildFreeze( - builder.as_mut_ptr(), - ok.as_value_ref(), - CString::default().as_ptr(), - )) - } - }; - builder.build_and(log_denom_ok, value_ok, "")? - }; - let shift = - builder.build_int_sub(angle_ty.const_int(angle_width, false), log_denom, "")?; - let value = builder.build_left_shift(value, shift, "")?; - - let ret_sum_ty = ts.llvm_sum_type(sum_with_error(USIZE_T))?; - let success_v = ret_sum_ty.build_tag(builder, 1, vec![value.into()])?; - let error_v = emit_value(self.0, &ConstError::new(3, "Invalid angle").into())?; - let builder = self.0.builder(); - let failure_v = ret_sum_ty.build_tag(builder, 0, vec![error_v])?; - let r = builder.build_select(ok, success_v, failure_v, "")?; - - args.outputs.finish(builder, [r]) - } - AngleOp::aparts => { - let [angle] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::aparts expects one argument"))?; - args.outputs.finish( - builder, - [angle, angle_ty.const_int(angle_width, false).into()], - ) - } - AngleOp::afromrad => { + RotationOp::from_halfturns => { // As we always normalise angles to have a log_denom of // angle_width, we do not need the log_denom. - let [_log_denom, rads] = args + let [half_turns] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::afromrad expects two arguments"))?; - let rads: FloatValue<'c> = rads - .try_into() - .map_err(|_| anyhow!("afromrad expects a float argument"))?; - let float_ty = rads.get_type(); - let two_pi = float_ty.const_float(PI * 2.0); - // normalised_rads is in the interval 0..1 - let normalised_rads = { - // normalised_rads = (rads / (2 * PI)) - floor(rads / (2 * PI)) - // note that floor(x) gives the largest integral value less - // than or equal to x so this deals with both positive and - // negative rads. - - let rads_by_2pi = builder.build_float_div(rads, two_pi, "")?; - let floor_rads_by_2pi = { - let floor = get_intrinsic(module, "llvm.floor", [float_ty.into()])?; - builder - .build_call(floor, &[rads_by_2pi.into()], "")? - .try_as_basic_value() - .left() - .ok_or(anyhow!("llvm.floor has no return value"))? - .into_float_value() - }; - - let normalised_rads = - builder.build_float_sub(rads_by_2pi, floor_rads_by_2pi, "")?; - - // We choose to treat {Quiet NaNs, infinities} as zero. - // the `llvm.is.fpclass` intrinsic was introduced in llvm 15 - // and is the best way to distinguish these float values. - // For now we are using llvm 14, and so we use 3 `feq`s. - // Below is commented code that we can use once we support - // llvm 15. - #[cfg(feature = "llvm14-0")] - let rads_ok = { - let is_pos_inf = builder.build_float_compare( - FloatPredicate::OEQ, - rads, - float_ty.const_float(f64::INFINITY), - "", - )?; - let is_neg_inf = builder.build_float_compare( - FloatPredicate::OEQ, - rads, - float_ty.const_float(f64::NEG_INFINITY), - "", - )?; - let is_nan = builder.build_float_compare( - FloatPredicate::UNO, - rads, - float_ty.const_zero(), - "", - )?; - let r = builder.build_or(is_pos_inf, is_neg_inf, "")?; - let r = builder.build_or(r, is_nan, "")?; - builder.build_not(r, "")? - }; - // let rads_ok = { - // let i32_ty = self.0.iw_context().i32_type(); - // let builder = self.0.builder(); - // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; - // // Here we pick out the following floats: - // // - bit 0: Signalling Nan - // // - bit 3: Negative normal - // // - bit 8: Positive normal - // let test = i32_ty.const_int((1 << 0) | (1 << 3) | (1 << 8), false); - // builder - // .build_call(is_fpclass, &[rads.into(), test.into()], "")? - // .try_as_basic_value() - // .left() - // .ok_or(anyhow!("llvm.is.fpclass has no return value"))? - // .into_int_value() - // }; - let zero = float_ty.const_zero(); - builder - .build_select(rads_ok, normalised_rads, zero, "")? - .into_float_value() - }; - - let value = { - // value = int(normalised_value * 2 ^ angle_width + .5) - let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; - let log_denom = float_ty.const_float(angle_width as f64); - let denom = builder - .build_call(exp2, &[log_denom.into()], "")? - .try_as_basic_value() - .left() - .ok_or(anyhow!("exp2 intrinsic had no return value"))? - .into_float_value(); - builder.build_float_to_unsigned_int( - builder.build_float_add( - builder.build_float_mul(normalised_rads, denom, "")?, - float_ty.const_float(0.5), + .map_err(|_| anyhow!("RotationOp::from_halfturns expects one arguments"))?; + let half_turns = half_turns.into_float_value(); + + // We must distinguish {NaNs, infinities} from finite + // values. The `llvm.is.fpclass` intrinsic was introduced in llvm 15 + // and is the best way to do so. For now we are using llvm + // 14, and so we use 3 `feq`s. + // Below is commented code that we can use once we support llvm 15. + #[cfg(feature = "llvm14-0")] + let half_turns_ok = { + let is_pos_inf = builder.build_float_compare( + FloatPredicate::OEQ, + half_turns, + angle_ty.const_float(f64::INFINITY), + "", + )?; + let is_neg_inf = builder.build_float_compare( + FloatPredicate::OEQ, + half_turns, + angle_ty.const_float(f64::NEG_INFINITY), + "", + )?; + let is_nan = builder.build_float_compare( + FloatPredicate::UNO, + half_turns, + angle_ty.const_zero(), + "", + )?; + builder.build_not( + builder.build_or( + builder.build_or(is_pos_inf, is_neg_inf, "")?, + is_nan, "", )?, - angle_ty, "", )? }; - args.outputs.finish(builder, [value.into()]) + // let rads_ok = { + // let i32_ty = self.0.iw_context().i32_type(); + // let builder = self.0.builder(); + // let is_fpclass = get_intrinsic(module, "llvm.is.fpclass", [float_ty.as_basic_type_enum(), i32_ty.as_basic_type_enum()])?; + // // Here we pick out the following floats: + // // - bit 0: Signalling Nan + // // - bit 3: Negative normal + // // - bit 8: Positive normal + // let test = i32_ty.const_int((1 << 0) | (1 << 3) | (1 << 8), false); + // builder + // .build_call(is_fpclass, &[rads.into(), test.into()], "")? + // .try_as_basic_value() + // .left() + // .ok_or(anyhow!("llvm.is.fpclass has no return value"))? + // .into_int_value() + // }; + + let result_sum_type = ts.llvm_sum_type(option_type(ROTATION_TYPE))?; + let rads_success = + result_sum_type.build_tag(builder, 1, vec![half_turns.into()])?; + let rads_failure = result_sum_type.build_tag(builder, 0, vec![])?; + let result = builder.build_select(half_turns_ok, rads_success, rads_failure, "")?; + args.outputs.finish(builder, [result]) } - AngleOp::atorad => { - let [angle] = args + RotationOp::to_halfturns => { + let [half_turns] = args .inputs .try_into() .map_err(|_| anyhow!("AngleOp::atorad expects one argument"))?; - let angle = angle.into_int_value(); - let r = { - // r = angle * 2 * PI / 2 ^ angle_width = angle * PI * 2 ^ -(angle_width - 1) - let value = builder.build_unsigned_int_to_float(angle, float_ty, "")?; - let denom = { - let exp2 = get_intrinsic(module, "llvm.exp2", [float_ty.into()])?; + let half_turns = half_turns.into_float_value(); + + // normalised_half_turns is in the interval 0..2 + let normalised_half_turns = { + // normalised_rads = (half_turns/2 - floor(half_turns/2)) * 2 + // note that floor(x) gives the largest integral value less + // than or equal to x so this deals with both positive and + // negative rads. + let turns = + builder.build_float_div(half_turns, angle_ty.const_float(2.0), "")?; + let floor_turns = { + let floor = get_intrinsic(module, "llvm.floor", [angle_ty.into()])?; builder - .build_call( - exp2, - &[float_ty.const_float(-((angle_width - 1) as f64)).into()], - "", - )? + .build_call(floor, &[turns.into()], "")? .try_as_basic_value() .left() - .ok_or(anyhow!("exp2 intrinsic had no return value"))? + .ok_or(anyhow!("llvm.floor has no return value"))? .into_float_value() }; - let value = builder.build_float_mul(value, float_ty.const_float(PI), "")?; - builder.build_float_mul(value, denom, "")? + let normalised_turns = builder.build_float_sub(turns, floor_turns, "")?; + builder.build_float_mul(normalised_turns, angle_ty.const_float(2.0), "")? }; - args.outputs.finish(builder, [r.into()]) - } - AngleOp::aeq => { - let [lhs, rhs] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::aeq expects two arguments"))?; - let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - let r = { - let r_i1 = builder.build_int_compare(IntPredicate::EQ, lhs, rhs, "")?; - let true_val = emit_value(self.0, &Value::true_val())?; - let false_val = emit_value(self.0, &Value::false_val())?; - self.0 - .builder() - .build_select(r_i1, true_val, false_val, "")? - }; - args.outputs.finish(self.0.builder(), [r]) - } - AngleOp::amul => { - let [lhs, rhs] = args - .inputs - .try_into() - .map_err(|_| anyhow!("AngleOp::amul expects two arguments"))?; - let (lhs, rhs) = (lhs.into_int_value(), rhs.into_int_value()); - let r = builder.build_int_mul(lhs, rhs, "")?; - args.outputs.finish(builder, [r.into()]) + args.outputs.finish(builder, [normalised_half_turns.into()]) } op => bail!("Unsupported op: {op:?}"), } } } -pub fn add_angle_extensions(cge: CodegenExtsMap<'_, H>) -> CodegenExtsMap<'_, H> { - cge.add_cge(AngleCodegenExtension) +pub fn add_rotation_extensions(cge: CodegenExtsMap<'_, H>) -> CodegenExtsMap<'_, H> { + cge.add_cge(RotationCodegenExtension) } impl<'c, H: HugrView> CodegenExtsMap<'c, H> { - pub fn add_angle_extensions(self) -> Self { - add_angle_extensions(self) + pub fn add_rotation_extensions(self) -> Self { + add_rotation_extensions(self) } } @@ -397,16 +209,16 @@ impl<'c, H: HugrView> CodegenExtsMap<'c, H> { mod test { use std::collections::HashSet; - use hugr::extension::prelude::ConstUsize; use hugr::{ builder::{Dataflow, DataflowSubContainer as _, SubContainer}, - extension::{prelude::BOOL_T, ExtensionSet}, + extension::ExtensionSet, ops::OpName, std_extensions::arithmetic::float_types::{self, ConstF64, FLOAT64_TYPE}, }; use rstest::rstest; - use tket2::extension::angle::{AngleOpBuilder as _, ANGLE_TYPE}; + use tket2::extension::rotation::{RotationOpBuilder as _, ROTATION_TYPE}; + use crate::utils::UnwrapBuilder; use crate::{ check_emission, emit::test::SimpleHugrConfig, @@ -419,26 +231,24 @@ mod test { #[rstest] fn emit_all_ops(mut llvm_ctx: TestContext) { let hugr = SimpleHugrConfig::new() - .with_ins(vec![ANGLE_TYPE, USIZE_T]) - .with_outs(BOOL_T) + .with_ins(vec![ROTATION_TYPE]) .with_extensions(tket2::extension::REGISTRY.to_owned()) - .finish(|mut builder| { - let [angle, scalar] = builder.input_wires_arr(); - let radians = builder.add_atorad(angle).unwrap(); - let angle = builder.add_afromrad(scalar, radians).unwrap(); - let angle = builder.add_amul(angle, scalar).unwrap(); - // let angle = builder.add_adiv(angle, scalar).unwrap(); - let angle = builder.add_aadd(angle, angle).unwrap(); - let angle = builder.add_asub(angle, angle).unwrap(); - let [num, log_denom] = builder.add_aparts(angle).unwrap(); - let _angle_sum = builder.add_anew(num, log_denom).unwrap(); - let angle = builder.add_aneg(angle).unwrap(); - let angle = builder.add_atrunc(angle, log_denom).unwrap(); - let bool = builder.add_aeq(angle, angle).unwrap(); - builder.finish_with_outputs([bool]).unwrap() + .finish_with_exts(|mut builder, reg| { + let [rot1] = builder.input_wires_arr(); + let half_turns = builder.add_to_halfturns(rot1).unwrap(); + let [rot2] = { + let mb_rot = builder.add_from_halfturns(half_turns).unwrap(); + builder + .build_unwrap_sum(reg, 1, option_type(ROTATION_TYPE), mb_rot) + .unwrap() + }; + let _ = builder + .add_dataflow_op(RotationOp::radd, [rot1, rot2]) + .unwrap(); + builder.finish_sub_container().unwrap() }); llvm_ctx.add_extensions(|cge| { - cge.add_angle_extensions() + cge.add_rotation_extensions() .add_default_prelude_extensions() .add_float_extensions() }); @@ -446,201 +256,71 @@ mod test { } #[rstest] - #[case(1,1, 1 << 63)] - #[case(0, 1, 0)] - #[case(3, 1, 0)] - #[case(8,4, 1 << 63)] - fn exec_anew( - mut exec_ctx: TestContext, - #[case] value: u64, - #[case] log_denom: u8, - #[case] expected_aparts_value: u64, - ) { - let hugr = SimpleHugrConfig::new() - .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) - .finish(|mut builder| { - let value = builder.add_load_value(ConstUsize::new(value)); - let log_denom = builder.add_load_value(ConstUsize::new(log_denom as u64)); - let mb_angle = builder.add_anew(value, log_denom).unwrap(); - let r = { - let variants = { - let et = sum_with_error(ANGLE_TYPE); - (0..2) - .map(|i| et.get_variant(i).unwrap().clone().try_into().unwrap()) - .collect::>() - }; - let mut conditional = builder - .conditional_builder((variants, mb_angle), [], USIZE_T.into()) - .unwrap(); - { - let mut case = conditional.case_builder(0).unwrap(); - let us0 = case.add_load_value(ConstUsize::new(0)); - case.finish_with_outputs([us0]).unwrap(); - } - { - let mut case = conditional.case_builder(1).unwrap(); - let [angle] = case.input_wires_arr(); - let [value, _log_denom] = case.add_aparts(angle).unwrap(); - case.finish_with_outputs([value]).unwrap(); - } - conditional.finish_sub_container().unwrap().out_wire(0) - }; - builder.finish_with_outputs([r]).unwrap() - }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); - } - - #[rstest] - #[case(ConstAngle::PI, 1, 1 << 63)] - #[case(ConstAngle::PI, LOG_DENOM_MAX, 1 << 63)] - fn exec_atrunc( - mut exec_ctx: TestContext, - #[case] angle: ConstAngle, - #[case] log_denom: u8, - #[case] expected_aparts_value: u64, - ) { - let hugr = SimpleHugrConfig::new() - .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) - .finish(|mut builder| { - let angle = builder.add_load_value(angle); - let log_denom = builder.add_load_value(ConstUsize::new(log_denom as u64)); - let angle = builder.add_atrunc(angle, log_denom).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); - builder.finish_with_outputs([value]).unwrap() - }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); - } - - #[rstest] - #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 3 << 62)] - #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] + #[case(ConstRotation::new(1.0).unwrap(), ConstRotation::new(0.5).unwrap(), 1.5)] + #[case(ConstRotation::PI, ConstRotation::new(1.5).unwrap(), 0.5)] fn exec_aadd( mut exec_ctx: TestContext, - #[case] angle1: ConstAngle, - #[case] angle2: ConstAngle, - #[case] expected_aparts_value: u64, + #[case] angle1: ConstRotation, + #[case] angle2: ConstRotation, + #[case] expected_half_turns: f64, ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) - .finish(|mut builder| { - let angle1 = builder.add_load_value(angle1); - let angle2 = builder.add_load_value(angle2); - let angle = builder.add_aadd(angle1, angle2).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); - builder.finish_with_outputs([value]).unwrap() - }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); - } - - #[rstest] - #[case(ConstAngle::new(1, 1).unwrap(), ConstAngle::new(4, 4).unwrap(), 1 << 62)] - #[case(ConstAngle::PI, ConstAngle::new(4, 8).unwrap(), 0)] - fn exec_asub( - mut exec_ctx: TestContext, - #[case] angle1: ConstAngle, - #[case] angle2: ConstAngle, - #[case] expected_aparts_value: u64, - ) { - let hugr = SimpleHugrConfig::new() - .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) - .finish(|mut builder| { - let angle1 = builder.add_load_value(angle1); - let angle2 = builder.add_load_value(angle2); - let angle = builder.add_asub(angle1, angle2).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); - builder.finish_with_outputs([value]).unwrap() - }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); - } - - #[rstest] - #[case(ConstAngle::PI, 2, 0)] - #[case(ConstAngle::PI, 3, 1 << 63)] - #[case(ConstAngle::PI, 11, 1 << 63)] - fn exec_amul( - mut exec_ctx: TestContext, - #[case] angle: ConstAngle, - #[case] factor: u64, - #[case] expected_aparts_value: u64, - ) { - let hugr = SimpleHugrConfig::new() - .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) + .with_outs(FLOAT64_TYPE) .finish(|mut builder| { - let angle = builder.add_load_value(angle); - let factor = builder.add_load_value(ConstUsize::new(factor)); - let angle = builder.add_amul(angle, factor).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); - builder.finish_with_outputs([value]).unwrap() - }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); - } + let rot2 = builder.add_load_value(angle1); + let rot1 = builder.add_load_value(angle2); + let rot = builder + .add_dataflow_op(RotationOp::radd, [rot1, rot2]) + .unwrap() + .out_wire(0); + let value = builder.add_to_halfturns(rot).unwrap(); - #[rstest] - #[case(ConstAngle::PI, 1 << 63)] - #[case(ConstAngle::PI_2, 3 << 62)] - #[case(ConstAngle::PI_4, 7 << 61)] - fn exec_aneg( - mut exec_ctx: TestContext, - #[case] angle: ConstAngle, - #[case] expected_aparts_value: u64, - ) { - let hugr = SimpleHugrConfig::new() - .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) - .finish(|mut builder| { - let angle = builder.add_load_value(angle); - let angle = builder.add_aneg(angle).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); builder.finish_with_outputs([value]).unwrap() }); - exec_ctx.add_extensions(|cge| cge.add_angle_extensions().add_default_prelude_extensions()); - - assert_eq!(expected_aparts_value, exec_ctx.exec_hugr_u64(hugr, "main")); + exec_ctx.add_extensions(|cge| { + cge.add_rotation_extensions() + .add_default_prelude_extensions() + .add_float_extensions() + }); + let half_turns = exec_ctx.exec_hugr_f64(hugr, "main"); + let epsilon = 0.0000000000001; // chosen without too much thought + assert!( + f64::abs(expected_half_turns - half_turns) < epsilon, + "abs({expected_half_turns} - {half_turns}) >= {epsilon}" + ); } #[rstest] - #[case(ConstAngle::PI, PI)] - #[ignore = "Fails due to a bug in tket2, fixed by https://github.com/CQCL/tket2/pull/609"] - #[case(ConstAngle::TAU, 2.0 * PI)] - #[case(ConstAngle::PI_2, PI / 2.0)] - #[case(ConstAngle::PI_4, PI / 4.0)] - fn exec_atorad( + #[case(ConstRotation::PI, 1.0)] + #[case(ConstRotation::TAU, 0.0)] + #[case(ConstRotation::PI_2, 0.5)] + #[case(ConstRotation::PI_4, 0.25)] + fn exec_to_halfturns( mut exec_ctx: TestContext, - #[case] angle: ConstAngle, - #[case] expected_rads: f64, + #[case] angle: ConstRotation, + #[case] expected_halfturns: f64, ) { let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) .with_outs(FLOAT64_TYPE) .finish(|mut builder| { - let angle = builder.add_load_value(angle); - let rads = builder.add_atorad(angle).unwrap(); - builder.finish_with_outputs([rads]).unwrap() + let rot = builder.add_load_value(angle); + let halfturns = builder.add_to_halfturns(rot).unwrap(); + builder.finish_with_outputs([halfturns]).unwrap() }); exec_ctx.add_extensions(|cge| { - cge.add_angle_extensions() + cge.add_rotation_extensions() .add_default_prelude_extensions() .add_float_extensions() }); - let rads = exec_ctx.exec_hugr_f64(hugr, "main"); - let epsilon = 0.0000000000001; // chosen without too much thought - assert!(f64::abs(expected_rads - rads) < epsilon); + let halfturns = exec_ctx.exec_hugr_f64(hugr, "main"); + let epsilon = 0.000000000001; // chosen without too much thought + assert!( + f64::abs(expected_halfturns - halfturns) < epsilon, + "abs({expected_halfturns} - {halfturns}) >= {epsilon}" + ); } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -697,48 +377,71 @@ mod test { } #[rstest] - #[case(PI, 1<<63)] - #[case(-PI, 1<<63)] - #[case(PI / 2.0, 1 << 62)] - #[case(-PI / 2.0, 3 << 62)] - #[case(PI / 4.0, 1 << 61)] - #[case(-PI / 4.0, 7 << 61)] - #[case(13.0 * PI, 1 << 63)] - #[case(-13.0 * PI, 1 << 63)] - #[case(f64::NAN, 0)] - #[case(f64::INFINITY, 0)] - #[case(f64::NEG_INFINITY, 0)] - fn exec_afromrad( + #[case(1.0, Some(1.0))] + #[case(-1.0, Some(1.0))] + #[case(0.5, Some(0.5))] + #[case(-0.5, Some(1.5))] + #[case(0.25, Some(0.25))] + #[case(-0.25, Some(1.75))] + #[case(13.5, Some(1.5))] + #[case(-13.5, Some(0.5))] + #[case(f64::NAN, None)] + #[case(f64::INFINITY, None)] + #[case(f64::NEG_INFINITY, None)] + fn exec_from_halfturns( mut exec_ctx: TestContext, - #[case] rads: f64, - #[case] expected_aparts_value: u64, + #[case] halfturns: f64, + #[case] expected_halfturns: Option, ) { + use hugr::{ops::Value, type_row}; + let hugr = SimpleHugrConfig::new() .with_extensions(tket2::extension::REGISTRY.to_owned()) - .with_outs(USIZE_T) + .with_outs(FLOAT64_TYPE) .finish(|mut builder| { - let konst: Value = if rads.is_finite() { - ConstF64::new(rads).into() + let konst: Value = if halfturns.is_finite() { + ConstF64::new(halfturns).into() } else { - NonFiniteConst64(rads).into() + NonFiniteConst64(halfturns).into() }; - let rads = builder.add_load_value(konst); - let us4 = builder.add_load_value(ConstUsize::new(4)); - let angle = builder.add_afromrad(us4, rads).unwrap(); - let [value, _log_denom] = builder.add_aparts(angle).unwrap(); - builder.finish_with_outputs([value]).unwrap() + let halfturns = { + let halfturns = builder.add_load_value(konst); + let mb_rot = builder.add_from_halfturns(halfturns).unwrap(); + let mut conditional = builder + .conditional_builder( + ([type_row![], type_row![ROTATION_TYPE]], mb_rot), + [], + type_row![FLOAT64_TYPE], + ) + .unwrap(); + { + let mut failure_case = conditional.case_builder(0).unwrap(); + let neg_one = failure_case.add_load_value(ConstF64::new(-1.0)); + failure_case.finish_with_outputs([neg_one]).unwrap(); + } + { + let mut success_case = conditional.case_builder(1).unwrap(); + let [rotation] = success_case.input_wires_arr(); + let halfturns = success_case.add_to_halfturns(rotation).unwrap(); + success_case.finish_with_outputs([halfturns]).unwrap(); + } + conditional.finish_sub_container().unwrap().out_wire(0) + }; + builder.finish_with_outputs([halfturns]).unwrap() }); exec_ctx.add_extensions(|cge| { - cge.add_angle_extensions() + cge.add_rotation_extensions() .add_default_prelude_extensions() .add_float_extensions() .add_cge(NonFiniteConst64CodegenExtension) }); - let r = exec_ctx.exec_hugr_u64(hugr, "main"); + let r = exec_ctx.exec_hugr_f64(hugr, "main"); // chosen without too much thought, except that a f64 has 53 bits of // precision so 1 << 11 is the lowest reasonable value. - let epsilon = 1 << 15; - assert!((expected_aparts_value.wrapping_sub(r) as i64).abs() < epsilon); + let epsilon = 0.0000000000001; // chosen without too much thought + + let expected_halfturns = expected_halfturns.unwrap_or(-1.0); + assert!((expected_halfturns - r).abs() < epsilon); } } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap deleted file mode 100644 index 4c16ccd..0000000 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@llvm14.snap +++ /dev/null @@ -1,58 +0,0 @@ ---- -source: src/custom/angle.rs -expression: mod_str ---- -; ModuleID = 'test_context' -source_filename = "test_context" - -@0 = private unnamed_addr constant [14 x i8] c"Invalid angle\00", align 1 - -define { i32, {}, {} } @_hl.main.1(i64 %0, i64 %1) { -alloca_block: - br label %entry_block - -entry_block: ; preds = %alloca_block - %2 = uitofp i64 %0 to double - %3 = call double @llvm.exp2.f64(double -6.300000e+01) - %4 = fmul double %2, 0x400921FB54442D18 - %5 = fmul double %4, %3 - %6 = fdiv double %5, 0x401921FB54442D18 - %7 = call double @llvm.floor.f64(double %6) - %8 = fsub double %6, %7 - %9 = fcmp oeq double %5, 0x7FF0000000000000 - %10 = fcmp oeq double %5, 0xFFF0000000000000 - %11 = fcmp uno double %5, 0.000000e+00 - %12 = or i1 %9, %10 - %13 = or i1 %12, %11 - %14 = xor i1 %13, true - %15 = select i1 %14, double %8, double 0.000000e+00 - %16 = call double @llvm.exp2.f64(double 6.400000e+01) - %17 = fmul double %15, %16 - %18 = fadd double %17, 5.000000e-01 - %19 = fptoui double %18 to i64 - %20 = mul i64 %19, %1 - %21 = add i64 %20, %20 - %22 = sub i64 %21, %21 - %23 = sub i64 0, %22 - %24 = icmp eq i64 %23, %23 - %25 = select i1 %24, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } - %26 = shl i64 1, 64 - %27 = icmp ule i64 64, 53 - %28 = icmp ult i64 %22, %26 - %29 = freeze i1 %28 - %30 = and i1 %27, %29 - %31 = sub i64 64, 64 - %32 = shl i64 %22, %31 - %33 = insertvalue { i64 } undef, i64 %32, 0 - %34 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %33, 2 - %35 = select i1 %30, { i32, { { i32, i8* } }, { i64 } } %34, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } - ret { i32, {}, {} } %25 -} - -; Function Attrs: nofree nosync nounwind readnone speculatable willreturn -declare double @llvm.exp2.f64(double) #0 - -; Function Attrs: nofree nosync nounwind readnone speculatable willreturn -declare double @llvm.floor.f64(double) #0 - -attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } diff --git a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap deleted file mode 100644 index b437ed9..0000000 --- a/src/custom/snapshots/hugr_llvm__custom__angle__test__emit_all_ops@pre-mem2reg@llvm14.snap +++ /dev/null @@ -1,105 +0,0 @@ ---- -source: src/custom/angle.rs -expression: mod_str ---- -; ModuleID = 'test_context' -source_filename = "test_context" - -@0 = private unnamed_addr constant [14 x i8] c"Invalid angle\00", align 1 - -define { i32, {}, {} } @_hl.main.1(i64 %0, i64 %1) { -alloca_block: - %"0" = alloca { i32, {}, {} }, align 8 - %"2_0" = alloca i64, align 8 - %"2_1" = alloca i64, align 8 - %"4_0" = alloca double, align 8 - %"5_0" = alloca i64, align 8 - %"6_0" = alloca i64, align 8 - %"8_0" = alloca i64, align 8 - %"10_0" = alloca i64, align 8 - %"14_0" = alloca i64, align 8 - %"12_0" = alloca i64, align 8 - %"12_1" = alloca i64, align 8 - %"16_0" = alloca i64, align 8 - %"18_0" = alloca { i32, {}, {} }, align 8 - %"13_0" = alloca { i32, { { i32, i8* } }, { i64 } }, align 8 - br label %entry_block - -entry_block: ; preds = %alloca_block - store i64 %0, i64* %"2_0", align 4 - store i64 %1, i64* %"2_1", align 4 - %"2_01" = load i64, i64* %"2_0", align 4 - %2 = uitofp i64 %"2_01" to double - %3 = call double @llvm.exp2.f64(double -6.300000e+01) - %4 = fmul double %2, 0x400921FB54442D18 - %5 = fmul double %4, %3 - store double %5, double* %"4_0", align 8 - %"2_12" = load i64, i64* %"2_1", align 4 - %"4_03" = load double, double* %"4_0", align 8 - %6 = fdiv double %"4_03", 0x401921FB54442D18 - %7 = call double @llvm.floor.f64(double %6) - %8 = fsub double %6, %7 - %9 = fcmp oeq double %"4_03", 0x7FF0000000000000 - %10 = fcmp oeq double %"4_03", 0xFFF0000000000000 - %11 = fcmp uno double %"4_03", 0.000000e+00 - %12 = or i1 %9, %10 - %13 = or i1 %12, %11 - %14 = xor i1 %13, true - %15 = select i1 %14, double %8, double 0.000000e+00 - %16 = call double @llvm.exp2.f64(double 6.400000e+01) - %17 = fmul double %15, %16 - %18 = fadd double %17, 5.000000e-01 - %19 = fptoui double %18 to i64 - store i64 %19, i64* %"5_0", align 4 - %"5_04" = load i64, i64* %"5_0", align 4 - %"2_15" = load i64, i64* %"2_1", align 4 - %20 = mul i64 %"5_04", %"2_15" - store i64 %20, i64* %"6_0", align 4 - %"6_06" = load i64, i64* %"6_0", align 4 - %"6_07" = load i64, i64* %"6_0", align 4 - %21 = add i64 %"6_06", %"6_07" - store i64 %21, i64* %"8_0", align 4 - %"8_08" = load i64, i64* %"8_0", align 4 - %"8_09" = load i64, i64* %"8_0", align 4 - %22 = sub i64 %"8_08", %"8_09" - store i64 %22, i64* %"10_0", align 4 - %"10_010" = load i64, i64* %"10_0", align 4 - %23 = sub i64 0, %"10_010" - store i64 %23, i64* %"14_0", align 4 - %"10_011" = load i64, i64* %"10_0", align 4 - store i64 %"10_011", i64* %"12_0", align 4 - store i64 64, i64* %"12_1", align 4 - %"14_012" = load i64, i64* %"14_0", align 4 - %"12_113" = load i64, i64* %"12_1", align 4 - store i64 %"14_012", i64* %"16_0", align 4 - %"16_014" = load i64, i64* %"16_0", align 4 - %"16_015" = load i64, i64* %"16_0", align 4 - %24 = icmp eq i64 %"16_014", %"16_015" - %25 = select i1 %24, { i32, {}, {} } { i32 1, {} poison, {} undef }, { i32, {}, {} } { i32 0, {} undef, {} poison } - store { i32, {}, {} } %25, { i32, {}, {} }* %"18_0", align 4 - %"18_016" = load { i32, {}, {} }, { i32, {}, {} }* %"18_0", align 4 - store { i32, {}, {} } %"18_016", { i32, {}, {} }* %"0", align 4 - %"12_017" = load i64, i64* %"12_0", align 4 - %"12_118" = load i64, i64* %"12_1", align 4 - %26 = shl i64 1, %"12_118" - %27 = icmp ule i64 %"12_118", 53 - %28 = icmp ult i64 %"12_017", %26 - %29 = freeze i1 %28 - %30 = and i1 %27, %29 - %31 = sub i64 64, %"12_118" - %32 = shl i64 %"12_017", %31 - %33 = insertvalue { i64 } undef, i64 %32, 0 - %34 = insertvalue { i32, { { i32, i8* } }, { i64 } } { i32 1, { { i32, i8* } } poison, { i64 } poison }, { i64 } %33, 2 - %35 = select i1 %30, { i32, { { i32, i8* } }, { i64 } } %34, { i32, { { i32, i8* } }, { i64 } } { i32 0, { { i32, i8* } } { { i32, i8* } { i32 3, i8* getelementptr inbounds ([14 x i8], [14 x i8]* @0, i32 0, i32 0) } }, { i64 } poison } - store { i32, { { i32, i8* } }, { i64 } } %35, { i32, { { i32, i8* } }, { i64 } }* %"13_0", align 8 - %"019" = load { i32, {}, {} }, { i32, {}, {} }* %"0", align 4 - ret { i32, {}, {} } %"019" -} - -; Function Attrs: nofree nosync nounwind readnone speculatable willreturn -declare double @llvm.exp2.f64(double) #0 - -; Function Attrs: nofree nosync nounwind readnone speculatable willreturn -declare double @llvm.floor.f64(double) #0 - -attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } diff --git a/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@llvm14.snap new file mode 100644 index 0000000..e214fef --- /dev/null +++ b/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@llvm14.snap @@ -0,0 +1,66 @@ +--- +source: src/custom/rotation.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +@0 = private unnamed_addr constant [37 x i8] c"Expected variant 1 but got variant 0\00", align 1 +@prelude.panic_template = private unnamed_addr constant [34 x i8] c"Program panicked (signal %i): %s\0A\00", align 1 + +define void @_hl.main.1(double %0) { +alloca_block: + br label %entry_block + +entry_block: ; preds = %alloca_block + %1 = fdiv double %0, 2.000000e+00 + %2 = call double @llvm.floor.f64(double %1) + %3 = fsub double %1, %2 + %4 = fmul double %3, 2.000000e+00 + %5 = fcmp oeq double %4, 0x7FF0000000000000 + %6 = fcmp oeq double %4, 0xFFF0000000000000 + %7 = fcmp uno double %4, 0.000000e+00 + %8 = or i1 %5, %6 + %9 = or i1 %8, %7 + %10 = xor i1 %9, true + %11 = insertvalue { double } undef, double %4, 0 + %12 = insertvalue { i32, {}, { double } } { i32 1, {} poison, { double } poison }, { double } %11, 2 + %13 = select i1 %10, { i32, {}, { double } } %12, { i32, {}, { double } } { i32 0, {} undef, { double } poison } + %14 = extractvalue { i32, {}, { double } } %13, 0 + switch i32 %14, label %15 [ + i32 1, label %17 + ] + +15: ; preds = %entry_block + %16 = extractvalue { i32, {}, { double } } %13, 1 + br label %cond_6_case_0 + +17: ; preds = %entry_block + %18 = extractvalue { i32, {}, { double } } %13, 2 + %19 = extractvalue { double } %18, 0 + br label %cond_6_case_1 + +cond_6_case_0: ; preds = %15 + %20 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 0 + %21 = extractvalue { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, 1 + %22 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %20, i8* %21) + call void @abort() + br label %cond_exit_6 + +cond_6_case_1: ; preds = %17 + br label %cond_exit_6 + +cond_exit_6: ; preds = %cond_6_case_1, %cond_6_case_0 + %"0.0" = phi double [ 0.000000e+00, %cond_6_case_0 ], [ %19, %cond_6_case_1 ] + %23 = fadd double %0, %"0.0" + ret void +} + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.floor.f64(double) #0 + +declare i32 @printf(i8*, ...) + +declare void @abort() + +attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } diff --git a/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@pre-mem2reg@llvm14.snap b/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@pre-mem2reg@llvm14.snap new file mode 100644 index 0000000..6e2787d --- /dev/null +++ b/src/custom/snapshots/hugr_llvm__custom__rotation__test__emit_all_ops@pre-mem2reg@llvm14.snap @@ -0,0 +1,96 @@ +--- +source: src/custom/rotation.rs +expression: mod_str +--- +; ModuleID = 'test_context' +source_filename = "test_context" + +@0 = private unnamed_addr constant [37 x i8] c"Expected variant 1 but got variant 0\00", align 1 +@prelude.panic_template = private unnamed_addr constant [34 x i8] c"Program panicked (signal %i): %s\0A\00", align 1 + +define void @_hl.main.1(double %0) { +alloca_block: + %"2_0" = alloca double, align 8 + %"4_0" = alloca double, align 8 + %"5_0" = alloca { i32, {}, { double } }, align 8 + %"6_0" = alloca double, align 8 + %"0" = alloca double, align 8 + %"11_0" = alloca { i32, i8* }, align 8 + %"12_0" = alloca double, align 8 + %"07" = alloca double, align 8 + %"14_0" = alloca double, align 8 + %"16_0" = alloca double, align 8 + br label %entry_block + +entry_block: ; preds = %alloca_block + store double %0, double* %"2_0", align 8 + %"2_01" = load double, double* %"2_0", align 8 + %1 = fdiv double %"2_01", 2.000000e+00 + %2 = call double @llvm.floor.f64(double %1) + %3 = fsub double %1, %2 + %4 = fmul double %3, 2.000000e+00 + store double %4, double* %"4_0", align 8 + %"4_02" = load double, double* %"4_0", align 8 + %5 = fcmp oeq double %"4_02", 0x7FF0000000000000 + %6 = fcmp oeq double %"4_02", 0xFFF0000000000000 + %7 = fcmp uno double %"4_02", 0.000000e+00 + %8 = or i1 %5, %6 + %9 = or i1 %8, %7 + %10 = xor i1 %9, true + %11 = insertvalue { double } undef, double %"4_02", 0 + %12 = insertvalue { i32, {}, { double } } { i32 1, {} poison, { double } poison }, { double } %11, 2 + %13 = select i1 %10, { i32, {}, { double } } %12, { i32, {}, { double } } { i32 0, {} undef, { double } poison } + store { i32, {}, { double } } %13, { i32, {}, { double } }* %"5_0", align 8 + %"5_03" = load { i32, {}, { double } }, { i32, {}, { double } }* %"5_0", align 8 + %14 = extractvalue { i32, {}, { double } } %"5_03", 0 + switch i32 %14, label %15 [ + i32 1, label %17 + ] + +15: ; preds = %entry_block + %16 = extractvalue { i32, {}, { double } } %"5_03", 1 + br label %cond_6_case_0 + +17: ; preds = %entry_block + %18 = extractvalue { i32, {}, { double } } %"5_03", 2 + %19 = extractvalue { double } %18, 0 + store double %19, double* %"07", align 8 + br label %cond_6_case_1 + +cond_6_case_0: ; preds = %15 + store { i32, i8* } { i32 1, i8* getelementptr inbounds ([37 x i8], [37 x i8]* @0, i32 0, i32 0) }, { i32, i8* }* %"11_0", align 8 + %"11_05" = load { i32, i8* }, { i32, i8* }* %"11_0", align 8 + %20 = extractvalue { i32, i8* } %"11_05", 0 + %21 = extractvalue { i32, i8* } %"11_05", 1 + %22 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([34 x i8], [34 x i8]* @prelude.panic_template, i32 0, i32 0), i32 %20, i8* %21) + call void @abort() + store double 0.000000e+00, double* %"12_0", align 8 + %"12_06" = load double, double* %"12_0", align 8 + store double %"12_06", double* %"0", align 8 + br label %cond_exit_6 + +cond_6_case_1: ; preds = %17 + %"08" = load double, double* %"07", align 8 + store double %"08", double* %"14_0", align 8 + %"14_09" = load double, double* %"14_0", align 8 + store double %"14_09", double* %"0", align 8 + br label %cond_exit_6 + +cond_exit_6: ; preds = %cond_6_case_1, %cond_6_case_0 + %"04" = load double, double* %"0", align 8 + store double %"04", double* %"6_0", align 8 + %"2_010" = load double, double* %"2_0", align 8 + %"6_011" = load double, double* %"6_0", align 8 + %23 = fadd double %"2_010", %"6_011" + store double %23, double* %"16_0", align 8 + ret void +} + +; Function Attrs: nofree nosync nounwind readnone speculatable willreturn +declare double @llvm.floor.f64(double) #0 + +declare i32 @printf(i8*, ...) + +declare void @abort() + +attributes #0 = { nofree nosync nounwind readnone speculatable willreturn } From ba7c09d6457808d12fa3b90a8778a870498b99c9 Mon Sep 17 00:00:00 2001 From: Douglas Wilson <141026920+doug-q@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:46:26 +0100 Subject: [PATCH 15/16] Update src/custom/rotation.rs Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> --- src/custom/rotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/rotation.rs b/src/custom/rotation.rs index 5af04f7..ce0b6a6 100644 --- a/src/custom/rotation.rs +++ b/src/custom/rotation.rs @@ -165,7 +165,7 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for RotationOpEmitter<'c, '_, H let [half_turns] = args .inputs .try_into() - .map_err(|_| anyhow!("AngleOp::atorad expects one argument"))?; + .map_err(|_| anyhow!("RotationOp::tohalfturns expects one argument"))?; let half_turns = half_turns.into_float_value(); // normalised_half_turns is in the interval 0..2 From bcdf0bacd2dfd2f4d3da4c24cea1821c75ab0392 Mon Sep 17 00:00:00 2001 From: Douglas Wilson Date: Mon, 16 Sep 2024 15:47:34 +0100 Subject: [PATCH 16/16] address review --- src/custom/rotation.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/custom/rotation.rs b/src/custom/rotation.rs index ce0b6a6..3c0c67d 100644 --- a/src/custom/rotation.rs +++ b/src/custom/rotation.rs @@ -95,8 +95,6 @@ impl<'c, H: HugrView> EmitOp<'c, ExtensionOp, H> for RotationOpEmitter<'c, '_, H args.outputs.finish(builder, [r.into()]) } RotationOp::from_halfturns => { - // As we always normalise angles to have a log_denom of - // angle_width, we do not need the log_denom. let [half_turns] = args .inputs .try_into()