From 935219445031278158656df13ebe27c6459d05f6 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 28 May 2020 00:31:03 +0200 Subject: [PATCH] test: adds test to test abi compatibility --- crates/mun_codegen/src/code_gen/symbols.rs | 2 +- crates/mun_codegen/src/ir.rs | 2 +- crates/mun_codegen/src/ir/body.rs | 2 +- crates/mun_codegen/src/ir/type_table.rs | 4 +- .../src/ir/{ir_types.rs => types.rs} | 31 ++- crates/mun_codegen/src/ir/types/test.rs | 192 ++++++++++++++++++ crates/mun_codegen_macros/src/lib.rs | 106 +++++++++- 7 files changed, 322 insertions(+), 17 deletions(-) rename crates/mun_codegen/src/ir/{ir_types.rs => types.rs} (78%) create mode 100644 crates/mun_codegen/src/ir/types/test.rs diff --git a/crates/mun_codegen/src/code_gen/symbols.rs b/crates/mun_codegen/src/code_gen/symbols.rs index fd947b1b5..bf578b168 100644 --- a/crates/mun_codegen/src/code_gen/symbols.rs +++ b/crates/mun_codegen/src/code_gen/symbols.rs @@ -1,4 +1,4 @@ -use crate::ir::ir_types as ir; +use crate::ir::types as ir; use crate::ir::{ dispatch_table::{DispatchTable, DispatchableFunction}, function, diff --git a/crates/mun_codegen/src/ir.rs b/crates/mun_codegen/src/ir.rs index 444858acf..9af50e5d9 100644 --- a/crates/mun_codegen/src/ir.rs +++ b/crates/mun_codegen/src/ir.rs @@ -14,9 +14,9 @@ pub mod file; pub(crate) mod file_group; pub mod function; mod intrinsics; -pub mod ir_types; pub mod ty; pub(crate) mod type_table; +pub mod types; /// Try to down cast an `AnyTypeEnum` into a `BasicTypeEnum`. fn try_convert_any_to_basic(ty: AnyTypeEnum) -> Option { diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 93c119259..600661cc3 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -14,7 +14,7 @@ use inkwell::{ }; use std::{collections::HashMap, sync::Arc}; -use crate::ir::ir_types as ir; +use crate::ir::types as ir; use crate::value::Global; use hir::ResolveBitness; use inkwell::basic_block::BasicBlock; diff --git a/crates/mun_codegen/src/ir/type_table.rs b/crates/mun_codegen/src/ir/type_table.rs index 433191b57..833f81f20 100644 --- a/crates/mun_codegen/src/ir/type_table.rs +++ b/crates/mun_codegen/src/ir/type_table.rs @@ -1,4 +1,4 @@ -use super::ir_types as ir; +use super::types as ir; use crate::ir::dispatch_table::{DispatchTable, FunctionPrototype}; use crate::type_info::{TypeGroup, TypeInfo}; use crate::value::{AsValue, CanInternalize, Global, IrValueContext, IterAsIrValue, Value}; @@ -192,7 +192,7 @@ impl<'a, 'ctx, 'm, D: IrDatabase> TypeTableBuilder<'a, 'ctx, 'm, D> { .as_value(self.value_context), size_in_bits: type_info.size.bit_size as u32, alignment: type_info.size.alignment as u8, - type_group: type_info.group.to_abi_type(), + group: type_info.group.to_abi_type(), } .as_value(self.value_context); diff --git a/crates/mun_codegen/src/ir/ir_types.rs b/crates/mun_codegen/src/ir/types.rs similarity index 78% rename from crates/mun_codegen/src/ir/ir_types.rs rename to crates/mun_codegen/src/ir/types.rs index ad2280a4c..6f794a637 100644 --- a/crates/mun_codegen/src/ir/ir_types.rs +++ b/crates/mun_codegen/src/ir/types.rs @@ -32,40 +32,45 @@ impl TransparentValue for abi::StructMemoryKind { } } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunTypeInfo"] +#[abi_type(abi::TypeInfo)] pub struct TypeInfo { pub guid: abi::Guid, pub name: Value<*const u8>, pub size_in_bits: u32, pub alignment: u8, - pub type_group: abi::TypeGroup, + pub group: abi::TypeGroup, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunFunctionSignature"] +#[abi_type(abi::FunctionSignature)] pub struct FunctionSignature { pub arg_types: Value<*const *const TypeInfo>, pub return_type: Value<*const TypeInfo>, pub num_arg_types: u16, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunFunctionPrototype"] +#[abi_type(abi::FunctionPrototype)] pub struct FunctionPrototype { pub name: Value<*const u8>, pub signature: FunctionSignature, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunFunctionDefinition"] +#[abi_type(abi::FunctionDefinition)] pub struct FunctionDefinition { pub prototype: FunctionPrototype, pub fn_ptr: Value<*const fn()>, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunStructInfo"] +#[abi_type(abi::StructInfo)] pub struct StructInfo { pub field_names: Value<*const *const u8>, pub field_types: Value<*const *const TypeInfo>, @@ -74,8 +79,9 @@ pub struct StructInfo { pub memory_kind: abi::StructMemoryKind, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunModuleInfo"] +#[abi_type(abi::ModuleInfo)] pub struct ModuleInfo { pub path: Value<*const u8>, pub functions: Value<*const FunctionDefinition>, @@ -84,19 +90,24 @@ pub struct ModuleInfo { pub num_types: u32, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunDispatchTable"] +#[abi_type(abi::DispatchTable)] pub struct DispatchTable { pub prototypes: Value<*const FunctionPrototype>, - pub fn_ptrs: Value<*const *mut fn()>, + pub fn_ptrs: Value<*mut *const fn()>, pub num_entries: u32, } -#[derive(AsValue)] +#[derive(AsValue, TestIsAbiCompatible)] #[ir_name = "struct.MunAssemblyInfo"] +#[abi_type(abi::AssemblyInfo)] pub struct AssemblyInfo { pub symbols: ModuleInfo, pub dispatch_table: DispatchTable, pub dependencies: Value<*const *const u8>, pub num_dependencies: u32, } + +#[cfg(test)] +mod test; diff --git a/crates/mun_codegen/src/ir/types/test.rs b/crates/mun_codegen/src/ir/types/test.rs new file mode 100644 index 000000000..e2a6eb8b7 --- /dev/null +++ b/crates/mun_codegen/src/ir/types/test.rs @@ -0,0 +1,192 @@ +use crate::value::{ConcreteValueType, Value}; + +/// A trait implemented by a type to check if its values match its corresponding abi type (T). This +/// trait can be derived. e.g.: +/// +/// ```ignore,rust +/// #[derive(AsValue, TestIsAbiCompatible)] +/// #[ir_name = "struct.MunTypeInfo"] +/// #[abi_type(abi::TypeInfo)] +/// pub struct TypeInfo { } +/// ``` +/// +/// The procedural macro calls for every field: +/// +/// ```ignore,rust +/// self::test::AbiTypeHelper::from_value(&abi_value.) +/// .ir_type::<>() +/// .assert_compatible(, , ); +/// ``` +pub trait TestIsAbiCompatible { + /// Runs a test to see if the implementor is compatible with the specified abi type. + fn test(abi_value: &T); +} + +/// A trait that is implemented if a type is ABI compatible with with T. +pub trait IsAbiCompatible {} + +/// A trait indicating that a type is *not* compatible. This is a helper trait used to determine +/// if a type is ABI compatible at runtime. By default this trait is implemented for all types. +pub trait IsNotAbiCompatible { + fn assert_compatible(&self, ir_type: &str, abi_type: &str, field_name: &str) { + panic!( + "the field '{}' on type '{}' is not compatible with the ABI version of the struct ({})", + field_name, ir_type, abi_type + ); + } +} +impl IsNotAbiCompatible for T {} + +/// Helper structs that enables extracting the type of a value. +pub struct AbiTypeHelper(std::marker::PhantomData); +pub struct AbiAndIrTypeHelper(std::marker::PhantomData, std::marker::PhantomData); + +impl AbiTypeHelper { + pub fn from_value(_: &T) -> Self { + Self(Default::default()) + } + + pub fn ir_type(&self) -> AbiAndIrTypeHelper { + AbiAndIrTypeHelper(Default::default(), Default::default()) + } +} + +impl> AbiAndIrTypeHelper { + pub fn assert_compatible(&self, _ir_type: &str, _abi_type: &str, _field_name: &str) {} +} + +impl IsAbiCompatible for u8 {} +impl IsAbiCompatible for u16 {} +impl IsAbiCompatible for u32 {} +impl IsAbiCompatible for abi::Guid {} +impl IsAbiCompatible for abi::TypeGroup {} +impl IsAbiCompatible for abi::StructMemoryKind {} +impl IsAbiCompatible<*const ::std::os::raw::c_char> for *const u8 {} +impl IsAbiCompatible<*const ::std::os::raw::c_void> for *const fn() {} +impl> IsAbiCompatible<*const S> for *const T {} +impl> IsAbiCompatible<*mut S> for *mut T {} +impl IsAbiCompatible for Value where T: IsAbiCompatible {} + +#[test] +#[cfg(test)] +fn test_type_info_abi_compatible() { + let abi_type = abi::TypeInfo { + guid: abi::Guid { + b: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }, + name: std::ptr::null(), + size_in_bits: 0, + alignment: 0, + group: abi::TypeGroup::FundamentalTypes, + }; + + super::TypeInfo::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_function_signature_abi_compatible() { + let abi_type = abi::FunctionSignature { + arg_types: std::ptr::null(), + return_type: std::ptr::null(), + num_arg_types: 0, + }; + + super::FunctionSignature::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_function_prototype_abi_compatible() { + let abi_type = abi::FunctionPrototype { + name: std::ptr::null(), + signature: abi::FunctionSignature { + arg_types: std::ptr::null(), + return_type: std::ptr::null(), + num_arg_types: 0, + }, + }; + + super::FunctionPrototype::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_function_definition_abi_compatible() { + let abi_type = abi::FunctionDefinition { + prototype: abi::FunctionPrototype { + name: std::ptr::null(), + signature: abi::FunctionSignature { + arg_types: std::ptr::null(), + return_type: std::ptr::null(), + num_arg_types: 0, + }, + }, + fn_ptr: std::ptr::null(), + }; + + super::FunctionDefinition::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_struct_info_abi_compatible() { + let abi_type = abi::StructInfo { + field_names: std::ptr::null(), + field_types: std::ptr::null(), + field_offsets: std::ptr::null(), + num_fields: 0, + memory_kind: abi::StructMemoryKind::Value, + }; + + super::StructInfo::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_module_info_abi_compatible() { + let abi_type = abi::ModuleInfo { + path: std::ptr::null(), + functions: std::ptr::null(), + num_functions: 0, + types: std::ptr::null(), + num_types: 0, + }; + + super::ModuleInfo::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_dispatch_table_abi_compatible() { + let abi_type = abi::DispatchTable { + prototypes: std::ptr::null(), + fn_ptrs: std::ptr::null_mut(), + num_entries: 0, + }; + + super::DispatchTable::test(&abi_type); +} + +#[test] +#[cfg(test)] +fn test_assembly_info_abi_compatible() { + let abi_type = abi::AssemblyInfo { + symbols: abi::ModuleInfo { + path: std::ptr::null(), + functions: std::ptr::null(), + num_functions: 0, + types: std::ptr::null(), + num_types: 0, + }, + dispatch_table: abi::DispatchTable { + prototypes: std::ptr::null(), + fn_ptrs: std::ptr::null_mut(), + num_entries: 0, + }, + dependencies: std::ptr::null(), + num_dependencies: 0, + }; + + super::AssemblyInfo::test(&abi_type); +} diff --git a/crates/mun_codegen_macros/src/lib.rs b/crates/mun_codegen_macros/src/lib.rs index 2330c657e..936638bfe 100644 --- a/crates/mun_codegen_macros/src/lib.rs +++ b/crates/mun_codegen_macros/src/lib.rs @@ -2,8 +2,11 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, Attribute, Data, DeriveInput, Lit, Meta}; +use syn::{parse_macro_input, Attribute, Data, DeriveInput, Lit, Meta, NestedMeta, Path}; +/// This procedural macro implements the `AsValue` trait as well as several required other traits. +/// All of these traits enable creating an `inkwell::values::StructValue` from a generic struct, as +/// long as all fields of the struct also implement `AsValue`. #[proc_macro_derive(AsValue, attributes(ir_name))] pub fn as_value_derive(input: TokenStream) -> TokenStream { // Parse Phase @@ -14,10 +17,12 @@ pub fn as_value_derive(input: TokenStream) -> TokenStream { Data::Enum(_) => panic!("#[derive(AsValue)] is only defined for structs, not for enums!"), }; + // Parse the `[ir_name = ".."]` part let mut ir_name = String::new(); for attr in derive_input .attrs .iter() + .filter(|a| a.path.get_ident().map(|i| *i == "ir_name").unwrap_or(false)) .map(Attribute::parse_meta) .filter_map(|x| x.ok()) { @@ -27,14 +32,16 @@ pub fn as_value_derive(input: TokenStream) -> TokenStream { ir_name = lit_str.value(); } _ => { - panic!("ActualName must be a string"); + panic!("ir_name must be a string"); } }; } } + // Get the typename of the struct we're working with let ident = &derive_input.ident; + // Generate a list of all field types let field_types_tuple = struct_data.fields.iter().map(|f| { let ty = &f.ty; quote! { @@ -42,6 +49,8 @@ pub fn as_value_derive(input: TokenStream) -> TokenStream { } }); + // Generate a list of where clauses that ensure that we can cast each field to an + // `inkwell::types::BasicTypeEnum` let field_types = struct_data.fields.iter().map(|f| { let ty = &f.ty; quote! { @@ -49,6 +58,8 @@ pub fn as_value_derive(input: TokenStream) -> TokenStream { } }); + // Generate a list of where clauses that ensure that we can cast each field to an + // `inkwell::values::BasicTypeValue` let field_types_values = struct_data.fields.iter().enumerate().map(|(idx, f)| { let name = f.ident.as_ref().map(|i| quote! { #i }).unwrap_or_else(|| quote! { #idx }); quote! { @@ -102,3 +113,94 @@ pub fn as_value_derive(input: TokenStream) -> TokenStream { impl crate::value::AddressableType<#ident> for #ident {} }).into() } + +/// A procedural macro that implements the `TestIsAbiCompatible` trait for a struct. This +/// implementation enables testing for every field of a struct whether its abi type is compatible +/// with the current implementation. +#[proc_macro_derive(TestIsAbiCompatible, attributes(abi_type))] +pub fn is_abi_compatible_derive(input: TokenStream) -> TokenStream { + // Parse Phase + let derive_input = parse_macro_input!(input as DeriveInput); + let struct_data = match derive_input.data { + Data::Struct(data) => data, + Data::Union(_) => { + panic!("#[derive(IsAbiCompatible)] is only defined for structs, not for unions!") + } + Data::Enum(_) => { + panic!("#[derive(IsAbiCompatible)] is only defined for structs, not for enums!") + } + }; + + // Parse the [abi_type(...)] part + let mut abi_type_name: Option = None; + for attr in derive_input + .attrs + .iter() + .filter(|a| { + a.path + .get_ident() + .map(|i| *i == "abi_type") + .unwrap_or(false) + }) + .map(Attribute::parse_meta) + .filter_map(|x| x.ok()) + { + if let Meta::List(meta_list) = attr { + if meta_list.nested.len() != 1 { + panic!("expected abi_type to be a single path") + } else if let NestedMeta::Meta(Meta::Path(p)) = meta_list.nested.first().unwrap() { + abi_type_name = Some(p.clone()); + } + } else { + panic!("expected abi_type to be path got: {:?}", attr) + } + } + + let abi_type = if let Some(tokens) = abi_type_name { + tokens + } else { + panic!("#[derive(IsAbiCompatible)] required abi_type to be defined") + }; + + // Construct the abi type path string + let abi_type_name = abi_type + .segments + .iter() + .map(|s| format!("{}", s.ident)) + .collect::>() + .join("::"); + + // Get the type and name of the struct we're implementing this for + let struct_type = &derive_input.ident; + let struct_type_name = format!("{}", struct_type); + + // Generate code for every field to test its compatibility + let field_types = struct_data.fields.iter().map(|f| { + let ty = &f.ty; + let name = f.ident.as_ref().unwrap().to_string(); + let ident = f.ident.as_ref().unwrap(); + quote! { + self::test::AbiTypeHelper::from_value(&abi_value.#ident) + .ir_type::<#ty>() + .assert_compatible(#struct_type_name, #abi_type_name, #name); + } + }); + + // Generate Phase + (quote! { + #[cfg(test)] + impl self::test::TestIsAbiCompatible<#abi_type> for #struct_type { + fn test(abi_value: &#abi_type) { + use self::test::*; + #(#field_types)* + } + } + + #[cfg(test)] + impl self::test::IsAbiCompatible<#abi_type> for #struct_type {} + + #[cfg(test)] + impl self::test::IsAbiCompatible<#struct_type> for #struct_type {} + }) + .into() +}