use serde_derive::{Deserialize, Serialize};
use serde_json;
pub const IR_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayoutIr {
pub version: u32,
pub types: Vec<TypeIr>,
}
impl LayoutIr {
pub fn new(types: Vec<TypeIr>) -> Self {
Self {
version: IR_SCHEMA_VERSION,
types,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypeIr {
pub type_name: String,
pub alignment: u64,
pub root: IrNode,
#[serde(default)]
pub parameters: Vec<IrParameter>,
}
impl TypeIr {
pub fn contains_sum_over_array(&self) -> bool {
self.root.contains_sum_over_array()
}
pub fn collect_call_nested_type_names(&self, out: &mut Vec<String>) {
self.root.collect_call_nested_type_names(out);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IrParameter {
pub name: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub derived: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Endianness {
Little,
Big,
}
impl Default for Endianness {
fn default() -> Self {
Endianness::Little
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct NodeMetadata {
#[serde(default)]
pub size_expr: Option<String>,
#[serde(default = "NodeMetadata::default_alignment")]
pub alignment: u64,
#[serde(default)]
pub endianness: Endianness,
}
impl NodeMetadata {
fn default_alignment() -> u64 {
1
}
pub fn aligned(alignment: u64) -> Self {
Self {
size_expr: None,
alignment,
endianness: Endianness::Little,
}
}
}
impl Default for NodeMetadata {
fn default() -> Self {
Self {
size_expr: None,
alignment: Self::default_alignment(),
endianness: Endianness::Little,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "kebab-case")]
pub enum IrNode {
ZeroSize {
#[serde(flatten)]
meta: NodeMetadata,
},
Const(ConstNode),
FieldRef(FieldRefNode),
AlignUp(AlignNode),
Switch(SwitchNode),
CallNested(CallNestedNode),
AddChecked(BinaryOpNode),
MulChecked(BinaryOpNode),
SumOverArray(SumOverArrayNode),
}
impl IrNode {
pub fn contains_sum_over_array(&self) -> bool {
match self {
IrNode::ZeroSize { .. } | IrNode::Const(_) | IrNode::FieldRef(_) => false,
IrNode::AlignUp(node) => node.node.contains_sum_over_array(),
IrNode::Switch(node) => {
node.cases
.iter()
.any(|case| case.node.contains_sum_over_array())
|| node
.default
.as_ref()
.map(|default| default.contains_sum_over_array())
.unwrap_or(false)
}
IrNode::CallNested(_) => false,
IrNode::AddChecked(node) | IrNode::MulChecked(node) => {
node.left.contains_sum_over_array() || node.right.contains_sum_over_array()
}
IrNode::SumOverArray(_) => true,
}
}
pub fn collect_call_nested_type_names(&self, out: &mut Vec<String>) {
match self {
IrNode::ZeroSize { .. } | IrNode::Const(_) | IrNode::FieldRef(_) => {}
IrNode::AlignUp(node) => node.node.collect_call_nested_type_names(out),
IrNode::Switch(node) => {
for case in &node.cases {
case.node.collect_call_nested_type_names(out);
}
if let Some(default) = &node.default {
default.collect_call_nested_type_names(out);
}
}
IrNode::CallNested(node) => out.push(node.type_name.clone()),
IrNode::AddChecked(node) | IrNode::MulChecked(node) => {
node.left.collect_call_nested_type_names(out);
node.right.collect_call_nested_type_names(out);
}
IrNode::SumOverArray(node) => {
node.count.collect_call_nested_type_names(out);
out.push(node.element_type_name.clone());
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstNode {
pub value: u64,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldRefNode {
pub path: String,
#[serde(default)]
pub parameter: Option<String>,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlignNode {
pub alignment: u64,
pub node: Box<IrNode>,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwitchNode {
pub tag: String,
pub cases: Vec<SwitchCase>,
#[serde(default)]
pub default: Option<Box<IrNode>>,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwitchCase {
pub tag_value: u64,
pub node: Box<IrNode>,
#[serde(default)]
pub parameters: Vec<IrParameter>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallNestedNode {
pub type_name: String,
#[serde(default)]
pub arguments: Vec<IrArgument>,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IrArgument {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BinaryOpNode {
pub left: Box<IrNode>,
pub right: Box<IrNode>,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SumOverArrayNode {
pub count: Box<IrNode>,
pub element_type_name: String,
pub field_name: String,
#[serde(flatten)]
pub meta: NodeMetadata,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_ir_roundtrip() {
let ir = LayoutIr::new(vec![TypeIr {
type_name: "Example".into(),
alignment: 4,
root: IrNode::Const(ConstNode {
value: 4,
meta: NodeMetadata::aligned(4),
}),
parameters: vec![IrParameter {
name: "payload.len".into(),
description: Some("Number of elements in payload".into()),
derived: false,
}],
}]);
let json = serde_json::to_string_pretty(&ir).expect("serialize");
let de: LayoutIr = serde_json::from_str(&json).expect("deserialize");
assert_eq!(de.version, IR_SCHEMA_VERSION);
assert_eq!(de.types.len(), 1);
assert_eq!(de.types[0].type_name, "Example");
if let IrNode::Const(ConstNode { value, .. }) = &de.types[0].root {
assert_eq!(value, &4);
} else {
panic!("expected const node");
}
}
}