@@ -6,6 +6,9 @@ XENBINDGEN=$(CURDIR)/xenbindgen
# Path to the autogenerated Rust bindings crate
CRATE_XENSYS=$(CURDIR)/xen-sys
+# Output folder for the autogenerated Rust files
+AUTOGEN_RS=$(CRATE_XENSYS)/src/autogen
+
# Output folder for the autogenerated C headers
#
# Must contain autogenerated files only because they're all wiped on update
@@ -25,13 +28,18 @@ all install uninstall clean:
# Remove all autogenerated files
.PHONY: clean-autogen
clean-autogen:
- rm -rf "${AUTOGEN_C}"
+ rm -rf "${AUTOGEN_RS}" "${AUTOGEN_C}"
# Refresh autogenerated files. Depending on clean-autogen is required in order
# for removals of specification files to cause the removal of their
# autogenerated files.
.PHONY: update
update: clean-autogen
+ # Update Rust bindings
+ cargo run --manifest-path "${XENBINDGEN}/Cargo.toml" -- --lang rust \
+ --indir "${XENBINDGEN}/extra" --outdir "${AUTOGEN_RS}"
+ cargo fmt --manifest-path "${CRATE_XENSYS}/Cargo.toml"
+
# Update C bindings
cargo run --manifest-path "${XENBINDGEN}/Cargo.toml" -- --lang c \
--indir "${XENBINDGEN}/extra" --outdir "${AUTOGEN_C}"
@@ -5,6 +5,8 @@
//! a few closely related primitives, like [`Align64`].
#![no_std]
+pub mod autogen;
+
use core::ops::{Deref, DerefMut};
/// Wrapper for pointers and 64bit integers so they are _always_ aligned to 8
@@ -5,6 +5,7 @@
mod spec;
mod c_lang;
+mod rs_lang;
use std::{io::Write, path::PathBuf};
@@ -35,6 +36,8 @@ struct Cli {
enum Lang {
#[doc(hidden)]
C,
+ #[doc(hidden)]
+ Rust,
}
fn main() {
@@ -66,6 +69,7 @@ fn main() {
};
let (extension, parser): (&str, fn(&OutFileDef) -> String) = match cli.lang {
+ Lang::Rust => (".rs", rs_lang::parse),
Lang::C => (".h", c_lang::parse),
};
new file mode 100644
@@ -0,0 +1,227 @@
+//! Rust backend
+//!
+//! A backend for the Rust programming language. Enums, structs and bitmaps
+//! are native (with the latter being available via the `bitflags` crate.
+//!
+//! 64bit primitives and pointers are wrapped in a _magic_ type called `Align64`.
+//! This type is expected to exist in the target crate and is meant to provide
+//! ergonomic mechanisms to, create, access and modify its contents. Its whole
+//! purpose is make those types have 64bit size and be 64bit aligned even on
+//! 32bit targets.
+use std::fmt::Write;
+
+use crate::spec::{BitmapDef, EnumDef, IncludeDef, 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.
+#[derive(Copy, Clone)]
+struct Indentation(usize);
+
+/// Default width of each level of indentation
+const INDENT_WIDTH: usize = 4;
+
+/// Convert an IDL type into its Rust type.
+fn typ_rs(typ: &Typ) -> String {
+ match typ {
+ Typ::Ptr(x) => format!("crate::Align64<*mut {}>", typ_rs(x)),
+ Typ::Enum(x) | Typ::Struct(x) | Typ::Bitmap(x) => x.to_case(Case::Pascal),
+ Typ::Array(x, len) => format!("[{}; {}]", typ_rs(x), len),
+ Typ::U8 => String::from("u8"),
+ Typ::U16 => String::from("u16"),
+ Typ::U32 => String::from("u32"),
+ Typ::U64 => String::from("crate::Align64<u64>"),
+ Typ::I8 => String::from("i8"),
+ Typ::I16 => String::from("i16"),
+ Typ::I32 => String::from("i32"),
+ Typ::I64 => String::from("crate::Align64<i64>"),
+ }
+}
+
+/// 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);
+ for line in comment.split('\n') {
+ write!(out, "{spaces}///").unwrap();
+ if !line.is_empty() {
+ write!(out, " {line}").unwrap();
+ }
+ writeln!(out).unwrap();
+ }
+}
+
+/// Perform external inclusion in the form of a Rust _use_ statement.
+fn includegen(out: &mut String, def: &IncludeDef) {
+ if def.imports.is_empty() {
+ return;
+ }
+
+ let refered_pascal: Vec<String> = def
+ .imports
+ .iter()
+ .map(|s| s.to_case(Case::Pascal))
+ .collect();
+
+ writeln!(
+ out,
+ "use super::{}::{{{}}};",
+ &def.from,
+ refered_pascal.join(",")
+ )
+ .unwrap();
+
+ writeln!(out).unwrap();
+}
+
+/// Write a Rust-compatible struct onto `out`
+fn structgen(out: &mut String, def: &StructDef) {
+ debug!("struct {}", def.name);
+
+ comment(out, &def.description, Indentation(0));
+ writeln!(out, "#[repr(C)]").unwrap();
+ writeln!(out, "#[derive(Clone, Debug, Default)]").unwrap();
+ write!(out, "pub struct {}", def.name.to_case(Case::Pascal)).unwrap();
+
+ if def.fields.is_empty() {
+ // zero-sized struct
+ writeln!(out, ";\n").unwrap();
+ return;
+ }
+
+ writeln!(out, " {{").unwrap();
+
+ for f in &def.fields {
+ trace!(" field {} type={:?}", f.name, f.typ);
+
+ comment(out, &f.description, Indentation(1));
+ writeln!(
+ out,
+ " pub {}: {},",
+ f.name.to_case(Case::Snake),
+ typ_rs(&f.typ)
+ )
+ .unwrap();
+ }
+ writeln!(out, "}}\n").unwrap();
+}
+
+/// Write a Rust-compatible enum onto `out`
+fn enumgen(out: &mut String, def: &EnumDef) {
+ debug!("enum {}", def.name);
+
+ comment(out, &def.description, Indentation(0));
+
+ // If the underlying type is 64bits things get trickier. What we want
+ // in that case is to use u64 for the underlying type, but set
+ // align(8) on the overall repr. Otherwise 32bit platforms have the wrong
+ // alignment.
+ let repr: &str = if def.typ == Typ::U64 || def.typ == Typ::I64 {
+ "align(8)"
+ } else {
+ &typ_rs(&def.typ)
+ };
+
+ writeln!(out, "#[repr({repr})]").unwrap();
+ writeln!(out, "#[derive(Clone, Debug, Default, PartialEq, Eq)]").unwrap();
+ writeln!(out, "pub enum {} {{", def.name.to_case(Case::Pascal)).unwrap();
+ for (i, f) in def.variants.iter().enumerate() {
+ trace!(" variant {}={}", f.name, f.value);
+
+ comment(out, &f.description, Indentation(1));
+ if i == 0 {
+ writeln!(out, " #[default]").unwrap();
+ }
+ writeln!(out, " {} = {},", f.name.to_case(Case::Pascal), f.value).unwrap();
+ }
+ writeln!(out, "}}\n").unwrap();
+}
+
+/// Write a Rust-compatible bitmap onto `out`.
+///
+/// NOTE: Uses the bitflags crate underneath
+fn bitmapgen(out: &mut String, def: &BitmapDef) {
+ debug!("bitmap {}", def.name);
+
+ writeln!(out, "bitflags! {{").unwrap();
+ comment(out, &def.description, Indentation(1));
+
+ // If the underlying type is 64bits things get trickier. What we want
+ // in that case is to use u64 for the underlying type, but set
+ // align(8) on the overall repr.
+ let is_64bits = def.typ == Typ::U64 || def.typ == Typ::I64;
+ let repr = if is_64bits { "align(8)" } else { "C" };
+
+ writeln!(out, " #[repr({repr})]").unwrap();
+ writeln!(
+ out,
+ " #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]"
+ )
+ .unwrap();
+ writeln!(
+ out,
+ " pub struct {}: {} {{",
+ def.name.from_case(Case::Snake).to_case(Case::Pascal),
+ if is_64bits {
+ "u64".to_owned()
+ } else {
+ typ_rs(&def.typ)
+ }
+ )
+ .unwrap();
+
+ for f in &def.bits {
+ trace!(" shift {}={}", f.name, f.shift);
+
+ comment(out, &f.description, Indentation(2));
+ writeln!(
+ out,
+ " const {} = 1 << {};",
+ f.name.from_case(Case::Snake).to_case(Case::Pascal),
+ f.shift
+ )
+ .unwrap();
+ }
+ writeln!(out, " }}").unwrap();
+
+ writeln!(out, "}}\n").unwrap();
+}
+
+/// Generates a single `.rs` file.
+///
+/// `filedef` is a language-agnostic high level description of what the output
+/// must contain. The function returns the contents of the new
+///
+/// # Aborts
+/// Aborts the process with `rc=1` on known illegal specifications.
+pub fn parse(filedef: &OutFileDef) -> String {
+ let mut out = String::new();
+
+ writeln!(out, "//! {}", &filedef.name).unwrap();
+ writeln!(out, "//!").unwrap();
+ writeln!(out, "//! AUTOGENERATED. DO NOT MODIFY").unwrap();
+ writeln!(out).unwrap();
+
+ if !filedef.bitmaps.is_empty() {
+ writeln!(out, "use bitflags::bitflags;\n").unwrap();
+ }
+
+ for def in &filedef.includes {
+ includegen(&mut out, def);
+ }
+
+ for def in &filedef.bitmaps {
+ bitmapgen(&mut out, def);
+ }
+
+ for def in &filedef.enums {
+ enumgen(&mut out, def);
+ }
+
+ for def in &filedef.structs {
+ structgen(&mut out, def);
+ }
+
+ out
+}