rust: add support for Intel PT regions (#140)

* rust: add `__itt*` functions in generated bindings

This change adds some bindgen-generated functions, and not just
variables, to the generated bindings. This should resolve #139 by making
the `pt_region_*` functions at least visibile in the `ittapi-sys` crate,
making it possible to add higher-level Rust versions.

We cannot generate bindings for all declared functions because these
cause link errors: they are only present in the dynamic library (`*.so`)
but not in the static library (`libittnotify.a`) that the `ittapi-sys`
crate links to. So, for these dynamically-provided functions we must
just retain the `*_ptr__3_0` data symbols and wait for them to be
resolved to functions pointers at runtime.

* rust: remove unused code

* rust: document static/dynamic linking in `README.md`

It is easy to forget how the `ittapi` system of static + dynamic linking
works (and why!). To answer the underlying question of issues like #139,
this change adds some `README.md` documentation explaining why we can't
just include all function symbols in the generated bindings.

* rust: add `Region` for marking Intel PT regions

In #139, @codecnotsupported pointed out a need for access to
`__itt_mark_pt_region_begin` and `__itt_mark_pt_region_end` for
fine-grained VTune analysis using Intel PT. This change adds a
high-level `Region` structure for easy access to these now-available
functions. Closes #139.
diff --git a/rust/ittapi-sys/Cargo.toml b/rust/ittapi-sys/Cargo.toml
index 1df3944..520f63e 100644
--- a/rust/ittapi-sys/Cargo.toml
+++ b/rust/ittapi-sys/Cargo.toml
@@ -28,5 +28,5 @@
 cc = "1.0.73"
 
 [dev-dependencies]
-bindgen = "0.68"
+bindgen = "0.69.4"
 diff = "0.1"
diff --git a/rust/ittapi-sys/README.md b/rust/ittapi-sys/README.md
index 3e5f44e..15dccf1 100644
--- a/rust/ittapi-sys/README.md
+++ b/rust/ittapi-sys/README.md
@@ -26,9 +26,16 @@
 
 ```toml
 [dependencies]
-ittapi-sys = "0.3"
+ittapi-sys = "*"
 ```
 
+Using the symbols in this crate can be tricky: `ittapi` consists of a static part (e.g.,
+`libittnotify.a`) linked in this crate _and_ a dynamic part (e.g., `libittnotify_collector.so`,
+VTune Profiler). The static part provides the ITT data symbols that may be subsequently resolved to
+actual implementations in the dynamic part--the data collector. This crate only provides symbols
+like `__itt_task_begin_ptr__3_0`, not `__itt_task_begin`, to avoid link errors; programs using
+`ittapi` should compile even if the dynamic part is not present. Using the [high-level Rust crate]
+avoids this complexity.
 
 ### Build
 
diff --git a/rust/ittapi-sys/src/freebsd/ittnotify_bindings.rs b/rust/ittapi-sys/src/freebsd/ittnotify_bindings.rs
index 1ff21d3..0ce95b3 100644
--- a/rust/ittapi-sys/src/freebsd/ittnotify_bindings.rs
+++ b/rust/ittapi-sys/src/freebsd/ittnotify_bindings.rs
@@ -69,6 +69,14 @@
 extern "C" {
     pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
 }
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
+}
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
+}
 pub type __itt_thread_set_name_ptr__3_0_t =
     ::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
 extern "C" {
diff --git a/rust/ittapi-sys/src/lib.rs b/rust/ittapi-sys/src/lib.rs
index ac7b2fd..24a9006 100644
--- a/rust/ittapi-sys/src/lib.rs
+++ b/rust/ittapi-sys/src/lib.rs
@@ -1,4 +1,5 @@
 //! This library contains OS-specific bindings to the C `ittapi` library.
+
 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
 #![allow(unused)]
 #![deny(clippy::all)]
@@ -32,11 +33,3 @@
 include!("freebsd/jitprofiling_bindings.rs");
 #[cfg(target_os = "openbsd")]
 include!("openbsd/jitprofiling_bindings.rs");
-
-// #[link(name = "ittnotify", kind = "static")]
-// extern "C" {
-//     #[link_name = "__itt_domain_create_init_3_0"]
-//     pub fn __itt_domain_create_init_3_0(name: *const std::os::raw::c_char) -> *mut __itt_domain;
-//     // #[link_name = "__itt_domain_create_init_3_0"]
-//     // pub fn __itt_domain_create(name: *const std::os::raw::c_char) -> *mut __itt_domain;
-// }
diff --git a/rust/ittapi-sys/src/linux/ittnotify_bindings.rs b/rust/ittapi-sys/src/linux/ittnotify_bindings.rs
index e3270a0..cd069fd 100644
--- a/rust/ittapi-sys/src/linux/ittnotify_bindings.rs
+++ b/rust/ittapi-sys/src/linux/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 pub const ITT_OS_WIN: u32 = 1;
 pub const ITT_OS_LINUX: u32 = 2;
@@ -59,6 +59,14 @@
 extern "C" {
     pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
 }
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
+}
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
+}
 pub type __itt_thread_set_name_ptr__3_0_t =
     ::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
 extern "C" {
diff --git a/rust/ittapi-sys/src/linux/jitprofiling_bindings.rs b/rust/ittapi-sys/src/linux/jitprofiling_bindings.rs
index 29927bd..1e9d976 100644
--- a/rust/ittapi-sys/src/linux/jitprofiling_bindings.rs
+++ b/rust/ittapi-sys/src/linux/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 #[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
 pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
diff --git a/rust/ittapi-sys/src/macos/ittnotify_bindings.rs b/rust/ittapi-sys/src/macos/ittnotify_bindings.rs
index bc065b2..9c1e40c 100644
--- a/rust/ittapi-sys/src/macos/ittnotify_bindings.rs
+++ b/rust/ittapi-sys/src/macos/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 pub const ITT_OS_WIN: u32 = 1;
 pub const ITT_OS_LINUX: u32 = 2;
@@ -59,6 +59,14 @@
 extern "C" {
     pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
 }
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
+}
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
+}
 pub type __itt_thread_set_name_ptr__3_0_t =
     ::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
 extern "C" {
diff --git a/rust/ittapi-sys/src/macos/jitprofiling_bindings.rs b/rust/ittapi-sys/src/macos/jitprofiling_bindings.rs
index 29927bd..1e9d976 100644
--- a/rust/ittapi-sys/src/macos/jitprofiling_bindings.rs
+++ b/rust/ittapi-sys/src/macos/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 #[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
 pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
diff --git a/rust/ittapi-sys/src/openbsd/ittnotify_bindings.rs b/rust/ittapi-sys/src/openbsd/ittnotify_bindings.rs
index 2a0ad9f..82e3cc5 100644
--- a/rust/ittapi-sys/src/openbsd/ittnotify_bindings.rs
+++ b/rust/ittapi-sys/src/openbsd/ittnotify_bindings.rs
@@ -52,6 +52,14 @@
 extern "C" {
     pub static mut __itt_pt_region_create_ptr__3_0: __itt_pt_region_create_ptr__3_0_t;
 }
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
+}
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
+}
 pub type __itt_thread_set_name_ptr__3_0_t =
     ::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
 extern "C" {
diff --git a/rust/ittapi-sys/src/windows/ittnotify_bindings.rs b/rust/ittapi-sys/src/windows/ittnotify_bindings.rs
index be4c19f..39458a7 100644
--- a/rust/ittapi-sys/src/windows/ittnotify_bindings.rs
+++ b/rust/ittapi-sys/src/windows/ittnotify_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 pub const ITT_OS_WIN: u32 = 1;
 pub const ITT_OS_LINUX: u32 = 2;
@@ -65,6 +65,14 @@
 extern "C" {
     pub static mut __itt_pt_region_createW_ptr__3_0: __itt_pt_region_createW_ptr__3_0_t;
 }
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the beginning of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_begin(region: __itt_pt_region);
+}
+extern "C" {
+    #[doc = " @brief function contains a special code pattern identified on the post-processing stage and\n marks the end of a code region targeted for Intel PT analysis\n @param[in] region - region id, 0 <= region < 8"]
+    pub fn __itt_mark_pt_region_end(region: __itt_pt_region);
+}
 pub type __itt_thread_set_nameA_ptr__3_0_t =
     ::std::option::Option<unsafe extern "C" fn(name: *const ::std::os::raw::c_char)>;
 extern "C" {
diff --git a/rust/ittapi-sys/src/windows/jitprofiling_bindings.rs b/rust/ittapi-sys/src/windows/jitprofiling_bindings.rs
index 17ce844..ad67453 100644
--- a/rust/ittapi-sys/src/windows/jitprofiling_bindings.rs
+++ b/rust/ittapi-sys/src/windows/jitprofiling_bindings.rs
@@ -1,4 +1,4 @@
-/* automatically generated by rust-bindgen 0.68.1 */
+/* automatically generated by rust-bindgen 0.69.4 */
 
 #[doc = "<\\brief Send this to shutdown the agent.\n Use NULL for event data."]
 pub const iJIT_jvm_event_iJVM_EVENT_TYPE_SHUTDOWN: iJIT_jvm_event = 2;
diff --git a/rust/ittapi-sys/tests/bindgen-up-to-date.rs b/rust/ittapi-sys/tests/bindgen-up-to-date.rs
index f16680e..7db83f0 100644
--- a/rust/ittapi-sys/tests/bindgen-up-to-date.rs
+++ b/rust/ittapi-sys/tests/bindgen-up-to-date.rs
@@ -32,6 +32,11 @@
         .formatter(bindgen::Formatter::Rustfmt)
         .allowlist_var("ITT.*")
         .allowlist_var("__itt.*")
+        // Also, note how few functions we allow: if we generate bindings for all the declared
+        // functions, we run into linking errors. Only some functions are actually defined in
+        // `libittnotify.a` but most are provided dynamically by the dynamic collection library. See
+        // the `README.md` for more details.
+        .allowlist_function("__itt_mark_pt.*")
         .header(concat(INCLUDE_PATH, "/ittnotify.h"))
         .generate()
         .expect("Unable to generate ittnotify bindings.")
diff --git a/rust/ittapi/src/domain.rs b/rust/ittapi/src/domain.rs
index 5c779a8..f77b08e 100644
--- a/rust/ittapi/src/domain.rs
+++ b/rust/ittapi/src/domain.rs
@@ -43,3 +43,14 @@
 /// [ITT documentation]:

 ///     https://www.intel.com/content/www/us/en/develop/documentation/vtune-help/top/api-support/instrumentation-and-tracing-technology-apis/instrumentation-tracing-technology-api-reference/domain-api.html

 unsafe impl Sync for Domain {}

+

+#[cfg(test)]

+mod tests {

+    use super::*;

+

+    #[test]

+    #[should_panic(expected = "unable to create a CString; does it contain a 0 byte?")]

+    fn zero_byte() {

+        let _domain = Domain::new("zero\0byte\0name");

+    }

+}

diff --git a/rust/ittapi/src/lib.rs b/rust/ittapi/src/lib.rs
index ad3ef9e..32e841e 100644
--- a/rust/ittapi/src/lib.rs
+++ b/rust/ittapi/src/lib.rs
@@ -13,6 +13,7 @@
 mod domain;
 mod event;
 pub mod jit;
+mod region;
 mod string;
 mod task;
 mod util;
@@ -20,5 +21,6 @@
 pub use collection_control::{detach, pause, resume};
 pub use domain::Domain;
 pub use event::Event;
+pub use region::{MarkedRegion, Region};
 pub use string::StringHandle;
 pub use task::Task;
diff --git a/rust/ittapi/src/region.rs b/rust/ittapi/src/region.rs
new file mode 100644
index 0000000..df20f90
--- /dev/null
+++ b/rust/ittapi/src/region.rs
@@ -0,0 +1,94 @@
+use std::ffi::CString;
+
+/// An Intel® Processor Trace region.
+pub struct Region(ittapi_sys::__itt_pt_region);
+impl Region {
+    /// Create a new Intel PT region.
+    ///
+    /// ```
+    /// # use ittapi::Region;
+    /// let region = Region::new("test-region");
+    /// ```
+    ///
+    /// # Panics
+    ///
+    /// Panics if the domain name contains a `0` byte.
+    #[must_use]
+    pub fn new(name: &str) -> Self {
+        let c_string =
+            CString::new(name).expect("unable to create a CString; does it contain a 0 byte?");
+        #[cfg(unix)]
+        let create_fn = unsafe { ittapi_sys::__itt_pt_region_create_ptr__3_0 };
+        #[cfg(windows)]
+        let create_fn = unsafe { ittapi_sys::__itt_pt_region_createA_ptr__3_0 };
+        let region = if let Some(create_fn) = create_fn {
+            unsafe { create_fn(c_string.as_ptr()) }
+        } else {
+            // Use this value as a sentinel to indicate that the region was not created.
+            u8::MAX
+        };
+        Self(region)
+    }
+
+    /// Mark a section of code as an Intel PT region using `__itt_mark_pt_region_begin`. This can be
+    /// used for fine-grained profiling, such as [anomaly detection] (a preview feature of VTune).
+    ///
+    /// [anomaly detection]:
+    ///     https://www.intel.com/content/www/us/en/docs/vtune-profiler/user-guide/2024-1/anomaly-detection-analysis.html
+    ///
+    /// ```
+    /// # use ittapi::Region;
+    /// let region = Region::new("test-region");
+    /// // Mark a region for fine-grained measurement, such as a tight loop.
+    /// for _ in 0..10 {
+    ///     let _marked = region.mark();
+    ///     let _ = 2 + 2;
+    ///     // Marked region ends here, when dropped; use `end()` to end it explicitly.
+    /// }
+    /// ```
+    #[inline]
+    #[must_use]
+    pub fn mark(&self) -> MarkedRegion {
+        unsafe { ittapi_sys::__itt_mark_pt_region_begin(self.0) };
+        MarkedRegion(self)
+    }
+}
+
+/// A [`MarkedRegion`] is a Rust helper structure for ergonomically ending a marked region using
+/// `__itt_mark_pt_region_end`. See [`Region::mark`] for more details.
+pub struct MarkedRegion<'a>(&'a Region);
+impl<'a> MarkedRegion<'a> {
+    /// End the marked region.
+    #[inline]
+    pub fn end(self) {
+        // Do nothing; the `Drop` implementation does the work. See discussion at
+        // https://stackoverflow.com/questions/53254645.
+    }
+}
+impl Drop for MarkedRegion<'_> {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe { ittapi_sys::__itt_mark_pt_region_end(self.0 .0) };
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    #[should_panic(expected = "unable to create a CString; does it contain a 0 byte?")]
+    fn zero_byte() {
+        let _region = Region::new("zero\0byte\0name");
+    }
+
+    #[test]
+    fn sanity() {
+        let region = Region::new("region");
+        for _ in 0..10 {
+            let _marked_region = region.mark();
+            // Do nothing.
+            _marked_region.end();
+        }
+    }
+}