Skip to content

Commit

Permalink
Metadata generation of attributes and constants (#2844)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr authored Feb 13, 2024
1 parent 7ce7894 commit ece2390
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 111 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/.vscode
/.vs
/target
/temp
*.lock
*.winmd
4 changes: 2 additions & 2 deletions crates/libs/bindgen/src/rdl/to_winmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn write_interface(writer: &mut winmd::Writer, namespace: &str, name: &str, memb
}

writer.tables.TypeDef.push(winmd::TypeDef {
Extends: 0,
Extends: winmd::TypeDefOrRef::none(),
FieldList: writer.tables.Field.len() as u32,
MethodList: writer.tables.MethodDef.len() as u32,
Flags: flags.0,
Expand All @@ -82,7 +82,7 @@ fn write_interface(writer: &mut winmd::Writer, namespace: &str, name: &str, memb
writer.tables.GenericParam.push(writer::GenericParam {
Number: number as u16,
Flags: 0,
Owner: writer::TypeOrMethodDef::TypeDef(writer.tables.TypeDef.len() as u32 - 1).encode(),
Owner: writer::TypeOrMethodDef::TypeDef(writer.tables.TypeDef.len() as u32 - 1),
Name: writer.strings.insert(generic),
});
}
Expand Down
80 changes: 77 additions & 3 deletions crates/libs/bindgen/src/winmd/from_reader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use metadata::{AsRow, HasAttributes};

pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap<&str, &str>, output: &str) -> Result<()> {
let mut writer = Writer::new(output);
Expand All @@ -22,7 +23,7 @@ pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap

let generics = &metadata::type_def_generics(def);

let extends = if let Some(extends) = def.extends() { writer.insert_type_ref(extends.namespace, extends.name) } else { 0 };
let extends = if let Some(extends) = def.extends() { writer.insert_type_ref(extends.namespace, extends.name) } else { TypeDefOrRef::none() };

writer.tables.TypeDef.push(TypeDef {
Extends: extends,
Expand All @@ -33,11 +34,13 @@ pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap
TypeNamespace: writer.strings.insert(def.namespace()),
});

let def_ref = writer.tables.TypeDef.len() as u32 - 1;

for generic in def.generics() {
writer.tables.GenericParam.push(GenericParam {
Number: generic.number(), // TODO: isn't this just going to be incremental?
Flags: 0,
Owner: TypeOrMethodDef::TypeDef(writer.tables.TypeDef.len() as u32 - 1).encode(),
Owner: TypeOrMethodDef::TypeDef(def_ref),
Name: writer.strings.insert(generic.name()),
});
}
Expand All @@ -53,7 +56,7 @@ pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap
rest => unimplemented!("{rest:?}"),
};

writer.tables.InterfaceImpl.push(InterfaceImpl { Class: writer.tables.TypeDef.len() as u32 - 1, Interface: reference });
writer.tables.InterfaceImpl.push(InterfaceImpl { Class: def_ref, Interface: reference });
}

// TODO: if the class is "Apis" then should we sort the fields (constants) and methods (functions) for stability
Expand All @@ -63,6 +66,10 @@ pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap
let signature = writer.insert_field_sig(&ty);

writer.tables.Field.push(Field { Flags: field.flags().0, Name: writer.strings.insert(field.name()), Signature: signature });

if let Some(constant) = field.constant() {
writer.tables.Constant.push(Constant { Type: constant.usize(0) as u16, Parent: HasConstant::Field(writer.tables.Field.len() as u32 - 1), Value: writer.blobs.insert(&constant.blob(2)) })
}
}

for method in def.methods() {
Expand All @@ -85,13 +92,80 @@ pub fn from_reader(reader: &metadata::Reader, config: std::collections::BTreeMap
writer.tables.Param.push(Param { Flags: param.flags().0, Sequence: param.sequence(), Name: writer.strings.insert(param.name()) });
}
}

for attribute in def.attributes() {
let metadata::AttributeType::MemberRef(attribute_ctor) = attribute.ty();
assert_eq!(attribute_ctor.name(), ".ctor");
let metadata::MemberRefParent::TypeRef(attribute_type) = attribute_ctor.parent();

let attribute_type_ref = if let TypeDefOrRef::TypeRef(type_ref) = writer.insert_type_ref(attribute_type.namespace(), attribute_type.name()) { MemberRefParent::TypeRef(type_ref) } else { panic!() };

let signature = attribute_ctor.signature();
let return_type = winmd_type(&signature.return_type);
let param_types: Vec<Type> = signature.params.iter().map(winmd_type).collect();
let signature = writer.insert_method_sig(signature.call_flags, &return_type, &param_types);

writer.tables.MemberRef.push(MemberRef { Class: attribute_type_ref, Name: writer.strings.insert(".ctor"), Signature: signature });

let mut values = 1u16.to_le_bytes().to_vec(); // prolog
let args = attribute.args();
let mut named_arg_count = false;

for (index, (name, value)) in args.iter().enumerate() {
value_blob(value, &mut values);

if !named_arg_count && !name.is_empty() {
named_arg_count = true;
let named_arg_count = (args.len() - index) as u16;
values.extend_from_slice(&named_arg_count.to_le_bytes());
break;
}
}

if !named_arg_count {
values.extend_from_slice(&0u16.to_le_bytes());
}

let values = writer.blobs.insert(&values);

writer.tables.CustomAttribute.push(CustomAttribute { Parent: HasAttribute::TypeDef(def_ref), Type: AttributeType::MemberRef(writer.tables.MemberRef.len() as u32 - 1), Value: values });
}
}

// TODO: In theory, `config` could instruct this function to balance the types across a number of winmd files
// like mdmerge supports for namespace-splitting.
write_to_file(output, writer.into_stream()).map_err(|err| err.with_path(output))
}

// TODO: need a Blob type for writing
fn value_blob(value: &metadata::Value, blob: &mut Vec<u8>) {
match value {
metadata::Value::Bool(value) => {
if *value {
blob.push(1)
} else {
blob.push(0)
}
}
metadata::Value::U32(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::I32(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::U16(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::U8(value) => blob.extend_from_slice(&value.to_le_bytes()),
metadata::Value::EnumDef(_def, value) => value_blob(value, blob),

metadata::Value::TypeName(value) => {
let value = value.to_string();
usize_blob(value.len(), blob);
blob.extend_from_slice(value.as_bytes());
}
metadata::Value::String(value) => {
usize_blob(value.len(), blob);
blob.extend_from_slice(value.as_bytes());
}
rest => unimplemented!("{rest:?}"),
}
}

// TODO: keep the basic type conversion
fn winmd_type(ty: &metadata::Type) -> Type {
match ty {
Expand Down
104 changes: 51 additions & 53 deletions crates/libs/bindgen/src/winmd/writer/codes.rs
Original file line number Diff line number Diff line change
@@ -1,71 +1,69 @@
#![allow(dead_code, clippy::enum_variant_names)]

/// A `ResolutionScope` is an index into a certain table indicating the scope in which a TypeRef can be resolved.
#[derive(Clone)]
pub enum ResolutionScope {
Module(u32),
ModuleRef(u32),
AssemblyRef(u32),
TypeRef(u32),
macro_rules! code {
($name:ident($size:literal) $(($table:ident, $code:literal))+) => {
#[derive(Clone, Copy)]
pub enum $name {
$($table(u32),)*
}
impl $name {
pub fn encode(&self) -> u32 {
match self {
$(Self::$table(row) => (row.overflowing_add(1).0) << $size | $code,)*
}
}
}
};
}

impl ResolutionScope {
pub fn encode(&self) -> u32 {
match self {
Self::Module(row) => (row + 1) << 2,
Self::ModuleRef(row) => ((row + 1) << 2) + 1,
Self::AssemblyRef(row) => ((row + 1) << 2) + 2,
Self::TypeRef(row) => ((row + 1) << 2) + 3,
}
}
code! { AttributeType(3)
(MemberRef, 3)
}

/// A `TypeDefOrRef` is an index into a certain table used to locate a type definition.
#[derive(Clone)]
pub enum TypeDefOrRef {
TypeDef(u32),
TypeRef(u32),
TypeSpec(u32),
// TODO: needs to be called HasCustomAttribute
code! { HasAttribute(5)
(MethodDef, 0)
(Field, 1)
(TypeRef, 2)
(TypeDef, 3)
(Param, 4)
(InterfaceImpl, 5)
(MemberRef, 6)
(TypeSpec, 13)
(GenericParam, 19)
}

impl TypeDefOrRef {
pub fn encode(&self) -> u32 {
match self {
Self::TypeDef(row) => (row + 1) << 2,
Self::TypeRef(row) => ((row + 1) << 2) + 1,
Self::TypeSpec(row) => ((row + 1) << 2) + 2,
}
}
code! { HasConstant(2)
(Field, 0)
}

/// A `HasConstant` is an index into a certain table used to identify the parent of a row in the `Constant` table.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum HasConstant {
Field(u32),
Param(u32),
Property(u32),
code! { MemberForwarded(1)
(MethodDef, 1)
}

impl HasConstant {
pub fn encode(&self) -> u32 {
match self {
Self::Field(row) => (row + 1) << 2,
Self::Param(row) => ((row + 1) << 2) + 1,
Self::Property(row) => ((row + 1) << 2) + 2,
}
}
code! { MemberRefParent(3)
(TypeRef, 1)
}

/// A `TypeOrMethodDef` is an index into a certain table used to locate the owner of a generic parameter.
#[derive(Clone)]
pub enum TypeOrMethodDef {
TypeDef(u32),
code! { TypeDefOrRef(2)
(TypeDef, 0)
(TypeRef, 1)
(TypeSpec, 2)
}

impl TypeOrMethodDef {
pub fn encode(&self) -> u32 {
match self {
Self::TypeDef(row) => (row + 1) << 1,
}
impl TypeDefOrRef {
pub fn none() -> Self {
TypeDefOrRef::TypeDef(u32::MAX)
}
}

code! { TypeOrMethodDef(1)
(TypeDef, 0)
}

code! { ResolutionScope(2)
(Module, 0)
(ModuleRef, 1)
(AssemblyRef, 2)
(TypeRef, 3)
}
Loading

0 comments on commit ece2390

Please sign in to comment.