Skip to content

Commit

Permalink
generator: Parse and autogenerate version-related defines (#431)
Browse files Browse the repository at this point in the history
* generator: Emit deprecation warnings and documentation link for defines

* generator: Parse and autogenerate version-related defines

With more and more version-related defines showing up this saves us a
bunch of time.
  • Loading branch information
MarijnS95 authored May 30, 2021
1 parent 9d6bf63 commit b81b19a
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 82 deletions.
28 changes: 24 additions & 4 deletions ash/src/vk/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,31 @@ use crate::vk::platform_types::*;
use crate::vk::{ptr_chain_iter, Handle};
use std::fmt;
use std::os::raw::*;
pub const API_VERSION_1_0: u32 = crate::vk::make_version(1, 0, 0);
pub const API_VERSION_1_1: u32 = crate::vk::make_version(1, 1, 0);
pub const API_VERSION_1_2: u32 = crate::vk::make_version(1, 2, 0);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
((major) << 22) | ((minor) << 12) | (patch)
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
(version) >> 22
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
((version) >> 12) & 0x3ffu32
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
(version) & 0xfffu32
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_0.html>"]
pub const API_VERSION_1_0: u32 = make_version(1, 0, 0);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_1.html>"]
pub const API_VERSION_1_1: u32 = make_version(1, 1, 0);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_API_VERSION_1_2.html>"]
pub const API_VERSION_1_2: u32 = make_version(1, 2, 0);
pub const HEADER_VERSION: u32 = 174u32;
pub const HEADER_VERSION_COMPLETE: u32 = crate::vk::make_version(1, 2, HEADER_VERSION);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_HEADER_VERSION_COMPLETE.html>"]
pub const HEADER_VERSION_COMPLETE: u32 = make_version(1, 2, HEADER_VERSION);
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSampleMask.html>"]
pub type SampleMask = u32;
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkBool32.html>"]
Expand Down
16 changes: 0 additions & 16 deletions ash/src/vk/macros.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,3 @@
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
(major << 22) | (minor << 12) | patch
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
version >> 22
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
(version >> 12) & 0x3ff
}
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
version & 0xfff
}
#[macro_export]
macro_rules! vk_bitflags_wrapped {
($ name : ident , $ all : expr , $ flag_type : ty) => {
Expand Down
189 changes: 127 additions & 62 deletions generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
use itertools::Itertools;
use nom::{
alt, character::complete::digit1, delimited, do_parse, map, named, one_of, opt, pair, preceded,
tag, terminated,
alt,
character::complete::{digit1, hex_digit1},
complete, delimited, do_parse, map, named, one_of, opt, pair, preceded, tag, terminated, value,
};
use once_cell::sync::Lazy;
use proc_macro2::{Delimiter, Group, Literal, Span, TokenStream, TokenTree};
Expand All @@ -26,30 +27,37 @@ pub enum CType {
Bool32,
}

impl CType {
fn to_string(&self) -> &'static str {
match self {
Self::USize => "usize",
Self::U32 => "u32",
Self::U64 => "u64",
Self::Float => "f32",
Self::Bool32 => "Bool32",
}
}
}

impl quote::ToTokens for CType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let term = match self {
CType::USize => format_ident!("usize"),
CType::U32 => format_ident!("u32"),
CType::U64 => format_ident!("u64"),
CType::Float => format_ident!("f32"),
CType::Bool32 => format_ident!("Bool32"),
};
term.to_tokens(tokens);
format_ident!("{}", self.to_string()).to_tokens(tokens);
}
}

named!(ctype<&str, CType>,
alt!(
tag!("ULL") => { |_| CType::U64 } |
tag!("U") => { |_| CType::U32 }
value!(CType::U64, complete!(tag!("ULL"))) |
value!(CType::U32, complete!(tag!("U")))
)
);

named!(cexpr<&str, (CType, String)>,
alt!(
map!(cfloat, |f| (CType::Float, format!("{:.2}", f))) |
inverse_number
inverse_number |
decimal_number |
hexadecimal_number
)
);

Expand All @@ -61,6 +69,17 @@ named!(decimal_number<&str, (CType, String)>,
)
);

named!(hexadecimal_number<&str, (CType, String)>,
preceded!(
alt!(tag!("0x") | tag!("0X")),
map!(
pair!(hex_digit1, ctype),
|(num, typ)| (typ, format!("0x{}{}", num.to_ascii_lowercase(), typ.to_string())
)
)
)
);

named!(inverse_number<&str, (CType, String)>,
map!(
delimited!(
Expand Down Expand Up @@ -183,29 +202,6 @@ pub fn handle_nondispatchable_macro() -> TokenStream {
}
}
}
pub fn vk_version_macros() -> TokenStream {
quote! {
#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_MAKE_VERSION.html>"]
pub const fn make_version(major: u32, minor: u32, patch: u32) -> u32 {
(major << 22) | (minor << 12) | patch
}

#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MAJOR.html>"]
pub const fn version_major(version: u32) -> u32 {
version >> 22
}

#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_MINOR.html>"]
pub const fn version_minor(version: u32) -> u32 {
(version >> 12) & 0x3ff
}

#[doc = "<https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_VERSION_PATCH.html>"]
pub const fn version_patch(version: u32) -> u32 {
version & 0xfff
}
}
}
pub fn vk_bitflags_wrapped_macro() -> TokenStream {
quote! {
#[macro_export]
Expand Down Expand Up @@ -714,38 +710,73 @@ fn name_to_tokens(type_name: &str) -> Ident {
format_ident!("{}", new_name.as_str())
}

fn map_identifier_to_rust(ident: Ident) -> TokenTree {
match ident.to_string().as_str() {
"VK_MAKE_VERSION" => {
TokenTree::Group(Group::new(Delimiter::None, quote!(crate::vk::make_version)))
/// Parses and rewrites a C literal into Rust
///
/// If no special pattern is recognized the original literal is returned.
/// Any new conversions need to be added to the [`cexpr()`] [`nom`] parser.
///
/// Examples:
/// - `0x3FFU` -> `0x3ffu32`
fn convert_c_literal(lit: Literal) -> Literal {
if let Ok((_, (_, rexpr))) = cexpr(&lit.to_string()) {
// lit::SynInt uses the same `.parse` method to create hexadecimal
// literals because there is no `Literal` constructor for it.
let mut stream = rexpr.parse::<TokenStream>().unwrap().into_iter();
// If expression rewriting succeeds this should parse into a single literal
match (stream.next(), stream.next()) {
(Some(TokenTree::Literal(l)), None) => l,
x => panic!("Stream must contain a single literal, not {:?}", x),
}
s => format_ident!("{}", constant_name(s)).into(),
} else {
lit
}
}

/// Parse and yield a C expression that is valid to write in Rust
/// Identifiers are replaced with their Rust vk equivalent.
///
/// Examples:
/// - `VK_MAKE_VERSION(1, 2, VK_HEADER_VERSION)` -> `crate::vk::make_version(1, 2, HEADER_VERSION)`
/// - `VK_MAKE_VERSION(1, 2, VK_HEADER_VERSION)` -> `make_version(1, 2, HEADER_VERSION)`
/// - `2*VK_UUID_SIZE` -> `2 * UUID_SIZE`
fn convert_c_expression(c_expr: &str) -> TokenStream {
fn rewrite_token_stream(stream: TokenStream) -> TokenStream {
fn convert_c_expression(c_expr: &str, identifier_renames: &BTreeMap<String, Ident>) -> TokenStream {
fn rewrite_token_stream(
stream: TokenStream,
identifier_renames: &BTreeMap<String, Ident>,
) -> TokenStream {
stream
.into_iter()
.map(|tt| match tt {
TokenTree::Group(group) => TokenTree::Group(Group::new(
group.delimiter(),
rewrite_token_stream(group.stream()),
rewrite_token_stream(group.stream(), identifier_renames),
)),
TokenTree::Ident(term) => map_identifier_to_rust(term),
_ => tt,
TokenTree::Ident(term) => {
let name = term.to_string();
identifier_renames
.get(&name)
.cloned()
.unwrap_or_else(|| format_ident!("{}", constant_name(&name)))
.into()
}
TokenTree::Literal(lit) => TokenTree::Literal(convert_c_literal(lit)),
tt => tt,
})
.collect::<TokenStream>()
}
let c_expr = c_expr
.parse()
.unwrap_or_else(|_| panic!("Failed to parse `{}` as Rust", c_expr));
rewrite_token_stream(c_expr, identifier_renames)
}

let c_expr = c_expr.parse().unwrap();
rewrite_token_stream(c_expr)
fn discard_outmost_delimiter(stream: TokenStream) -> TokenStream {
let stream = stream.into_iter().collect_vec();
// Discard the delimiter if this stream consists of a single top-most group
if let [TokenTree::Group(group)] = stream.as_slice() {
TokenTree::Group(Group::new(Delimiter::None, group.stream())).into()
} else {
stream.into_iter().collect::<TokenStream>()
}
}

impl FieldExt for vkxml::Field {
Expand Down Expand Up @@ -1200,23 +1231,53 @@ pub fn generate_extension<'a>(
};
Some(q)
}
pub fn generate_define(define: &vkxml::Define) -> TokenStream {
pub fn generate_define(
define: &vkxml::Define,
identifier_renames: &mut BTreeMap<String, Ident>,
) -> TokenStream {
let name = constant_name(&define.name);
let ident = format_ident!("{}", name);
let deprecated = define
.comment
.as_ref()
.map_or(false, |c| c.contains("DEPRECATED"));

if name == "NULL_HANDLE" || deprecated {
if name == "NULL_HANDLE" {
quote!()
} else if let Some(value) = &define.value {
str::parse::<u32>(value).map_or(quote!(), |v| quote!(pub const #ident: u32 = #v;))
} else if let Some(c_expr) = &define.c_expression {
if define.defref.contains(&"VK_MAKE_VERSION".to_string()) {
let c_expr = convert_c_expression(c_expr);
if define.name.contains(&"VERSION".to_string()) {
let link = khronos_link(&define.name);
let c_expr = c_expr.trim_start_matches('\\');
let c_expr = c_expr.replace("(uint32_t)", "");
let c_expr = convert_c_expression(&c_expr, &identifier_renames);
let c_expr = discard_outmost_delimiter(c_expr);

let deprecated = define
.comment
.as_ref()
.and_then(|c| c.strip_prefix("DEPRECATED: "))
.map(|comment| quote!(#[deprecated = #comment]));

quote!(pub const #ident: u32 = #c_expr;)
let (code, ident) = if define.parameters.is_empty() {
(quote!(pub const #ident: u32 = #c_expr;), ident)
} else {
let params = define
.parameters
.iter()
.map(|param| format_ident!("{}", param))
.map(|i| quote!(#i: u32));
let ident = format_ident!("{}", name.to_lowercase());
(
quote!(pub const fn #ident(#(#params),*) -> u32 { #c_expr }),
ident,
)
};

identifier_renames.insert(define.name.clone(), ident);

quote! {
#deprecated
#[doc = #link]
#code
}
} else {
quote!()
}
Expand Down Expand Up @@ -1843,7 +1904,7 @@ pub fn derive_setters(
let set_size_stmt = if array_size.contains("ename:") || array_size.contains('*') {
// c_size should contain the same minus `ename:`-prefixed identifiers
let array_size = field.c_size.as_ref().unwrap_or(array_size);
let c_size = convert_c_expression(array_size);
let c_size = convert_c_expression(array_size, &BTreeMap::new());
let inner_type = field.inner_type_tokens();

slice_param_ty_tokens = quote!([#inner_type; #c_size]);
Expand Down Expand Up @@ -2186,9 +2247,12 @@ pub fn generate_definition(
root_structs: &HashSet<String, impl BuildHasher>,
bitflags_cache: &mut HashSet<Ident, impl BuildHasher>,
const_values: &mut BTreeMap<Ident, ConstantTypeInfo>,
identifier_renames: &mut BTreeMap<String, Ident>,
) -> Option<TokenStream> {
match *definition {
vkxml::DefinitionsElement::Define(ref define) => Some(generate_define(define)),
vkxml::DefinitionsElement::Define(ref define) => {
Some(generate_define(define, identifier_renames))
}
vkxml::DefinitionsElement::Typedef(ref typedef) => Some(generate_typedef(typedef)),
vkxml::DefinitionsElement::Struct(ref _struct) => {
Some(generate_struct(_struct, root_structs, union_types))
Expand Down Expand Up @@ -2585,6 +2649,8 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
})
.collect::<HashSet<&str>>();

let mut identifier_renames = BTreeMap::new();

let root_names = root_struct_names(&definitions);
let definition_code: Vec<_> = definitions
.into_iter()
Expand All @@ -2595,6 +2661,7 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
&root_names,
&mut bitflags_cache,
&mut const_values,
&mut identifier_renames,
)
})
.collect();
Expand All @@ -2611,7 +2678,6 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
let bitflags_macro = vk_bitflags_wrapped_macro();
let handle_nondispatchable_macro = handle_nondispatchable_macro();
let define_handle_macro = define_handle_macro();
let version_macros = vk_version_macros();

let ptr_chain_code = quote! {
/// Iterates through the pointer chain. Includes the item that is passed into the function.
Expand All @@ -2633,7 +2699,6 @@ pub fn write_source_code<P: AsRef<Path>>(vk_xml: &Path, src_dir: P) {
};

let macros_code = quote! {
#version_macros
#bitflags_macro
#handle_nondispatchable_macro
#define_handle_macro
Expand Down

0 comments on commit b81b19a

Please sign in to comment.