Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for const pointers to integer types #56

Merged
merged 3 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ provider my_provider {
```

This script defines a single provider, `test`, with two probes, `start` and `stop`,
with a different set of arguments. (Numeric primitive types and `&str`s are currently
supported.)
with a different set of arguments. (Integral primitive types, pointers to
integral types, and `&str`s are currently supported. Note that `char*` is used
to indicate Rust-style UTF-8 strings. If you'd like a byte array, use `uint8_t*`
or `int8_t*`.)

This provider definition must be converted into Rust code, which can be done in a simple
build script:
Expand Down
9 changes: 6 additions & 3 deletions dtrace-parser/src/dtrace.pest
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// A definition of the basic grammar of DTrace provider definitions.
// Copyright 2021 Oxide Computer Company
// Copyright 2022 Oxide Computer Company

// Some basic tokens
PROBE_KEY = @{ "probe" }
Expand All @@ -17,8 +17,11 @@ IDENTIFIER = @{ ASCII_ALPHA+ ~ (ASCII_ALPHANUMERIC | "_")* }
BIT_WIDTH = @{ "8" | "16" | "32" | "64" }
SIGNED_INT = ${ "int" ~ BIT_WIDTH ~ "_t" }
UNSIGNED_INT = ${ "uint" ~ BIT_WIDTH ~ "_t" }
STRING = { "char" ~ "*" }
DATA_TYPE = { STRING | UNSIGNED_INT | SIGNED_INT}
INTEGER = ${ (SIGNED_INT | UNSIGNED_INT) }
bnaecker marked this conversation as resolved.
Show resolved Hide resolved
STAR = ${ "*" }
INTEGER_POINTER = ${ INTEGER ~ STAR }
STRING = { "char" ~ STAR }
DATA_TYPE = { INTEGER_POINTER | INTEGER | STRING }

// A list of probe arguments, which are just data types
ARGUMENT_LIST = { ( DATA_TYPE ~ ("," ~ DATA_TYPE)* )* }
Expand Down
238 changes: 174 additions & 64 deletions dtrace-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

use std::collections::HashSet;
use std::convert::TryFrom;
use std::fmt;
use std::fs;
use std::path::Path;

Expand Down Expand Up @@ -60,20 +61,101 @@ fn expect_token(pair: &Pair<'_, Rule>, rule: Rule) -> Result<(), DTraceError> {
}
}

/// The bit-width of an integer data type
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum BitWidth {
Bit8,
Bit16,
Bit32,
Bit64,
}

impl fmt::Display for BitWidth {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let w = match self {
BitWidth::Bit8 => "8",
BitWidth::Bit16 => "16",
BitWidth::Bit32 => "32",
BitWidth::Bit64 => "64",
};
write!(f, "{}", w)
}
}

/// The signed-ness of an integer data type
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Sign {
Signed,
Unsigned,
}

/// An integer data type
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Integer {
pub sign: Sign,
pub width: BitWidth,
}

const RUST_TYPE_PREFIX: &str = "::std::os::raw::c_";

impl Integer {
pub fn to_c_type(&self) -> String {
let prefix = match self.sign {
Sign::Unsigned => "u",
_ => "",
};
format!("{prefix}int{}_t", self.width)
}

pub fn to_rust_ffi_type(&self) -> String {
let ty = match (self.sign, self.width) {
(Sign::Unsigned, BitWidth::Bit8) => "uchar",
(Sign::Unsigned, BitWidth::Bit16) => "ushort",
(Sign::Unsigned, BitWidth::Bit32) => "uint",
(Sign::Unsigned, BitWidth::Bit64) => "ulonglong",
(Sign::Signed, BitWidth::Bit8) => "schar",
(Sign::Signed, BitWidth::Bit16) => "short",
(Sign::Signed, BitWidth::Bit32) => "int",
(Sign::Signed, BitWidth::Bit64) => "longlong",
};
format!("{RUST_TYPE_PREFIX}{ty}")
}

pub fn to_rust_type(&self) -> String {
let prefix = match self.sign {
Sign::Signed => "i",
Sign::Unsigned => "u",
};
format!("{prefix}{}", self.width)
}
}

/// Represents the data type of a single probe argument.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DataType {
U8,
U16,
U32,
U64,
I8,
I16,
I32,
I64,
Integer(Integer),
Pointer(Integer),
String,
}

impl From<Pair<'_, Rule>> for Integer {
fn from(integer_type: Pair<'_, Rule>) -> Integer {
let sign = match integer_type.as_rule() {
Rule::SIGNED_INT => Sign::Signed,
Rule::UNSIGNED_INT => Sign::Unsigned,
_ => unreachable!("Expected a signed or unsigned integer"),
};
let width = match integer_type.into_inner().as_str() {
"8" => BitWidth::Bit8,
"16" => BitWidth::Bit16,
"32" => BitWidth::Bit32,
"64" => BitWidth::Bit64,
_ => unreachable!("Expected a bit width"),
};
Integer { sign, width }
}
}

impl TryFrom<&Pair<'_, Rule>> for DataType {
type Error = DTraceError;

Expand All @@ -85,20 +167,42 @@ impl TryFrom<&Pair<'_, Rule>> for DataType {
.next()
.expect("Data type token is expected to contain a concrete type");
let typ = match inner.as_rule() {
Rule::UNSIGNED_INT => match inner.into_inner().as_str() {
"8" => DataType::U8,
"16" => DataType::U16,
"32" => DataType::U32,
"64" => DataType::U64,
_ => unreachable!(),
},
Rule::SIGNED_INT => match inner.into_inner().as_str() {
"8" => DataType::I8,
"16" => DataType::I16,
"32" => DataType::I32,
"64" => DataType::I64,
_ => unreachable!(),
},
Rule::INTEGER => {
let integer = pair
.clone()
.into_inner()
.next()
.expect("Expected a signed or unsigned integral type");
assert!(matches!(integer.as_rule(), Rule::INTEGER));
DataType::Integer(Integer::from(
integer
.clone()
.into_inner()
.next()
.expect("Expected an integral type"),
))
}
Rule::INTEGER_POINTER => {
let pointer = pair
.clone()
.into_inner()
.next()
.expect("Expected a pointer to a signed or unsigned integral type");
assert!(matches!(pointer.as_rule(), Rule::INTEGER_POINTER));
let mut parts = pointer.clone().into_inner();
let integer = parts
.next()
.expect("Expected a signed or unsigned integral type");
let star = parts.next().expect("Expected a literal `*`");
assert_eq!(star.as_rule(), Rule::STAR);
DataType::Pointer(Integer::from(
integer
.clone()
.into_inner()
.next()
.expect("Expected an integral type"),
))
}
Rule::STRING => DataType::String,
_ => unreachable!("Parsed an unexpected DATA_TYPE token"),
};
Expand All @@ -118,49 +222,28 @@ impl DataType {
/// Convert a type into its C type represenation as a string
pub fn to_c_type(&self) -> String {
match self {
DataType::U8 => "uint8_t",
DataType::U16 => "uint16_t",
DataType::U32 => "uint32_t",
DataType::U64 => "uint64_t",
DataType::I8 => "int8_t",
DataType::I16 => "int16_t",
DataType::I32 => "int32_t",
DataType::I64 => "int64_t",
DataType::String => "char*",
DataType::Integer(int) => int.to_c_type(),
DataType::Pointer(int) => format!("{}*", int.to_c_type()),
DataType::String => String::from("char*"),
}
.into()
}

/// Return the Rust FFI type representation of this data type
pub fn to_rust_ffi_type(&self) -> String {
match self {
DataType::U8 => "::std::os::raw::c_uchar",
DataType::U16 => "::std::os::raw::c_ushort",
DataType::U32 => "::std::os::raw::c_uint",
DataType::U64 => "::std::os::raw::c_ulonglong",
DataType::I8 => "::std::os::raw::c_schar",
DataType::I16 => "::std::os::raw::c_short",
DataType::I32 => "::std::os::raw::c_int",
DataType::I64 => "::std::os::raw::c_longlong",
DataType::String => "*const ::std::os::raw::c_char",
DataType::Integer(int) => int.to_rust_ffi_type(),
DataType::Pointer(int) => format!("*const {}", int.to_rust_ffi_type()),
DataType::String => format!("*const {RUST_TYPE_PREFIX}char"),
}
.into()
}

/// Return the native Rust type representation of this data type
pub fn to_rust_type(&self) -> String {
match self {
DataType::U8 => "u8",
DataType::U16 => "u16",
DataType::U32 => "u32",
DataType::U64 => "u64",
DataType::I8 => "i8",
DataType::I16 => "i16",
DataType::I32 => "i32",
DataType::I64 => "i64",
DataType::String => "&str",
DataType::Integer(int) => int.to_rust_type(),
DataType::Pointer(int) => format!("*const {}", int.to_rust_type()),
DataType::String => String::from("&str"),
}
.into()
}
}

Expand Down Expand Up @@ -375,7 +458,16 @@ impl TryFrom<&str> for File {

#[cfg(test)]
mod tests {
use super::{DTraceParser, DataType, File, Probe, Provider, Rule, TryFrom};
use super::BitWidth;
use super::DTraceParser;
use super::DataType;
use super::File;
use super::Integer;
use super::Probe;
use super::Provider;
use super::Rule;
use super::Sign;
use super::TryFrom;
use ::pest::Parser;
use rstest::{fixture, rstest};

Expand Down Expand Up @@ -486,14 +578,22 @@ mod tests {
#[rstest(
defn,
data_type,
case("uint8_t", DataType::U8),
case("uint16_t", DataType::U16),
case("uint32_t", DataType::U32),
case("uint64_t", DataType::U64),
case("int8_t", DataType::I8),
case("int16_t", DataType::I16),
case("int32_t", DataType::I32),
case("int64_t", DataType::I64),
case("uint8_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8 })),
case("uint16_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16 })),
case("uint32_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32 })),
case("uint64_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64 })),
case("int8_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit8 })),
case("int16_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit16 })),
case("int32_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit32 })),
case("int64_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit64 })),
case("uint8_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8})),
case("uint16_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16})),
case("uint32_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32})),
case("uint64_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64})),
case("int8_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit8})),
case("int16_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit16})),
case("int32_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit32})),
case("int64_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit64})),
case("char*", DataType::String)
)]
fn test_data_type_enum(defn: &str, data_type: DataType) {
Expand All @@ -512,7 +612,7 @@ mod tests {
#[fixture]
fn probe_data() -> (String, String) {
let provider = String::from("foo");
let probe = String::from("probe baz(char*, uint16_t, uint8_t);");
let probe = String::from("probe baz(char*, uint16_t, uint8_t*);");
(provider, probe)
}

Expand All @@ -532,7 +632,17 @@ mod tests {
assert_eq!(probe.name, "baz");
assert_eq!(
probe.types,
&[DataType::String, DataType::U16, DataType::U8]
&[
DataType::String,
DataType::Integer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit16,
}),
DataType::Pointer(Integer {
sign: Sign::Unsigned,
width: BitWidth::Bit8,
}),
]
);
}

Expand Down
5 changes: 5 additions & 0 deletions probe-test-attr/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ mod test {
/// Some types aren't JSON serializable. These will not break the program, but an error message
/// will be seen in DTrace.
fn not_json_serializable(_: crate::Whoops) {}

/// Constant pointers to integer types are also supported
fn work_with_pointer(_buffer: *const u8, _: u64) {}
}

fn main() {
Expand All @@ -90,6 +93,7 @@ fn main() {
x: 0,
buffer: vec![1; 12],
};
let buffer = vec![2; 4];
loop {
test::start_work!(|| arg.x);
std::thread::sleep(std::time::Duration::from_secs(1));
Expand All @@ -104,5 +108,6 @@ fn main() {
});
test::arg_as_tuple!(|| (arg.x, &arg.buffer[..]));
test::not_json_serializable!(|| Whoops::NoBueno(0));
test::work_with_pointer!(|| (buffer.as_ptr(), buffer.len() as u64));
}
}
Loading