diff mbox series

[RFC,07/25] tools/xenbindgen: Add support for structs in TOML specs

Message ID 20241115115200.2824-8-alejandro.vallejo@cloud.com (mailing list archive)
State New
Headers show
Series Introduce xenbindgen to autogen hypercall structs | expand

Commit Message

Alejandro Vallejo Nov. 15, 2024, 11:51 a.m. UTC
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
---
 tools/rust/xenbindgen/src/c_lang.rs | 56 ++++++++++++++++++++++++-
 tools/rust/xenbindgen/src/spec.rs   | 64 ++++++++++++++++++++++++++++-
 2 files changed, 117 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/tools/rust/xenbindgen/src/c_lang.rs b/tools/rust/xenbindgen/src/c_lang.rs
index f05e36bb362f..597e0ed41362 100644
--- a/tools/rust/xenbindgen/src/c_lang.rs
+++ b/tools/rust/xenbindgen/src/c_lang.rs
@@ -17,9 +17,10 @@ 
 
 use std::fmt::Write;
 
-use crate::spec::OutFileDef;
+use crate::spec::{OutFileDef, StructDef, Typ};
 
 use convert_case::{Case, Casing};
+use log::{debug, trace};
 
 /// An abstract indentation level. 0 is no indentation, 1 is [`INDENT_WIDTH`]
 /// and so on.
@@ -29,6 +30,39 @@  struct Indentation(usize);
 /// Default width of each level of indentation
 const INDENT_WIDTH: usize = 4;
 
+/// Create a C-compatible struct field. Without the terminating semicolon.
+fn structfield(typ: &Typ, name: &str) -> String {
+    match typ {
+        Typ::Ptr(x) => {
+            let t: &Typ = x;
+            format!(
+                "XEN_GUEST_HANDLE_64({}) {name}",
+                match t {
+                    Typ::U8 => "uint8",
+                    Typ::U16 => "uint16",
+                    Typ::U32 => "uint32",
+                    Typ::U64 => "uint64_aligned_t",
+                    Typ::I8 => "int8",
+                    Typ::I16 => "int16",
+                    Typ::I32 => "int32",
+                    Typ::I64 => "int64_aligned_t",
+                    _ => panic!("foo {t:?}"),
+                }
+            )
+        }
+        Typ::Struct(x) => format!("struct {x} {name}"),
+        Typ::Array(x, len) => format!("{}{name}[{len}]", structfield(x, "")),
+        Typ::U8 => format!("uint8_t {name}"),
+        Typ::U16 => format!("uint16_t {name}"),
+        Typ::U32 => format!("uint32_t {name}"),
+        Typ::U64 => format!("uint64_aligned_t {name}"),
+        Typ::I8 => format!("int8_t {name}"),
+        Typ::I16 => format!("int16_t {name}"),
+        Typ::I32 => format!("int32_t {name}"),
+        Typ::I64 => format!("int64_aligned_t {name}"),
+    }
+}
+
 /// Add a comment to a struct or a field.
 fn comment(out: &mut String, comment: &str, ind: Indentation) {
     let spaces = " ".repeat(INDENT_WIDTH * ind.0);
@@ -48,6 +82,22 @@  fn comment(out: &mut String, comment: &str, ind: Indentation) {
     }
 }
 
+/// Write a C-compatible struct onto `out`
+fn structgen(out: &mut String, def: &StructDef) {
+    debug!("struct {}", def.name);
+
+    comment(out, &def.description, Indentation(0));
+    writeln!(out, "struct {} {{", def.name.to_case(Case::Snake)).unwrap();
+    for f in &def.fields {
+        trace!("  field {} type={:?}", f.name, f.typ);
+
+        comment(out, &f.description, Indentation(1));
+        writeln!(out, "    {};", structfield(&f.typ, &f.name),).unwrap();
+    }
+    writeln!(out, "}};").unwrap();
+    writeln!(out).unwrap();
+}
+
 /// Generates a single `.h` file.
 ///
 /// `filedef` is a language-agnostic high level description of what the output
@@ -67,6 +117,10 @@  pub fn parse(filedef: &OutFileDef) -> String {
     writeln!(out, "#ifndef __XEN_AUTOGEN_{name}_H").unwrap();
     writeln!(out, "#define __XEN_AUTOGEN_{name}_H\n").unwrap();
 
+    for def in &filedef.structs {
+        structgen(&mut out, def);
+    }
+
     writeln!(out, "#endif /* __XEN_AUTOGEN_{name}_H */\n").unwrap();
 
     out
diff --git a/tools/rust/xenbindgen/src/spec.rs b/tools/rust/xenbindgen/src/spec.rs
index 08c4dc3a7eba..e183378329ad 100644
--- a/tools/rust/xenbindgen/src/spec.rs
+++ b/tools/rust/xenbindgen/src/spec.rs
@@ -17,9 +17,61 @@  use std::{fs::read_to_string, path::Path};
 
 use log::{debug, info};
 
+/// An IDL type. A type may be a primitive integer, a pointer to an IDL type,
+/// an array of IDL types or a struct composed of IDL types. Every integer must
+/// be aligned to its size.
+///
+/// FIXME: This enumerated type is recovered as-is from the `typ` field in the
+/// 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)]
+#[serde(rename_all = "lowercase", tag = "tag", content = "args")]
+pub enum Typ {
+    Struct(String),
+    U8,
+    U16,
+    U32,
+    U64,
+    I8,
+    I16,
+    I32,
+    I64,
+    Ptr(Box<Typ>),
+    Array(Box<Typ>, usize),
+}
+
+/// Deserialized form of a hypercall struct
+#[derive(Debug, serde::Deserialize)]
+pub struct StructDef {
+    /// Name of the struct
+    pub name: String,
+    /// Description of what the struct is for. This string is added as a comment
+    /// on top of the autogenerated struct.
+    pub description: String,
+    /// Fields contained in the struct. May be none, in which case it's a zero
+    /// length struct.
+    pub fields: Vec<FieldDef>,
+}
+
+/// Deserialized form of a field within a hypercall struct (see [`StructDef`])
+#[derive(Debug, serde::Deserialize)]
+pub struct FieldDef {
+    /// Name of the field
+    pub name: String,
+    /// Description of what the field is for. This string is added as a comment
+    /// on top of the autogenerated field.
+    pub description: String,
+    /// Type of the field.
+    pub typ: Typ,
+}
+
 /// A language-agnostic specification.
 #[derive(Debug, serde::Deserialize)]
-struct InFileDef;
+struct InFileDef {
+    /// List of structs described in this input specification.
+    structs: Option<Vec<StructDef>>,
+}
 
 /// Description of an abstract output (i.e: `.rs`, `.h`, etc).
 ///
@@ -28,6 +80,8 @@  struct InFileDef;
 pub struct OutFileDef {
     /// The name of the output file, without the final extension.
     pub name: String,
+    /// List of structs described by all input spec files merged on this file.
+    pub structs: Vec<StructDef>,
 }
 
 impl OutFileDef {
@@ -40,13 +94,19 @@  impl OutFileDef {
     pub fn new(name: String, dir: &Path) -> Result<Self, Error> {
         info!("Reading {dir:?} to generate an output file");
 
-        let ret = Self { name };
+        let mut ret = Self {
+            name,
+            structs: Vec::new(),
+        };
 
         for entry in from_ioerr(dir.read_dir())? {
             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);
+            }
         }
 
         Ok(ret)