use crate::key::{
CollectionDelimiterPolicy, Endianness, EnumHolderWidth, EnumeratorValue, FixedDigitCount,
KeyHolderExtensibility, LayoutKey, LengthCodePolicy, MapDelimiter, MapEntryOrder,
MapKeyOrdering, MustUnderstandPolicy, PlCdr1HeaderPolicy, Purpose, ShortPidRange,
UnionLabelMatch, WcharPosition,
};
use crate::plan::{
Extent, LayoutError, LengthCode, MAX_DEPTH, MemberHeader, MemberPlan, Offset, TypeLayout,
align_to,
};
use alloc::vec::Vec;
use zerodds_ast::{Ast, CharWidth, Extensibility, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
pub const PID_SHORT_MAX_ID: u16 = 0x3f00;
pub const PID_EXTENDED: u16 = 0x3f01;
pub const PID_LIST_END: u16 = 0x3f02;
pub const PID_SHORT_MAX_LEN: u32 = 0xffff;
#[must_use]
pub fn collection_is_delimited(ast: &Ast, element: Id<TypeNode>, key: LayoutKey) -> bool {
if !key.encoding.is_v2() {
return false;
}
match key.collection_delimiters {
CollectionDelimiterPolicy::Never => false,
CollectionDelimiterPolicy::Specification => !is_primitive_after_aliases(ast, element),
CollectionDelimiterPolicy::EnumIsPrimitive => {
!is_primitive_after_aliases(ast, element)
&& !is_enumeration_or_bitmask_after_aliases(ast, element)
}
}
}
fn is_enumeration_or_bitmask_after_aliases(ast: &Ast, type_: Id<TypeNode>) -> bool {
let mut current = type_;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
return false;
};
match &node.kind {
TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => return true,
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(next) => current = next,
None => return false,
},
_ => return false,
}
}
false
}
#[must_use]
pub fn sequence_element_carries_length(
ast: &Ast,
element: Id<TypeNode>,
key: LayoutKey,
) -> Option<bool> {
if !key.sequence_element_delimiter.writes_element_length() {
return Some(false);
}
let node = ast.type_(element).ok()?;
Some(match &node.kind {
TypeKind::Structure { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Union { .. }
| TypeKind::Bitset { .. } => extensibility_of(ast, node, key) != Extensibility::Final,
_ => false,
})
}
#[must_use]
pub fn array_is_delimited(ast: &Ast, element: Id<TypeNode>, key: LayoutKey) -> bool {
if matches!(
key.array_element_flattening,
crate::key::ArrayElementFlattening::StopsAtAlias
) {
return collection_is_delimited(ast, element, key);
}
let mut current = element;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
break;
};
match &node.kind {
TypeKind::Alias { aliased }
| TypeKind::Array {
element: aliased, ..
} => match aliased.resolved() {
Some(next) => current = next,
None => break,
},
_ => break,
}
}
collection_is_delimited(ast, current, key)
}
fn is_primitive_after_aliases(ast: &Ast, type_: Id<TypeNode>) -> bool {
let mut current = type_;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
return false;
};
match &node.kind {
TypeKind::Primitive(_) => return true,
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(next) => current = next,
None => return false,
},
_ => return false,
}
}
false
}
pub const PL_CDR1_ALIGNMENT: u8 = 4;
const SEQUENCE_LENGTH_ALIGNMENT: u8 = 4;
fn through_aliases(ast: &Ast, type_: Id<TypeNode>) -> Id<TypeNode> {
let mut current = type_;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
return current;
};
match &node.kind {
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(next) => current = next,
None => return current,
},
_ => return current,
}
}
current
}
fn closes_a_cycle(ast: &Ast, element: Id<TypeNode>, deps: &DepCollector) -> bool {
deps.is_active(element) || deps.is_active(through_aliases(ast, element))
}
fn closes_a_cycle_without_length(
ast: &Ast,
member_type: Id<TypeNode>,
deps: &DepCollector,
) -> bool {
deps.is_active_without_barrier(member_type)
|| deps.is_active_without_barrier(through_aliases(ast, member_type))
}
fn has_finite_value(
ast: &Ast,
type_: Id<TypeNode>,
active: &mut alloc::collections::BTreeSet<Id<TypeNode>>,
depth: usize,
) -> bool {
let type_ = through_aliases(ast, type_);
if depth > MAX_DEPTH || !active.insert(type_) {
return false;
}
let finite = ast.type_(type_).is_ok_and(|node| match &node.kind {
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
ast.all_members_of(type_).into_iter().all(|member| {
ast.member(member).is_ok_and(|member| {
member.facets.is_optional()
|| member
.type_
.resolved()
.is_some_and(|type_| has_finite_value(ast, type_, active, depth + 1))
})
})
}
TypeKind::Union { .. } => ast.cases_of(type_).iter().any(|case| {
ast.case(*case)
.ok()
.and_then(|case| ast.member(case.member).ok())
.and_then(|member| member.type_.resolved())
.is_some_and(|type_| has_finite_value(ast, type_, active, depth + 1))
}),
TypeKind::Array { element, .. } => element
.resolved()
.is_some_and(|type_| has_finite_value(ast, type_, active, depth + 1)),
TypeKind::Primitive(_)
| TypeKind::Fixed { .. }
| TypeKind::String { .. }
| TypeKind::Sequence { .. }
| TypeKind::Map { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. } => true,
_ => false,
});
active.remove(&type_);
finite
}
fn finite_external_member(ast: &Ast, member: &zerodds_ast::MemberNode) -> bool {
member.facets.xtypes.as_ref().is_some_and(|x| x.is_external)
&& member.type_.resolved().is_some_and(|type_| {
has_finite_value(ast, type_, &mut alloc::collections::BTreeSet::new(), 0)
})
}
#[derive(Debug, Clone, Default)]
pub struct DepCollector {
seen: Vec<NodeId>,
active: Vec<(Id<TypeNode>, usize)>,
barriers: usize,
}
impl DepCollector {
#[must_use]
pub const fn new() -> Self {
Self {
seen: Vec::new(),
active: Vec::new(),
barriers: 0,
}
}
pub fn note(&mut self, node: NodeId) {
if !self.seen.contains(&node) {
self.seen.push(node);
}
}
fn is_active(&self, type_: Id<TypeNode>) -> bool {
self.active.iter().any(|(id, _)| *id == type_)
}
fn is_active_without_barrier(&self, type_: Id<TypeNode>) -> bool {
self.active
.iter()
.any(|(id, at)| *id == type_ && *at == self.barriers)
}
#[must_use]
pub fn into_deps(self) -> Vec<NodeId> {
self.seen
}
}
#[must_use]
pub fn primitive_layout(kind: PrimitiveKind, key: LayoutKey, position: WcharPosition) -> (u8, u32) {
if kind == PrimitiveKind::WChar && key.wchar_framing.has_length_octet(position) {
return key
.wchar_framing
.scalar_layout(position, key.wchar_width.octets());
}
let size = match kind {
PrimitiveKind::Boolean
| PrimitiveKind::Octet
| PrimitiveKind::Char
| PrimitiveKind::Int8
| PrimitiveKind::UInt8 => 1,
PrimitiveKind::Int16 | PrimitiveKind::UInt16 => 2,
PrimitiveKind::WChar => u32::from(key.wchar_width.octets()),
PrimitiveKind::Int32 | PrimitiveKind::UInt32 | PrimitiveKind::Float32 => 4,
PrimitiveKind::Int64 | PrimitiveKind::UInt64 | PrimitiveKind::Float64 => 8,
PrimitiveKind::Float128 => 16,
};
let align = size.min(u32::from(key.encoding.max_alignment()));
(u8::try_from(align).unwrap_or(1), size)
}
#[must_use]
pub fn types_that_close_an_inadmissible_cycle(
ast: &Ast,
key: LayoutKey,
) -> Vec<(Id<TypeNode>, NodeId)> {
let mut found = Vec::new();
let mut seen = alloc::collections::BTreeSet::new();
for (id, _) in ast.types() {
let mut deps = DepCollector::new();
if let Err(LayoutError::RecursionOutsideCollection { node }) = plan(ast, id, key, &mut deps)
{
if seen.insert(node) {
found.push((id, node));
}
}
}
found
}
#[must_use]
pub fn member_held_inline_closes_a_cycle(
ast: &Ast,
enclosing: Id<TypeNode>,
member_type: Id<TypeNode>,
) -> bool {
let target = through_aliases(ast, enclosing);
let mut seen = alloc::collections::BTreeSet::new();
reaches_inline(ast, member_type, target, &mut seen, 0)
}
fn reaches_inline(
ast: &Ast,
from: Id<TypeNode>,
target: Id<TypeNode>,
seen: &mut alloc::collections::BTreeSet<Id<TypeNode>>,
depth: usize,
) -> bool {
if depth > MAX_DEPTH {
return false;
}
let from = through_aliases(ast, from);
if from == target {
return true;
}
if !seen.insert(from) {
return false;
}
let Ok(node) = ast.type_(from) else {
return false;
};
match &node.kind {
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
ast.all_members_of(from).into_iter().any(|member| {
ast.member(member)
.ok()
.and_then(|node| node.type_.resolved())
.is_some_and(|type_| reaches_inline(ast, type_, target, seen, depth + 1))
})
}
TypeKind::Union { .. } => ast.cases_of(from).iter().any(|case| {
ast.case(*case)
.ok()
.and_then(|case_node| ast.member(case_node.member).ok())
.and_then(|member| member.type_.resolved())
.is_some_and(|type_| reaches_inline(ast, type_, target, seen, depth + 1))
}),
TypeKind::Array { element, .. } => element
.resolved()
.is_some_and(|element| reaches_inline(ast, element, target, seen, depth + 1)),
TypeKind::Alias { .. }
| TypeKind::Primitive(_)
| TypeKind::Fixed { .. }
| TypeKind::String { .. }
| TypeKind::Sequence { .. }
| TypeKind::ValueBox { .. }
| TypeKind::Map { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Native
| TypeKind::ValueBase
| TypeKind::Any
| TypeKind::AnnotationType { .. }
| TypeKind::ObjectRef { .. } => false,
}
}
pub fn plan(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
deps: &mut DepCollector,
) -> Result<TypeLayout, LayoutError> {
if key.purpose == Purpose::Key
&& key.key_holder == KeyHolderExtensibility::ForcedFinal
&& key.endianness != Endianness::Big
{
return Err(LayoutError::KeyMustBeBigEndian);
}
plan_at(ast, type_, key, WcharPosition::Member, deps, 0)
}
fn plan_at(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
position: WcharPosition,
deps: &mut DepCollector,
depth: usize,
) -> Result<TypeLayout, LayoutError> {
deps.active.push((type_, deps.barriers));
let planned = plan_at_inner(ast, type_, key, position, deps, depth);
deps.active.pop();
planned
}
fn plan_at_inner(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
position: WcharPosition,
deps: &mut DepCollector,
depth: usize,
) -> Result<TypeLayout, LayoutError> {
let node = ast.type_(type_)?;
let node_id = node.node_id;
deps.note(node_id);
if depth > MAX_DEPTH {
return Err(LayoutError::TooDeep { node: node_id });
}
match &node.kind {
TypeKind::Primitive(kind) => {
let (align, size) = primitive_layout(*kind, key, position);
Ok(TypeLayout::scalar(node_id, align, Extent::Sized(size)))
}
TypeKind::Fixed { digits, .. } => {
let extent = if key.fixed_digits.is_default() {
Extent::Sized((u32::from(*digits) + 2) / 2)
} else {
Extent::Unbounded
};
Ok(TypeLayout::scalar(node_id, 1, extent))
}
TypeKind::String { width, .. } => {
let align = match width {
CharWidth::Char => 4,
CharWidth::WChar => 4,
};
Ok(TypeLayout::scalar(node_id, align, Extent::Unbounded))
}
TypeKind::Sequence { element, .. } => {
let element = element
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let align = if closes_a_cycle(ast, element, deps) {
SEQUENCE_LENGTH_ALIGNMENT
} else {
deps.barriers += 1;
let planned = plan_at(
ast,
element,
key,
WcharPosition::inside_collection(),
deps,
depth + 1,
);
deps.barriers -= 1;
planned?.align
};
let mut layout = TypeLayout::scalar(
node_id,
align.max(SEQUENCE_LENGTH_ALIGNMENT),
Extent::Unbounded,
);
layout.needs_dheader = collection_is_delimited(ast, element, key);
Ok(layout)
}
TypeKind::Array { element, dims } => {
let element_id = element
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
if closes_a_cycle_without_length(ast, element_id, deps) {
return Err(LayoutError::RecursionOutsideCollection { node: node_id });
}
let inner = if closes_a_cycle(ast, element_id, deps) {
TypeLayout::scalar(node_id, 1, Extent::Unbounded)
} else {
plan_at(
ast,
element_id,
key,
WcharPosition::inside_collection(),
deps,
depth + 1,
)?
};
let mut count: Option<u32> = Some(1);
for dim in dims {
count = count.and_then(|so_far| {
let value = ast.eval_integral(*dim).ok()?;
so_far.checked_mul(u32::try_from(value).ok()?)
});
}
let mut total = match (count, inner.extent) {
(Some(count), Extent::Sized(size)) if count > 0 => {
let stride = if key.element_padding.pads_after(size, inner.align) {
align_to(size, inner.align)
} else {
size
};
stride
.checked_mul(count.saturating_sub(1))
.and_then(|body| body.checked_add(size))
.map_or(Extent::Unbounded, Extent::Sized)
}
(Some(0), Extent::Sized(_)) => Extent::Sized(0),
_ => Extent::Unbounded,
};
let dheader = array_is_delimited(ast, element_id, key);
if dheader {
total = match total {
Extent::Sized(size) => {
size.checked_add(4).map_or(Extent::Unbounded, Extent::Sized)
}
Extent::Unbounded => Extent::Unbounded,
};
}
let align = if dheader {
inner.align.max(4)
} else {
inner.align
};
let mut layout = TypeLayout::scalar(node_id, align, total);
layout.needs_dheader = dheader;
Ok(layout)
}
TypeKind::Map { key: k, value, .. } => {
let k = k
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let v = value
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let key_align = if closes_a_cycle(ast, k, deps) {
SEQUENCE_LENGTH_ALIGNMENT
} else {
{
deps.barriers += 1;
let planned = plan_at(
ast,
k,
key,
WcharPosition::inside_collection(),
deps,
depth + 1,
);
deps.barriers -= 1;
planned?.align
}
};
let value_align = if closes_a_cycle(ast, v, deps) {
SEQUENCE_LENGTH_ALIGNMENT
} else {
{
deps.barriers += 1;
let planned = plan_at(
ast,
v,
key,
WcharPosition::inside_collection(),
deps,
depth + 1,
);
deps.barriers -= 1;
planned?.align
}
};
let mut layout = TypeLayout::scalar(
node_id,
key_align.max(value_align).max(SEQUENCE_LENGTH_ALIGNMENT),
Extent::Unbounded,
);
layout.needs_dheader = match key.map_delimiter {
MapDelimiter::EitherHalf => {
collection_is_delimited(ast, k, key) || collection_is_delimited(ast, v, key)
}
MapDelimiter::ValueOnly => collection_is_delimited(ast, v, key),
};
Ok(layout)
}
TypeKind::Alias { aliased } => {
let target = aliased
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
plan_at(ast, target, key, position, deps, depth + 1)
}
TypeKind::Enumeration { .. } => {
let bits = enumeration_bit_bound(ast, type_, key.enum_holder);
let size = bit_bound_size(bits);
Ok(TypeLayout::scalar(
node_id,
size,
Extent::Sized(u32::from(size)),
))
}
TypeKind::Bitmask { .. } => {
let bits = node
.facets
.xtypes
.as_ref()
.and_then(|x| x.bit_bound)
.unwrap_or(32);
let size = bit_bound_size(bits);
Ok(TypeLayout::scalar(
node_id,
size,
Extent::Sized(u32::from(size)),
))
}
TypeKind::Bitset { .. } => {
let total_bits: u32 = ast
.all_bitfields_of(type_)
.iter()
.filter_map(|f| ast.bitfield(*f).ok())
.map(|f| u32::from(f.bits))
.sum();
let bytes = total_bits.div_ceil(8).max(1);
let holder = bytes.next_power_of_two().min(8);
let align =
u8::try_from(holder.min(u32::from(key.encoding.max_alignment()))).unwrap_or(1);
Ok(TypeLayout::scalar(node_id, align, Extent::Sized(holder)))
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let members = ast.all_members_of(type_);
plan_aggregate(ast, node_id, &members, node, key, deps, depth)
}
TypeKind::Union {
discriminator,
cases,
..
} => {
let disc = discriminator
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let disc_layout = plan_at(ast, disc, key, position.inside(), deps, depth + 1)?;
if key.purpose == Purpose::Key
&& crate::keyhash::union_key_discriminator(ast, type_).is_some()
{
return Ok(TypeLayout::scalar(
node_id,
disc_layout.align,
disc_layout.extent,
));
}
let extensibility = extensibility_of(ast, node, key);
let mutable = extensibility == Extensibility::Mutable
&& key.aggregate_delimiter.frames_mutable_members();
let mut align = disc_layout.align;
let mut widest = 0u32;
let mut fixed = disc_layout.extent.is_sized();
let mut plans: Vec<MemberPlan> = Vec::new();
let mut next_id = key.union_framing.first_branch_id();
for case in cases {
let Ok(case_node) = ast.case(*case) else {
continue;
};
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let Some(member_type) = member.type_.resolved() else {
return Err(LayoutError::Unresolved { node: node_id });
};
deps.note(member.node_id);
let external = finite_external_member(ast, member);
if !external && closes_a_cycle_without_length(ast, member_type, deps) {
return Err(LayoutError::RecursionOutsideCollection {
node: member.node_id,
});
}
let branch = if closes_a_cycle(ast, member_type, deps) {
TypeLayout::scalar(member.node_id, 1, Extent::Unbounded)
} else {
if external {
deps.barriers += 1;
}
let planned =
plan_at(ast, member_type, key, position.inside(), deps, depth + 1);
if external {
deps.barriers -= 1;
}
planned?
};
align = align.max(branch.align);
match branch.extent {
Extent::Sized(size) => widest = widest.max(size),
Extent::Unbounded => fixed = false,
}
let member_id = member.facets.effective_member_id().unwrap_or(next_id);
next_id = member_id.saturating_add(1);
if mutable {
if external {
deps.barriers += 1;
}
let header = member_header(
ast,
member_type,
member_id,
branch.extent,
member_must_understand(&member.facets, key.must_understand),
key,
deps,
depth,
);
if external {
deps.barriers -= 1;
}
plans.push(MemberPlan {
member: member.node_id,
member_id,
header: header?,
offset: Offset::Dynamic,
align: branch.align,
});
}
}
let needs_dheader = key.aggregate_delimiter.writes_dheader()
&& key.encoding.is_v2()
&& extensibility != Extensibility::Final;
let extent = match (fixed && !mutable, disc_layout.extent) {
(true, Extent::Sized(disc_size)) => {
Extent::Sized(align_to(disc_size, align).saturating_add(widest))
}
_ => Extent::Unbounded,
};
let discriminator = if mutable {
Some(member_header(
ast,
disc,
0,
disc_layout.extent,
key.must_understand.discriminator_is_understood(),
key,
deps,
depth,
)?)
} else {
None
};
Ok(TypeLayout {
type_: node_id,
align,
extent,
needs_dheader,
needs_sentinel: key.encoding.is_v1()
&& mutable
&& key.union_framing.writes_the_sentinel(),
discriminator,
members: plans,
})
}
TypeKind::Native | TypeKind::AnnotationType { .. } => {
Err(LayoutError::UnsupportedForEncoding {
node: node_id,
encoding: key.encoding,
reason: "the type has no defined wire form",
})
}
TypeKind::ObjectRef { .. } => Err(LayoutError::UnsupportedForEncoding {
node: node_id,
encoding: key.encoding,
reason: "an object reference has no wire form in DDS-XTypes; CORBA writes an IOR \
for one, which is GIOP's framing and not XCDR's",
}),
TypeKind::ValueBase => Err(LayoutError::UnsupportedForEncoding {
node: node_id,
encoding: key.encoding,
reason: "`ValueBase` has no wire form in DDS-XTypes, which has no valuetype; CORBA \
CDR writes a chunked value with its repository id ahead of it",
}),
TypeKind::Any => Err(LayoutError::UnsupportedForEncoding {
node: node_id,
encoding: key.encoding,
reason: "`any` has no wire form in DDS-XTypes; it is encoded only by CORBA CDR, \
which writes a TypeCode ahead of the value",
}),
TypeKind::ValueBox { boxed } => {
let target = boxed
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let inner = plan_at(ast, target, key, position, deps, depth + 1)?;
Ok(TypeLayout::scalar(
node_id,
inner.align.max(1),
Extent::Unbounded,
))
}
}
}
fn bit_bound_size(bits: u16) -> u8 {
match bits {
0..=8 => 1,
9..=16 => 2,
17..=32 => 4,
_ => 8,
}
}
#[must_use]
pub fn member_must_understand(facets: &zerodds_ast::Facets, policy: MustUnderstandPolicy) -> bool {
facets.is_must_understand() || (policy.key_implies_the_flag() && facets.is_key())
}
#[must_use]
pub fn enumeration_bit_bound(ast: &Ast, type_: Id<TypeNode>, policy: EnumHolderWidth) -> u16 {
policy.holder_bound(ast.effective_bit_bound(type_))
}
#[must_use]
pub fn enumerator_wire_value(
ast: &Ast,
enumerator: Id<zerodds_ast::EnumeratorNode>,
policy: EnumeratorValue,
) -> Option<i128> {
match policy {
EnumeratorValue::Declared => ast.effective_enumerator_value(enumerator),
EnumeratorValue::Ordinal => {
let node = ast.enumerator(enumerator).ok()?;
let siblings = ast.enumerators_of(node.parent);
let at = siblings.iter().position(|id| *id == enumerator)?;
i128::try_from(at).ok()
}
}
}
#[must_use]
pub fn union_negative_matches_nothing(
ast: &Ast,
union_: Id<TypeNode>,
policy: UnionLabelMatch,
) -> bool {
if policy.is_default() {
return false;
}
let Ok(node) = ast.type_(union_) else {
return false;
};
let TypeKind::Union { discriminator, .. } = &node.kind else {
return false;
};
let Some(discriminator) = discriminator.resolved() else {
return false;
};
let Some((signed, bits)) = discriminator_signedness(ast, discriminator) else {
return false;
};
if policy.label_matches(-1, signed, bits) {
return false;
}
ast.cases_of(union_).iter().all(|id| {
ast.case(*id)
.ok()
.and_then(|case| ast.member(case.member).ok())
.and_then(|member| member.type_.resolved())
.is_some_and(|type_| branch_has_storage(ast, type_))
})
}
fn branch_has_storage(ast: &Ast, type_: Id<TypeNode>) -> bool {
let mut current = type_;
for _ in 0..MAX_DEPTH {
let Ok(node) = ast.type_(current) else {
return false;
};
match &node.kind {
TypeKind::Alias { aliased } => match aliased.resolved() {
Some(next) => current = next,
None => return false,
},
TypeKind::Primitive(kind) => {
return matches!(
kind,
PrimitiveKind::Boolean
| PrimitiveKind::Octet
| PrimitiveKind::Char
| PrimitiveKind::Int8
| PrimitiveKind::UInt8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64
| PrimitiveKind::Float32
| PrimitiveKind::Float64
);
}
_ => return false,
}
}
false
}
#[must_use]
pub const fn fixed_wire_digits(
declared: u8,
significant: u8,
string_digits: u8,
policy: FixedDigitCount,
) -> u8 {
policy.wire_digits(declared, significant, string_digits)
}
#[must_use]
pub fn map_key_ordering(
ast: &Ast,
map: Id<TypeNode>,
policy: MapEntryOrder,
) -> Option<MapKeyOrdering> {
if policy.is_default() {
return None;
}
let Ok(node) = ast.type_(map) else {
return None;
};
let TypeKind::Map { key, .. } = &node.kind else {
return None;
};
key_ordering(ast, key.resolved()?)
}
fn key_ordering(ast: &Ast, key: Id<TypeNode>) -> Option<MapKeyOrdering> {
let mut current = key;
for _ in 0..MAX_DEPTH {
match &ast.type_(current).ok()?.kind {
TypeKind::Alias { aliased } => current = aliased.resolved()?,
TypeKind::Primitive(kind) => {
return Some(match kind {
PrimitiveKind::Int8
| PrimitiveKind::Int16
| PrimitiveKind::Int32
| PrimitiveKind::Int64
| PrimitiveKind::Char => MapKeyOrdering::SignedInteger,
PrimitiveKind::Boolean
| PrimitiveKind::Octet
| PrimitiveKind::UInt8
| PrimitiveKind::UInt16
| PrimitiveKind::UInt32
| PrimitiveKind::UInt64
| PrimitiveKind::WChar => MapKeyOrdering::UnsignedInteger,
_ => return None,
});
}
TypeKind::Enumeration { .. } => return Some(MapKeyOrdering::EnumeratorValue),
TypeKind::String { .. } => return Some(MapKeyOrdering::CodeUnits),
_ => return None,
}
}
None
}
fn discriminator_signedness(ast: &Ast, discriminator: Id<TypeNode>) -> Option<(bool, u16)> {
let mut current = discriminator;
for _ in 0..64 {
match &ast.type_(current).ok()?.kind {
TypeKind::Alias { aliased } => current = aliased.resolved()?,
TypeKind::Primitive(kind) => {
return Some(match kind {
PrimitiveKind::Int8 => (true, 8),
PrimitiveKind::Int16 => (true, 16),
PrimitiveKind::Int32 => (true, 32),
PrimitiveKind::Int64 => (true, 64),
PrimitiveKind::UInt8 | PrimitiveKind::Octet => (false, 8),
PrimitiveKind::UInt16 => (false, 16),
PrimitiveKind::UInt32 => (false, 32),
PrimitiveKind::UInt64 => (false, 64),
_ => return None,
});
}
_ => return None,
}
}
None
}
#[must_use]
pub fn extensibility_of(ast: &Ast, node: &TypeNode, key: LayoutKey) -> Extensibility {
if key.purpose == Purpose::Key && key.key_holder == KeyHolderExtensibility::ForcedFinal {
return Extensibility::Final;
}
let mut current = node;
for _ in 0..crate::plan::MAX_DEPTH {
if let Some(extensibility) = current.facets.extensibility() {
return extensibility;
}
let TypeKind::Structure {
base: Some(base), ..
} = ¤t.kind
else {
break;
};
let Some(base) = base.resolved().and_then(|id| ast.type_(id).ok()) else {
break;
};
current = base;
}
key.default_extensibility
}
#[allow(clippy::too_many_lines)]
fn plan_aggregate(
ast: &Ast,
node_id: NodeId,
members: &[Id<zerodds_ast::MemberNode>],
node: &TypeNode,
key: LayoutKey,
deps: &mut DepCollector,
depth: usize,
) -> Result<TypeLayout, LayoutError> {
let extensibility = extensibility_of(ast, node, key);
let mutable =
extensibility == Extensibility::Mutable && key.aggregate_delimiter.frames_mutable_members();
let needs_dheader = key.aggregate_delimiter.writes_dheader()
&& key.encoding.is_v2()
&& extensibility != Extensibility::Final;
let mut plans: Vec<MemberPlan> = Vec::new();
let mut cursor = 0u32;
let mut align = 1u8;
let mut fixed = true;
let mut next_id = 0u32;
let holder = key.purpose == Purpose::Key;
let only_keys = holder && crate::keyhash::any_key_member(ast, members);
let subtree = crate::keyhash::member_key_for(key, holder && !only_keys);
let mut ordered: Vec<(Id<zerodds_ast::MemberNode>, u32)> = Vec::new();
for member in members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
deps.note(member_node.node_id);
let member_id = member_node.facets.effective_member_id().unwrap_or(next_id);
next_id = member_id.saturating_add(1);
if only_keys && !member_node.facets.is_key() {
continue;
}
ordered.push((*member, member_id));
}
if holder && key.key_holder_order == crate::key::KeyHolderOrder::AscendingMemberId {
ordered.sort_by_key(|(_, member_id)| *member_id);
}
for (member, member_id) in &ordered {
let member_id = *member_id;
let Ok(member_node) = ast.member(*member) else {
continue;
};
let member_type = member_node
.type_
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
let optional = member_node.facets.is_optional();
let terminates = optional || finite_external_member(ast, member_node);
if !terminates && closes_a_cycle_without_length(ast, member_type, deps) {
return Err(LayoutError::RecursionOutsideCollection {
node: member_node.node_id,
});
}
let inner = if closes_a_cycle(ast, member_type, deps) {
TypeLayout::scalar(member_node.node_id, 1, Extent::Unbounded)
} else {
if terminates {
deps.barriers += 1;
}
let planned = plan_at(
ast,
member_type,
subtree,
WcharPosition::Member,
deps,
depth + 1,
);
if terminates {
deps.barriers -= 1;
}
planned?
};
let must_understand = member_must_understand(&member_node.facets, key.must_understand);
let header = if mutable {
if terminates {
deps.barriers += 1;
}
let header = member_header(
ast,
member_type,
member_id,
inner.extent,
must_understand,
key,
deps,
depth,
);
if terminates {
deps.barriers -= 1;
}
header?
} else if optional {
if key.encoding.is_v1() && key.aggregate_delimiter.frames_mutable_members() {
MemberHeader::OptionalCdr1 {
pid: u16::try_from(member_id).unwrap_or(u16::MAX),
extended: needs_extended_pid(
member_id,
inner.extent,
key.pl_cdr1_headers,
key.short_pid_range,
) || extended_as_constructed(ast, member_type, key)?,
must_understand: must_understand && key.member_pid_carries_must_understand(),
}
} else {
MemberHeader::OptionalFlag
}
} else {
MemberHeader::NoHeader
};
align = align.max(inner.align);
let offset = if fixed && matches!(header, MemberHeader::NoHeader) {
let at = align_to(cursor, inner.align);
Offset::Fixed(at)
} else {
Offset::Dynamic
};
if let (Offset::Fixed(at), Extent::Sized(size)) = (offset, inner.extent) {
cursor = at.saturating_add(size);
} else {
fixed = false;
}
plans.push(MemberPlan {
member: member_node.node_id,
member_id,
header,
offset,
align: inner.align,
});
}
let extent = if !fixed || mutable {
Extent::Unbounded
} else if needs_dheader {
Extent::Sized(cursor.saturating_add(4))
} else {
Extent::Sized(cursor)
};
Ok(TypeLayout {
type_: node_id,
align,
extent,
needs_dheader,
needs_sentinel: mutable && key.encoding.is_v1(),
discriminator: None,
members: plans,
})
}
#[allow(clippy::too_many_arguments)]
fn member_header(
ast: &Ast,
member_type: Id<TypeNode>,
member_id: u32,
extent: Extent,
must_understand: bool,
key: LayoutKey,
deps: &mut DepCollector,
depth: usize,
) -> Result<MemberHeader, LayoutError> {
Ok(if key.encoding.is_v1() {
MemberHeader::PidCdr1 {
pid: u16::try_from(member_id).unwrap_or(u16::MAX),
extended: needs_extended_pid(
member_id,
extent,
key.pl_cdr1_headers,
key.short_pid_range,
) || extended_as_constructed(ast, member_type, key)?,
must_understand: must_understand && key.member_pid_carries_must_understand(),
}
} else {
MemberHeader::EmHeader {
lc: length_code(ast, member_type, key, deps, depth + 1)?,
must_understand: must_understand && key.must_understand.writes_flag(),
}
})
}
fn extended_as_constructed(
ast: &Ast,
member_type: Id<TypeNode>,
key: LayoutKey,
) -> Result<bool, LayoutError> {
if !key.pl_cdr1_headers.extends_constructed() || key.purpose != Purpose::Data {
return Ok(false);
}
Ok(!crate::large_sample::keeps_short_header(ast, member_type)?)
}
#[must_use]
pub fn needs_extended_pid(
member_id: u32,
extent: Extent,
policy: PlCdr1HeaderPolicy,
range: ShortPidRange,
) -> bool {
if policy.always_extended() {
return true;
}
if member_id > u32::from(range.highest_short_id()) {
return true;
}
match extent {
Extent::Sized(size) => size > PID_SHORT_MAX_LEN,
Extent::Unbounded => false,
}
}
pub fn length_code(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
deps: &mut DepCollector,
depth: usize,
) -> Result<LengthCode, LayoutError> {
let node = ast.type_(type_)?;
let node_id = node.node_id;
deps.note(node_id);
if depth > MAX_DEPTH {
return Err(LayoutError::TooDeep { node: node_id });
}
if key.length_codes == LengthCodePolicy::UniversalNextInt {
return Ok(LengthCode::Lc4);
}
if closes_a_cycle(ast, type_, deps) {
return Ok(recursive_length_code(ast, type_, key));
}
if !key.length_codes.reuses_a_leading_word() {
let inner = plan_at(ast, type_, key, WcharPosition::Member, deps, depth + 1)?;
return Ok(short_code_or_nextint(inner.extent, key));
}
Ok(match &node.kind {
TypeKind::Alias { aliased } => {
let target = aliased
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
return length_code(ast, target, key, deps, depth + 1);
}
TypeKind::String { .. } => LengthCode::Lc5,
TypeKind::Map { .. } => {
let inner = plan_at(ast, type_, key, WcharPosition::Member, deps, depth + 1)?;
if inner.needs_dheader {
LengthCode::Lc5
} else {
LengthCode::Lc4
}
}
TypeKind::Sequence { element, .. } => {
let element = element
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
if collection_is_delimited(ast, element, key) {
LengthCode::Lc5
} else {
return sequence_length_code(ast, element, key, 0);
}
}
TypeKind::Array { element, .. } => {
let element = element
.resolved()
.ok_or(LayoutError::Unresolved { node: node_id })?;
if array_is_delimited(ast, element, key) {
LengthCode::Lc5
} else {
let inner = plan_at(ast, type_, key, WcharPosition::Member, deps, depth + 1)?;
short_code_or_nextint(inner.extent, key)
}
}
TypeKind::Primitive(kind) => {
let (_, size) = primitive_layout(*kind, key, WcharPosition::Member);
code_for_size(size)
}
TypeKind::Enumeration { .. } => {
let bits = enumeration_bit_bound(ast, type_, key.enum_holder);
code_for_size(u32::from(bit_bound_size(bits)))
}
TypeKind::Bitmask { .. } => {
let bits = node
.facets
.xtypes
.as_ref()
.and_then(|x| x.bit_bound)
.unwrap_or(32);
code_for_size(u32::from(bit_bound_size(bits)))
}
TypeKind::Bitset { .. } => {
let inner = plan_at(ast, type_, key, WcharPosition::Member, deps, depth + 1)?;
match inner.extent {
Extent::Sized(size) if size <= 8 && size.is_power_of_two() => code_for_size(size),
_ => LengthCode::Lc4,
}
}
_ => nested_length_code(ast, type_, key, deps, depth)?,
})
}
fn short_code_or_nextint(extent: Extent, key: LayoutKey) -> LengthCode {
match extent {
Extent::Sized(size)
if key.length_codes.short_code_where_it_fits()
&& size <= 8
&& size.is_power_of_two() =>
{
code_for_size(size)
}
_ => LengthCode::Lc4,
}
}
fn recursive_length_code(ast: &Ast, type_: Id<TypeNode>, key: LayoutKey) -> LengthCode {
if key.length_codes.reuses_nested_delimiter() && opens_with_dheader(ast, type_, key) {
LengthCode::Lc5
} else {
LengthCode::Lc4
}
}
fn opens_with_dheader(ast: &Ast, type_: Id<TypeNode>, key: LayoutKey) -> bool {
let target = through_aliases(ast, type_);
let Ok(node) = ast.type_(target) else {
return false;
};
if !matches!(
node.kind,
TypeKind::Structure { .. } | TypeKind::Union { .. } | TypeKind::Valuetype { .. }
) {
return false;
}
key.encoding.is_v2() && extensibility_of(ast, node, key) != Extensibility::Final
}
fn nested_length_code(
ast: &Ast,
type_: Id<TypeNode>,
key: LayoutKey,
deps: &mut DepCollector,
depth: usize,
) -> Result<LengthCode, LayoutError> {
let inner = plan_at(ast, type_, key, WcharPosition::Member, deps, depth + 1)?;
if inner.needs_dheader && key.length_codes.reuses_nested_delimiter() {
return Ok(LengthCode::Lc5);
}
Ok(short_code_or_nextint(inner.extent, key))
}
fn sequence_length_code(
ast: &Ast,
element: Id<TypeNode>,
key: LayoutKey,
depth: usize,
) -> Result<LengthCode, LayoutError> {
let node = ast.type_(element)?;
if depth > MAX_DEPTH {
return Err(LayoutError::TooDeep { node: node.node_id });
}
Ok(match &node.kind {
TypeKind::Alias { aliased } => {
let target = aliased
.resolved()
.ok_or(LayoutError::Unresolved { node: node.node_id })?;
return sequence_length_code(ast, target, key, depth + 1);
}
TypeKind::Primitive(kind) => {
let (_, size) = primitive_layout(*kind, key, WcharPosition::inside_collection());
code_for_element_size(size)
}
TypeKind::Enumeration { .. } => {
let bits = enumeration_bit_bound(ast, element, key.enum_holder);
code_for_element_size(u32::from(bit_bound_size(bits)))
}
TypeKind::Bitmask { .. } => {
let bits = node
.facets
.xtypes
.as_ref()
.and_then(|x| x.bit_bound)
.unwrap_or(32);
code_for_element_size(u32::from(bit_bound_size(bits)))
}
_ => LengthCode::Lc4,
})
}
fn code_for_element_size(size: u32) -> LengthCode {
match size {
1 => LengthCode::Lc5,
4 => LengthCode::Lc6,
8 => LengthCode::Lc7,
_ => LengthCode::Lc4,
}
}
fn code_for_size(size: u32) -> LengthCode {
match size {
1 => LengthCode::Lc0,
2 => LengthCode::Lc1,
4 => LengthCode::Lc2,
8 => LengthCode::Lc3,
_ => LengthCode::Lc4,
}
}