@@ -55,6 +55,14 @@ fn main() {
error!("{x:#?}");
std::process::exit(1);
}
+ Err(spec::Error::BadAbi(x)) => {
+ error!("Broken ABI rule: {x}");
+ std::process::exit(1);
+ }
+ Err(spec::Error::MissingDefinition(x)) => {
+ error!("Missing include: {x}");
+ std::process::exit(1);
+ }
};
let (extension, parser): (&str, fn(&OutFileDef) -> String) = match cli.lang {
@@ -13,7 +13,7 @@
//! to properly decode the type and match it with a variant of the [`Typ`] type
//! with the payload landing in the payload of the variant itself.
-use std::{fs::read_to_string, path::Path};
+use std::{cmp::max, fs::read_to_string, path::Path};
use log::{debug, info};
@@ -25,7 +25,7 @@ use log::{debug, info};
/// TOML files. Ideally, that representation should be more ergonomic and the
/// parser instructed to deal with it.
#[allow(clippy::missing_docs_in_private_items)]
-#[derive(Debug, serde::Deserialize, PartialEq)]
+#[derive(Clone, Debug, serde::Deserialize, PartialEq)]
#[serde(rename_all = "lowercase", tag = "tag", content = "args")]
pub enum Typ {
Bitmap(String),
@@ -43,8 +43,120 @@ pub enum Typ {
Array(Box<Typ>, usize),
}
+impl Typ {
+ /// Returns the size of this type in octets. The specification must be
+ /// passed on the side so the function can look-up subordinate types.
+ ///
+ /// # Errors
+ /// `Err` on type lookup failure (e.g: enum doesn't exist).
+ pub fn size(&self, spec: &Spec) -> Result<usize, Error> {
+ match self {
+ Typ::U8 | Typ::I8 => Ok(1),
+ Typ::U16 | Typ::I16 => Ok(2),
+ Typ::U32 | Typ::I32 => Ok(4),
+ Typ::U64 | Typ::I64 | Typ::Ptr(_) => Ok(8),
+ Typ::Enum(s) => spec.find_enum(s)?.typ.size(spec),
+ Typ::Bitmap(s) => spec.find_bitmap(s)?.typ.size(spec),
+ Typ::Struct(s) => {
+ // The size of a struct is the sum of the sizes of its subfields
+ // as long as it's packed, and that is mandated by the ABI.
+ let mut size = 0;
+ for f in &spec.find_struct(s)?.fields {
+ size += f.typ.size(spec)?;
+ }
+ Ok(size)
+ }
+ Typ::Array(t, n) => Ok(n * t.size(spec)?),
+ }
+ }
+
+ /// Returns the alignment of this type in octets. The specification must
+ /// be passed on the side so the function can look-up subordinate types.
+ ///
+ /// # Errors
+ /// `Err` on type lookup failure (e.g: enum doesn't exist).
+ pub fn alignment(&self, spec: &Spec) -> Result<usize, Error> {
+ match self {
+ Typ::U8 | Typ::I8 => Ok(1),
+ Typ::U16 | Typ::I16 => Ok(2),
+ Typ::U32 | Typ::I32 => Ok(4),
+ Typ::U64 | Typ::I64 | Typ::Ptr(_) => Ok(8),
+ Typ::Enum(s) => spec.find_enum(s)?.typ.alignment(spec),
+ Typ::Bitmap(s) => spec.find_bitmap(s)?.typ.alignment(spec),
+ Typ::Struct(s) => {
+ // The alignment of a struct is as large as its largest field
+ let mut alignment = 1;
+ for f in &spec.find_struct(s)?.fields {
+ alignment = max(alignment, f.typ.alignment(spec)?);
+ }
+ Ok(alignment)
+ }
+ Typ::Array(t, _) => t.alignment(spec),
+ }
+ }
+
+ /// `Ok` iff the type is {i,u}{8,16,32,64}.
+ pub fn is_primitive(&self) -> Result<(), Error> {
+ match self {
+ Typ::U8 | Typ::I8 | Typ::U16 | Typ::I16 | Typ::U32 | Typ::I32 | Typ::U64 | Typ::I64 => {
+ Ok(())
+ }
+ _ => Err(Error::BadAbi(format!("{self:?} while expecting primitive"))),
+ }
+ }
+
+ /// `Ok` iff the type respects all ABI rules. Note that for a spec to
+ /// satisfy the ABI restrictions _all_ types must satisfy them.
+ pub fn abi_compatible(&self, spec: &Spec) -> Result<(), Error> {
+ match self {
+ // Unconditionally ok. They have fixed size with equal alignment
+ Typ::U8
+ | Typ::I8
+ | Typ::U16
+ | Typ::I16
+ | Typ::U32
+ | Typ::I32
+ | Typ::U64
+ | Typ::I64
+ | Typ::Ptr(_) => Ok(()),
+
+ // Ok as long as their backing type is itself primitive
+ Typ::Enum(s) => spec.find_enum(s)?.typ.is_primitive(),
+ Typ::Bitmap(s) => spec.find_bitmap(s)?.typ.is_primitive(),
+
+ // Every field must be compatible and there can be no padding
+ Typ::Struct(s) => {
+ let def = spec.find_struct(s)?;
+ let mut offset = 0;
+
+ for f in &def.fields {
+ f.typ.abi_compatible(spec)?;
+
+ if offset & (f.typ.alignment(spec)? - 1) != 0 {
+ return Err(Error::BadAbi(format!(
+ "implicit padding in struct {s}, before {}",
+ f.name
+ )));
+ }
+
+ offset += f.typ.size(spec)?;
+ }
+
+ if offset & (self.alignment(spec)? - 1) != 0 {
+ return Err(Error::BadAbi(format!(
+ "implicit padding in struct {s} at the tail",
+ )));
+ }
+
+ Ok(())
+ }
+ Typ::Array(t, _) => t.abi_compatible(spec),
+ }
+ }
+}
+
/// Deserialized form of a hypercall struct
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct StructDef {
/// Name of the struct
pub name: String,
@@ -57,7 +169,7 @@ pub struct StructDef {
}
/// Deserialized form of a field within a hypercall struct (see [`StructDef`])
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct FieldDef {
/// Name of the field
pub name: String,
@@ -69,7 +181,7 @@ pub struct FieldDef {
}
/// Description of a lang-agnostic enumerated type.
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct EnumDef {
/// snake-cased name of this enumeration.
///
@@ -90,7 +202,7 @@ pub struct EnumDef {
}
/// Lang-agnostic description of a bitmap type.
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct BitmapDef {
/// Snake-cased name of this bitmap.
///
@@ -109,7 +221,7 @@ pub struct BitmapDef {
}
/// Lang-agnostic description of a single bit within a particular bitmap type.
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct BitDef {
/// Snake-cased name of this bit. Depending on the backend, the name
/// might be prefixed by the name of its type (as is commonly done in C).
@@ -122,7 +234,7 @@ pub struct BitDef {
}
/// Lang-agnostic description of a single variant of an enumerated type.
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct VariantDef {
/// Snake-cased name of this variant. Depending on the backend, the name
/// might be prefixed by the name of its type (as is commonly done in C).
@@ -138,7 +250,7 @@ pub struct VariantDef {
///
/// Used in specifications to state a number of types (described in `imports`)
/// is needed from another generated file (the `from` field).
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
pub struct IncludeDef {
/// Name of the [`InFileDef`] that contains the imported tokens of
/// `imports`.
@@ -148,7 +260,7 @@ pub struct IncludeDef {
}
/// A language-agnostic specification.
-#[derive(Debug, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Deserialize)]
struct InFileDef {
/// List of types described in other [`InFileDef`] that are required here.
includes: Option<Vec<IncludeDef>>,
@@ -163,7 +275,7 @@ struct InFileDef {
/// Description of an abstract output (i.e: `.rs`, `.h`, etc).
///
/// Contains every element of the ABI that needs representing.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub struct OutFileDef {
/// The name of the output file, without the final extension.
pub name: String,
@@ -206,18 +318,18 @@ impl OutFileDef {
let path = from_ioerr(entry)?.path();
debug!("Reading {:?} to generate outfile={}", path, ret.name);
let toml_str = from_ioerr(read_to_string(path))?;
- let filedef: InFileDef = toml::from_str(&toml_str).map_err(Error::Toml)?;
- if let Some(structs) = filedef.structs {
- ret.structs.extend(structs);
+ let infiledef: InFileDef = toml::from_str(&toml_str).map_err(Error::Toml)?;
+ if let Some(x) = infiledef.structs {
+ ret.structs.extend(x);
}
- if let Some(enums) = filedef.enums {
- ret.enums.extend(enums);
+ if let Some(x) = infiledef.enums {
+ ret.enums.extend(x);
}
- if let Some(bitmaps) = filedef.bitmaps {
- ret.bitmaps.extend(bitmaps);
+ if let Some(x) = infiledef.bitmaps {
+ ret.bitmaps.extend(x);
}
- if let Some(includes) = filedef.includes {
- ret.includes.extend(includes);
+ if let Some(x) = infiledef.includes {
+ ret.includes.extend(x);
}
}
@@ -228,10 +340,14 @@ impl OutFileDef {
/// Internal error type for every error spec parsing could encounter
#[derive(Debug)]
pub enum Error {
- /// Wrapper around IO errors
+ /// IO errors (e.g: opening files, etc)
Io(std::io::Error),
- /// Wrapper around deserialization errors
+ /// Deserialization errors (i.e: from malformed specifications)
Toml(toml::de::Error),
+ /// Failed ABI consistency checks
+ BadAbi(String),
+ /// Failed definition lookups (typically typos)
+ MissingDefinition(String),
}
/// Maps an [`std::io::Error`] onto a [`Error`] type for easier propagation
@@ -242,7 +358,7 @@ fn from_ioerr<T>(t: std::io::Result<T>) -> Result<T, Error> {
/// Object containing the abstract definitions of all output files.
///
/// See [`OutFileDef`] to details on the specification contents of each output.
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct Spec(pub Vec<OutFileDef>);
impl Spec {
@@ -255,7 +371,7 @@ impl Spec {
pub fn new(root: &Path) -> Result<Self, Error> {
info!("Reading {root:?} as top-level directory");
- let mut ret = Self(Vec::new());
+ let mut ret: Spec = Self::default();
for outfile in from_ioerr(root.read_dir())? {
// Each folder in the root defines a single output file
let outfile = from_ioerr(outfile)?;
@@ -263,6 +379,199 @@ impl Spec {
ret.0.push(OutFileDef::new(name, &outfile.path())?);
}
+ for arch in &[ret.x86(), ret.arm(), ret.riscv(), ret.ppc()] {
+ arch.is_valid()?;
+ }
+
Ok(ret)
}
+
+ /// Checks names are not duplicated. Some languages might not support type namespaces
+ /// so and it simplifies type lookup in [`Spec::abi_is_compliant`].
+ fn has_unique_names(&self) -> Result<(), Error> {
+ let mut all_names = Vec::<String>::new();
+
+ for outfile in &self.0 {
+ all_names.extend(outfile.structs.iter().map(|e| e.name.clone()));
+ all_names.extend(outfile.enums.iter().map(|e| e.name.clone()));
+ all_names.extend(outfile.bitmaps.iter().map(|e| e.name.clone()));
+ }
+
+ if all_names.is_empty() {
+ return Err(Error::BadAbi("Empty spec".to_string()));
+ }
+
+ let mut dedupped = all_names.clone();
+ dedupped.sort();
+ dedupped.dedup();
+
+ if dedupped.len() != all_names.len() {
+ // There's duplicates. Be nice and point out which
+ let mut dup = all_names.last().unwrap();
+ for (i, name) in dedupped.iter().enumerate() {
+ if all_names[i] != *name {
+ dup = name;
+ break;
+ }
+ }
+
+ return Err(Error::BadAbi(format!("Duplicate identifier: {dup}")));
+ }
+
+ Ok(())
+ }
+
+ /// Enforce restrictions to guarantee ABI sanity
+ ///
+ /// 1. ABI must be identical in 32 and 64 bits.
+ /// 2. For every type, alignment must match size.
+ /// 3. Structs must have no implicit padding, not even at the tail.
+ /// 4. All type names are distinct.
+ fn is_valid(&self) -> Result<(), Error> {
+ info!("Validating specification");
+
+ self.has_unique_names()?;
+ for def in &self.0 {
+ for s in &def.structs {
+ debug!("Validating struct {}", s.name);
+ Typ::Struct(s.name.to_string()).abi_compatible(self)?;
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Duplicates a specification; Leaves out arch-specific definitions.
+ ///
+ /// This has the effect of deduplicating the fields used polymorphically
+ /// across arch-specific code (e.g: See `xen_arch_domainconfig` in the spec)
+ fn common(&self) -> Self {
+ let mut ret = Self::default();
+
+ for def in &self.0 {
+ if !def.name.starts_with("arch-") {
+ ret.0.push(def.clone());
+ }
+ }
+
+ ret
+ }
+
+ /// Duplicates a specification; Leaves out non-x86 architectures.
+ ///
+ /// This has the effect of deduplicating the fields used polymorphically
+ /// across arch-specific code (e.g: See `xen_arch_domainconfig` in the spec)
+ pub fn x86(&self) -> Self {
+ let mut ret = self.common();
+
+ if let Some(x) = self.0.iter().find(|x| x.name == "arch-x86") {
+ let mut def = x.clone();
+ def.name = String::from("arch");
+ ret.0.push(def);
+ }
+
+ ret
+ }
+
+ /// Duplicates a specification; Leaves out non-ARM architectures.
+ ///
+ /// This has the effect of deduplicating the fields used polymorphically
+ /// across arch-specific code (e.g: See `xen_arch_domainconfig` in the spec)
+ pub fn arm(&self) -> Spec {
+ let mut ret = self.common();
+
+ if let Some(x) = self.0.iter().find(|x| x.name == "arch-arm") {
+ let mut def = x.clone();
+ def.name = String::from("arch");
+ ret.0.push(def);
+ }
+
+ ret
+ }
+
+ /// Duplicates a specification; Leaves out non-PowerPC architectures.
+ ///
+ /// This has the effect of deduplicating the fields used polymorphically
+ /// across arch-specific code (e.g: See `xen_arch_domainconfig` in the spec)
+ pub fn ppc(&self) -> Spec {
+ let mut ret = self.common();
+
+ if let Some(x) = self.0.iter().find(|x| x.name == "arch-ppc") {
+ let mut def = x.clone();
+ def.name = String::from("arch");
+ ret.0.push(def);
+ }
+
+ ret
+ }
+
+ /// Duplicates a specification; Leaves out non-RiscV architectures.
+ ///
+ /// This has the effect of deduplicating the fields used polymorphically
+ /// across arch-specific code (e.g: See `xen_arch_domainconfig` in the spec)
+ pub fn riscv(&self) -> Spec {
+ let mut ret = self.common();
+
+ if let Some(x) = self.0.iter().find(|x| x.name == "arch-riscv") {
+ let mut def = x.clone();
+ def.name = String::from("arch");
+ ret.0.push(def);
+ }
+
+ ret
+ }
+
+ /// Find a struct with a particular name within a [`Spec`] definition.
+ ///
+ /// Assumes a flat namespace, so there are no two types with the same
+ /// name. This is ensured with [`Spec::has_unique_names`].
+ pub fn find_struct(&self, name: &str) -> Result<&StructDef, Error> {
+ debug!("Looking up struct {name}");
+
+ for filedef in &self.0 {
+ for s in &filedef.structs {
+ if s.name == name {
+ return Ok(s);
+ }
+ }
+ }
+
+ Err(Error::MissingDefinition(format!("missing struct {name}")))
+ }
+
+ /// Find an enum with a particular name within a [`Spec`] definition.
+ ///
+ /// Assumes a flat namespace, so there are no two types with the same
+ /// name. This is ensured with [`Spec::has_unique_names`].
+ pub fn find_enum(&self, name: &str) -> Result<&EnumDef, Error> {
+ debug!("Looking up enum {name}");
+
+ for filedef in &self.0 {
+ for s in &filedef.enums {
+ if s.name == name {
+ return Ok(s);
+ }
+ }
+ }
+
+ Err(Error::MissingDefinition(format!("missing enum {name}")))
+ }
+
+ /// Find a bitmap with a particular name within a [`Spec`] definition.
+ ///
+ /// Assumes a flat namespace, so there are no two types with the same
+ /// name. This is ensured with [`Spec::has_unique_names`].
+ pub fn find_bitmap(&self, name: &str) -> Result<&BitmapDef, Error> {
+ debug!("Looking up bitmap {name}");
+
+ for filedef in &self.0 {
+ for s in &filedef.bitmaps {
+ if s.name == name {
+ return Ok(s);
+ }
+ }
+ }
+
+ Err(Error::MissingDefinition(format!("missing enum {name}")))
+ }
}
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com> --- tools/rust/xenbindgen/src/main.rs | 8 + tools/rust/xenbindgen/src/spec.rs | 357 ++++++++++++++++++++++++++++-- 2 files changed, 341 insertions(+), 24 deletions(-)